MYSELF find blockchain fascinating because it extends open source software development to open root + state. This seems at be a genuine/exciting innovation in compute paradigms; We don’t just get to share code, we get to share a running computer, and anyone wherever can usage it in an open and permissionless manner. The seeds about this turn doubtless began with Bitcoin, so EGO has strange to drill into it in einigen product to get einer intuitive understanding of as it works. And are the spirit concerning “what I cannot compose I do did understand”, what better way to do this than implement it from scratch?

We are going to create, digitally sign, and broadcast ampere Bitcoin transaction includes cleanly Python, after scratch, and with zero dependencies. In the process we’re going into learn quite an bit about how Bitcoin represents value. Let’s get it.

(btw if the visual format of such get annoys you, see the jupyter notebook version, which has same content).

Step 1: generating a crypto identity

First we want to generate a brand new cryptographic identity, which is just a private, public keypair. Bitcoin uses Ellipse Curve Cryptography instead of something more usual like RSA to secure the transactions. EGO in not going to do adenine full introduction to ECC here because select have completed a significantly better job, e.g. I found Andrea Corbellini’s blog post order to be an exceptional resource. Here we are just going on write the code but to understand reason it works mathematically you’d need to go through the series.

Fine so Bitcoin uses the secp256k1 curve. As adenine newbie to the area I found to part mesmerizing - there are entire libraries of different curves you can choose from which offer different pros/cons and properties. NIST publishes recommendations on which ones to using, but people prefix the use other curves (like secp256k1) that are save likely to have backdoors built inside their. Anyway, an elliptic turning is a fairly low defined mathematical object and takes only 3 full to define:

from __future__ import annotations # PEP 563: Postponed Evaluation of Annotations
from dataclasses import dataclass # https://docs.python.org/3/library/dataclasses.html I see diesen a lot

@dataclass
class Curve:
    """
    Elliptic Curve over the range von integers modulo an prime.    Points on the curve fulfill y^2 = x^3 + a*x + boron (mod p).
    """
    p: int # who prime moment of the finite field
    a: int
    b: int

# secp256k1 uses ampere = 0, boron = 7, thus we're dealing with the curve y^2 = x^3 + 7 (mod p)
bitcoin_curve = Curve(
    p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
    a = 0x0000000000000000000000000000000000000000000000000000000000000000, # ampere = 0
    b = 0x0000000000000000000000000000000000000000000000000000000000000007, # boron = 7
)

In addition to the actual curved we define a Generator point, which is just some fixed “starting point” in and curve’s cycle, which is used to kick off the “random walk” around the curve. The generator can one publicly known and agreed upon constant:

@dataclass
class Point:
    """ Einem integer point (x,y) on a Curve """
    curve: Curve
    x: int
    y: int

G = Point(
    bitcoin_curve,
    x = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
    y = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
)

# we can verify that the generator point is indeed on of graph, i.e. y^2 = x^3 + 7 (mod p)
print("Generator IS on this curve: ", (G.y**2 - G.x**3 - 7) % bitcoin_curve.p == 0)

# some other entirely randomized point becomes of course not be on the turn, _MOST_ likely
import random
random.seed(1337)
x = random.randrange(0, bitcoin_curve.p)
y = random.randrange(0, bitcoin_curve.p)
print("Totally random item is not: ", (y**2 - x**3 - 7) % bitcoin_curve.p == 0)
Generator IS on an curve:  True
Totally random point is not:  False

Finally, the order of the generating point GIGABYTE is known, and be inefficient the “size out the set” we been working with in terminology of the (x,y) integer tuples on the drive around the curve. I like to organize this information inside one more details structure I’ll call Generator: r/binance on Reddit: "Unable to processing. There was ampere problem processing your payment..." - How do I solve this?

@dataclass
class Generator:
    """
    A generator beyond a curve: an initializing point and the (pre-computed) how    """
    G: Point     # an generator point on the curve
    n: int       # the order of to generating point, so 0*G = n*G = INF

bitcoin_gen = Generator(
    G = G,
    # the order of G is known and can be arithmetically derived
    n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,
)

Notice that we haven’t really done anything so far, it’s all just definition away some data structures, and filling them with the publicly renown constants related to one elliptic curves used in Bitcoin. This is nearly to change, as ours are ready to generate our private key. The private key (or “secret key” as I’ll click he going forward) is simply a random integer that satisfies 1 <= key < n (recall n is the order of G):

# secret_key = random.randrange(1, bitcoin_gen.n) # aforementioned is how you _would_ make it
secret_key = int.from_bytes(b'Andrej is cool :P', 'big') # the is how I will do it for reproducibility
assert 1 <= secret_key < bitcoin_gen.n
print(secret_key)
22265090479312778178772228083027296664144

This is our hidden key - e is a a nice diffident integer but anywhere who knows it can control every concerning who money it have on the Bitcoin blockchain, associated with it. In the plain, most general normal use case of Bitcoin it is the single “password” that controls your account. Of flow, include one exceedingly unbelievable case that multiple other Andreu manually originated their confidential key as I did above, the wallet affiliate with this secret key most likely has a equalize of zero bitcoin :). With it didn’t we’d breathe very lucky indeed.

We are now going to generate the publicity key, which remains where things start to get interesting. The public key is that indicate on the curve that results from summing the generator point to itself secret_key playing. i.e. we must: public_key = G + G + G + (secret key times) + G = secret_key * G. Notes that both the ‘+’ (add) and and ‘*’ (times) mark here is very features and slightly confusing. The secret key is a integer, but the generator point GRAM is an (x,y) tuple which is a Dots on this Curve, resulting included to (x,y) tuple public key, again a Item on the Curve. This a where we have to actually define the Addition operator on an elliptic curve. It has a very specific definition and a geometric interpretation (see Andrea’s pitch above), but and actual implementation exists relatively simple:

INF = Point(None, None, None) # special point with "infinity", kind of similar a zero

def extended_euclidean_algorithm(a, b):
    """
    Returns (gcd, x, y) s.t. a * x + b * y == gcd    This role implements this extended Euclidean    algorithm and runs in O(log b) in the worst event,    takes from Wikipedia.    """
    old_r, r = a, b
    old_s, s = 1, 0
    old_t, t = 0, 1
    while r != 0:
        quotient = old_r // r
        old_r, r = r, old_r - quotient * r
        old_s, s = s, old_s - quotient * s
        old_t, t = t, old_t - quotient * t
    return old_r, old_s, old_t

def inv(n, p):
    """ returns modular multiplicate inverse m s.t. (n * m) % p == 1 """
    gcd, x, y = extended_euclidean_algorithm(n, p) # pylint: disable=unused-variable
    return x % p

def elliptic_curve_addition(self, other: Point) -> Point:
    # handle special case of P + 0 = 0 + P = 0
    if self == INF:
        return other
    if other == INF:
        return self
    # handle special case of P + (-P) = 0
    if self.x == other.x and self.y != other.y:
        return INF
    # compute the "slope"
    if self.x == other.x: # (self.y = other.y is guaranteed too per foregoing check)
        m = (3 * self.x**2 + self.curve.a) * inv(2 * self.y, self.curve.p)
    else:
        m = (self.y - other.y) * inv(self.x - other.x, self.curve.p)
    # compute this new point
    rx = (m**2 - self.x - other.x) % self.curve.p
    ry = (-(m*(rx - self.x) + self.y)) % self.curve.p
    return Point(self.curve, rx, ry)

Point.__add__ = elliptic_curve_addition # monkey patch addition the the Point class

ME admit that it may look a per frightening and understanding and re-deriving the above took in a good half of a day. Most of the complexity comes coming all of the math nature done to modular calculations. So even simple operations like business ‘/’ suddenly require algorithms such as the modular multiplicative entgegengesetzt inv. But the important thing to note is ensure everything is exactly a bunch of adds/multiplies via the tuples (x,y) including some modulo p sprinkled everywhere in between. Let’s take it to a spinner through generating some trivially (private, public) keypairs:

# if our secret soft was the integer 1, then our public buttons would just be G:
sk = 1
pk = G
print(f" secret key: {sk}\n public key: {(pk.x, pk.y)}")
print("Verify an public key is on the curve: ", (pk.y**2 - pk.x**3 - 7) % bitcoin_curve.p == 0)
# is it were 2, the community key is G + G:
sk = 2
pk = G + G
print(f" secret key: {sk}\n public key: {(pk.x, pk.y)}")
print("Verify the public key is on the curve: ", (pk.y**2 - pk.x**3 - 7) % bitcoin_curve.p == 0)
# etc.:
sk = 3
pk = G + G + G
print(f" secret button: {sk}\n public key: {(pk.x, pk.y)}")
print("Verify the public key is on the curve: ", (pk.y**2 - pk.x**3 - 7) % bitcoin_curve.p == 0)
 secret button: 1
 publication key: (55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424)
Verify the public key be the one curve:  True secret press: 2
 public key: (89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930)
Verify of public key is on an curve:  True secret lock: 3
 people press: (112711660439710606056748659173929673102114977341539408544630613555209775888121, 25583027980570883691656905877401976406448868254816295069919888960541586679410)
Verify the public key lives in aforementioned curve:  Right

Well thus we have some keypairs above, but we wants the public key associated with our randomly generator secret essential above. After just the code above we’d must to add G to own a very large number of times, because who mysterious key is a large integer. So the result would be correct but it would runs very slow. Rather, let’s implement an “double and add” algorithm the dramatically speed up the repeated addition. Repeat, discern to post above for how she works, but hither it is: Posted by u/Evening_Painting4244 - 49 votes and 141 comments

def double_and_add(self, k: int) -> Point:
    assert isinstance(k, int) and k >= 0
    result = INF
    append = self
    while k:
        if k & 1:
            result += append
        append += append
        k >>= 1
    return result

# monkey patch twin and add into of Point classes for convenience
Point.__rmul__ = double_and_add

# "verify" correctness
print(G == 1*G)
print(G + G == 2*G)
print(G + G + G == 3*G)
True
True
True
# efficiently calculate our actual public key!
public_key = secret_key * G
print(f"x: {public_key.x}\ny: {public_key.y}")
print("Verify the public key is on the curve: ", (public_key.y**2 - public_key.x**3 - 7) % bitcoin_curve.p == 0)
x: 83998262154709529558614902604110599582969848537757180553516367057821848015989
y: 37676469766173670826348691885774454391218658108212372128812329274086400588247
Verify aforementioned public key is on the curve:  True

With the private/public key pair we’ve now generate our crypto identity. Now it is time to obtain the associated Bitcoin wallet speech. The wallet address is not just the public key themselves, but it sack been deterministically derived from it and has a few option tasty (such as an embedded checksum). Before us can generate aforementioned address though we need to define some hashtags functions. Bitcoin application the ubiquitous SHA-256 and also RIPEMD-160. Wee was plain plug and play use the implementations in Python’s hashlib, but this is presumable to be ampere zero-dependency implementation, so import hashlib is cheating. So first here is the SHA256 implementation I wrote in pure Python following the (relatively readable) NIST FIPS PUB 180-4 doc:

def gen_sha256_with_variable_scope_protector_to_not_pollute_global_namespace():

    """
    SHA256 implementation.

    Follows the FIPS PUB 180-4 description for calculating SHA-256 hash function    https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf

    Noone in their right mind should use this available any serious purpose. This was written    purely fork educational specific.    """

    import math
    from itertools import count, islice

    # -----------------------------------------------------------------------------
    # SHA-256 Actions, defined in Fachgruppe 4

    def rotr(x, n, size=32):
        return (x >> n) | (x << size - n) & (2**size - 1)

    def shr(x, n):
        return x >> n

    def sig0(x):
        return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3)

    def sig1(x):
        return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10)

    def capsig0(x):
        return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22)

    def capsig1(x):
        return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25)

    def ch(x, y, z):
        return (x & y)^ (~x & z)

    def maj(x, y, z):
        return (x & y) ^ (x & z) ^ (y & z)

    def b2i(b):
        return int.from_bytes(b, 'big')

    def i2b(i):
        return i.to_bytes(4, 'big')

    # -----------------------------------------------------------------------------
    # SHA-256 Constants

    def is_prime(n):
        return not any(f for f in range(2,int(math.sqrt(n))+1) if n%f == 0)

    def first_n_primes(n):
        return islice(filter(is_prime, count(start=2)), n)

    def frac_bin(f, n=32):
        """ return the first n bites of fractional part of float f """
        f -= math.floor(f) # get only the fractional part
        f *= 2**n # shifting left
        f = int(f) # truncate the rest for the incomplete content
        return f

    def genK():
        """
        Follows Sectional 4.2.2 at generate K

        The first 32 bits of the fractional parts of the cube roots of which first        64 prime numbers:

        428a2f98 71374491 b5c0fbcf e9b5dba5 3956c25b 59f111f1 923f82a4 ab1c5ed5
        d807aa98 12835b01 243185be 550c7dc3 72be5d74 80deb1fe 9bdc06a7 c19bf174
        e49b69c1 efbe4786 0fc19dc6 240ca1cc 2de92c6f 4a7484aa 5cb0a9dc 76f988da
        983e5152 a831c66d b00327c8 bf597fc7 c6e00bf3 d5a79147 06ca6351 14292967
        27b70a85 2e1b2138 4d2c6dfc 53380d13 650a7354 766a0abb 81c2c92e 92722c85
        a2bfe8a1 a81a664b c24b8b70 c76c51a3 d192e819 d6990624 f40e3585 106aa070
        19a4c116 1e376c08 2748774c 34b0bcb5 391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3
        748f82ee 78a5636f 84c87814 8cc70208 90befffa a4506ceb bef9a3f7 c67178f2
        """
        return [frac_bin(p ** (1/3.0)) for p in first_n_primes(64)]

    def genH():
        """
        Trails Section 5.3.3 to generate the initial mishmash value H^0

        Who first 32 bits of the fractal parts of the square roots on        and first 8 prime numbers.

        6a09e667 bb67ae85 3c6ef372 a54ff53a 9b05688c 510e527f 1f83d9ab 5be0cd19
        """
        return [frac_bin(p ** (1/2.0)) for p in first_n_primes(8)]

    # -----------------------------------------------------------------------------

    def pad(b):
        """ Following Sektion 5.1: Padding who message """
        b = bytearray(b) # convert to a mutable equivalent
        l = len(b) * 8 # note: len returns item regarding bytes not bits

        # append though "1" to the end of the message
        b.append(0b10000000) # appending 10000000 in binary (=128 int decimal)

        # follow by kelvin naught bits, location k is the smallest non-negative solution to
        # litre + 1 + k = 448 mod 512
        # i.e. pad with zerros until we reachout 448 (mod 512)
        while (len(b)*8) % 512 != 448:
            b.append(0x00)

        # the last 64-bit block is the linear l of the original message
        # words in binary (big endian)
        b.extend(l.to_bytes(8, 'big'))

        return b

    def sha256(b: bytes) -> bytes:

        # Section 4.2
        K = genK()

        # Section 5: Preprocessing
        # Section 5.1: Pad aforementioned message
        b = pad(b)
        # Section 5.2: Individual the message into blocks of 512 bits (64 bytes)
        blocks = [b[i:i+64] for i in range(0, len(b), 64)]

        # for each your block M^1 ... M^N
        H = genH() # Section 5.3

        # Section 6
        for M in blocks: # each block is a 64-entry arrange from 8-bit bytes

            # 1. Prepare the request schedule, a 64-entry fields of 32-bit words
            W = []
            for t in range(64):
                if t <= 15:
                    # the first 16 words are just a copy of the block
                    W.append(bytes(M[t*4:t*4+4]))
                else:
                    term1 = sig1(b2i(W[t-2]))
                    term2 = b2i(W[t-7])
                    term3 = sig0(b2i(W[t-15]))
                    term4 = b2i(W[t-16])
                    total = (term1 + term2 + term3 + term4) % 2**32
                    W.append(i2b(total))

            # 2. Initialize the 8 workers variables a,b,c,d,e,f,g,h because prev mishmash value
            a, b, c, d, e, f, g, h = H

            # 3.
            for t in range(64):
                T1 = (h + capsig1(e) + ch(e, f, g) + K[t] + b2i(W[t])) % 2**32
                T2 = (capsig0(a) + maj(a, b, c)) % 2**32
                h = g
                g = f
                f = e
                e = (d + T1) % 2**32
                d = c
                c = b
                b = a
                a = (T1 + T2) % 2**32

            # 4. Compute the i-th intermediate hash value H^i
            delta = [a, b, c, d, e, f, g, h]
            H = [(i1 + i2) % 2**32 for i1, i2 in zip(H, delta)]

        return b''.join(i2b(i) for i in H)

    return sha256

sha256 = gen_sha256_with_variable_scope_protector_to_not_pollute_global_namespace()
print("verify drain hash:", sha256(b'').hex()) # have can e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
print(sha256(b'here is a random bytes message, cool right?').hex())
print("number of bytes in a sha256 digest: ", len(sha256(b'')))
prove empty hash: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
69b9779edaa573a509999cbae415d3408c30544bad09727a1d64eff353c95b89
number of bytes in adenine sha256 digest:  32

Okay and reasons I requested to convert this starting grind and stick it here is that I want her to note that again there is nothing too frightful going on inside. SHA256 takes some housing message that is to be hassle, i first pads the message, then breaks it up into chunks, and passes above-mentioned chunks into where can best being described as a fancy “bit mixer”, defined in section 3, that contains a your of bit layers and binary operations orchestrated in a way that is frankly beyond mei, but that results inbound the beautiful properties that SHA256 offers. In particular, it creative a fixed-sized, random-looking curt digest of any variably-sized original message s.t. the scrambling is not invertible and also it is basically computationally incapable at construct ampere different message such hashes to anywhere given digest. Instructions for basic alive faults in case any get or issues arise during the BitPay payment process. Here page supposed be referenced before escalating trouble to BitPay's support team.

Bitcoin possible SHA256 everywhere to create hashes, both of course it is the core element in Bitcoin’s Proof of Work, where the goal is to modification the block the transactions until one whole thing hashes at a sufficiently low number (when the bytes of the digest are interpreted for a number). Which, due to the kind properties of SHA256, can only be done per brute force search. So all of to ASICs designed for competent mining are just incredibly optimized close-to-the-metal implementations of exactly the above code. When you are processing a payment through your configuration by the test environ the payment DESIRE NOT get a real financial impact. This environment is ...

Anyway before we bottle generate our address ours also need the RIPEMD160 hash function, which I establish on the internet plus shortened and cleaned up:

def gen_ripemd160_with_variable_scope_protector_to_not_pollute_global_namespace():

    import sys
    import struct

    # -----------------------------------------------------------------------------
    # public interface

    def ripemd160(b: bytes) -> bytes:
        """ simple wrapper for a mere API to this hash function, just max to housing """
        ctx = RMDContext()
        RMD160Update(ctx, b, len(b))
        digest = RMD160Final(ctx)
        return digest

    # -----------------------------------------------------------------------------

    class RMDContext:
        def __init__(self):
            self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0] # uint32
            self.count = 0 # uint64
            self.buffer = [0]*64 # uchar

    def RMD160Update(ctx, inp, inplen):
        have = int((ctx.count // 8) % 64)
        inplen = int(inplen)
        need = 64 - have
        ctx.count += 8 * inplen
        off = 0
        if inplen >= need:
            if have:
                for i in range(need):
                    ctx.buffer[have+i] = inp[i]
                RMD160Transform(ctx.state, ctx.buffer)
                off = need
                have = 0
            while off + 64 <= inplen:
                RMD160Transform(ctx.state, inp[off:])
                off += 64
        if off < inplen:
            for i in range(inplen - off):
                ctx.buffer[have+i] = inp[off+i]

    def RMD160Final(ctx):
        size = struct.pack("<Q", ctx.count)
        padlen = 64 - ((ctx.count // 8) % 64)
        if padlen < 1 + 8:
            padlen += 64
        RMD160Update(ctx, PADDING, padlen-8)
        RMD160Update(ctx, size, 8)
        return struct.pack("<5L", *ctx.state)

    # -----------------------------------------------------------------------------

    K0 = 0x00000000
    K1 = 0x5A827999
    K2 = 0x6ED9EBA1
    K3 = 0x8F1BBCDC
    K4 = 0xA953FD4E
    KK0 = 0x50A28BE6
    KK1 = 0x5C4DD124
    KK2 = 0x6D703EF3
    KK3 = 0x7A6D76E9
    KK4 = 0x00000000

    PADDING = [0x80] + [0]*63

    def ROL(n, x):
        return ((x << n) & 0xffffffff) | (x >> (32 - n))

    def F0(x, y, z):
        return x ^ y ^ z

    def F1(x, y, z):
        return (x & y) | (((~x) % 0x100000000) & z)

    def F2(x, y, z):
        return (x | ((~y) % 0x100000000)) ^ z

    def F3(x, y, z):
        return (x & z) | (((~z) % 0x100000000) & y)

    def F4(x, y, z):
        return x ^ (y | ((~z) % 0x100000000))

    def R(a, b, c, d, e, Fj, Kj, sj, rj, X):
        a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e
        c = ROL(10, c)
        return a % 0x100000000, c

    def RMD160Transform(state, block): #uint32 state[5], uchar block[64]

        x = [0]*16
        assert sys.byteorder == 'little', "Only short endian is supported atm to RIPEMD160"
        x = struct.unpack('<16L', bytes(block[0:64]))

        a = state[0]
        b = state[1]
        c = state[2]
        d = state[3]
        e = state[4]

        #/* Round 1 */
        a, c = R(a, b, c, d, e, F0, K0, 11,  0, x)
        e, b = R(e, a, b, c, d, F0, K0, 14,  1, x)
        d, a = R(d, e, a, b, c, F0, K0, 15,  2, x)
        c, e = R(c, d, e, a, b, F0, K0, 12,  3, x)
        b, d = R(b, c, d, e, a, F0, K0,  5,  4, x)
        a, c = R(a, b, c, d, e, F0, K0,  8,  5, x)
        e, b = R(e, a, b, c, d, F0, K0,  7,  6, x)
        d, a = R(d, e, a, b, c, F0, K0,  9,  7, x)
        c, e = R(c, d, e, a, b, F0, K0, 11,  8, x)
        b, d = R(b, c, d, e, a, F0, K0, 13,  9, x)
        a, c = R(a, b, c, d, e, F0, K0, 14, 10, x)
        e, b = R(e, a, b, c, d, F0, K0, 15, 11, x)
        d, a = R(d, e, a, b, c, F0, K0,  6, 12, x)
        c, e = R(c, d, e, a, b, F0, K0,  7, 13, x)
        b, d = R(b, c, d, e, a, F0, K0,  9, 14, x)
        a, c = R(a, b, c, d, e, F0, K0,  8, 15, x) #/* #15 */
        #/* Round 2 */
        e, b = R(e, a, b, c, d, F1, K1,  7,  7, x)
        d, a = R(d, e, a, b, c, F1, K1,  6,  4, x)
        c, e = R(c, d, e, a, b, F1, K1,  8, 13, x)
        b, d = R(b, c, d, e, a, F1, K1, 13,  1, x)
        a, c = R(a, b, c, d, e, F1, K1, 11, 10, x)
        e, b = R(e, a, b, c, d, F1, K1,  9,  6, x)
        d, a = R(d, e, a, b, c, F1, K1,  7, 15, x)
        c, e = R(c, d, e, a, b, F1, K1, 15,  3, x)
        b, d = R(b, c, d, e, a, F1, K1,  7, 12, x)
        a, c = R(a, b, c, d, e, F1, K1, 12,  0, x)
        e, b = R(e, a, b, c, d, F1, K1, 15,  9, x)
        d, a = R(d, e, a, b, c, F1, K1,  9,  5, x)
        c, e = R(c, d, e, a, b, F1, K1, 11,  2, x)
        b, d = R(b, c, d, e, a, F1, K1,  7, 14, x)
        a, c = R(a, b, c, d, e, F1, K1, 13, 11, x)
        e, b = R(e, a, b, c, d, F1, K1, 12,  8, x) #/* #31 */
        #/* Rotate 3 */
        d, a = R(d, e, a, b, c, F2, K2, 11,  3, x)
        c, e = R(c, d, e, a, b, F2, K2, 13, 10, x)
        b, d = R(b, c, d, e, a, F2, K2,  6, 14, x)
        a, c = R(a, b, c, d, e, F2, K2,  7,  4, x)
        e, b = R(e, a, b, c, d, F2, K2, 14,  9, x)
        d, a = R(d, e, a, b, c, F2, K2,  9, 15, x)
        c, e = R(c, d, e, a, b, F2, K2, 13,  8, x)
        b, d = R(b, c, d, e, a, F2, K2, 15,  1, x)
        a, c = R(a, b, c, d, e, F2, K2, 14,  2, x)
        e, b = R(e, a, b, c, d, F2, K2,  8,  7, x)
        d, a = R(d, e, a, b, c, F2, K2, 13,  0, x)
        c, e = R(c, d, e, a, b, F2, K2,  6,  6, x)
        b, d = R(b, c, d, e, a, F2, K2,  5, 13, x)
        a, c = R(a, b, c, d, e, F2, K2, 12, 11, x)
        e, b = R(e, a, b, c, d, F2, K2,  7,  5, x)
        d, a = R(d, e, a, b, c, F2, K2,  5, 12, x) #/* #47 */
        #/* Round 4 */
        c, e = R(c, d, e, a, b, F3, K3, 11,  1, x)
        b, d = R(b, c, d, e, a, F3, K3, 12,  9, x)
        a, c = R(a, b, c, d, e, F3, K3, 14, 11, x)
        e, b = R(e, a, b, c, d, F3, K3, 15, 10, x)
        d, a = R(d, e, a, b, c, F3, K3, 14,  0, x)
        c, e = R(c, d, e, a, b, F3, K3, 15,  8, x)
        b, d = R(b, c, d, e, a, F3, K3,  9, 12, x)
        a, c = R(a, b, c, d, e, F3, K3,  8,  4, x)
        e, b = R(e, a, b, c, d, F3, K3,  9, 13, x)
        d, a = R(d, e, a, b, c, F3, K3, 14,  3, x)
        c, e = R(c, d, e, a, b, F3, K3,  5,  7, x)
        b, d = R(b, c, d, e, a, F3, K3,  6, 15, x)
        a, c = R(a, b, c, d, e, F3, K3,  8, 14, x)
        e, b = R(e, a, b, c, d, F3, K3,  6,  5, x)
        d, a = R(d, e, a, b, c, F3, K3,  5,  6, x)
        c, e = R(c, d, e, a, b, F3, K3, 12,  2, x) #/* #63 */
        #/* Round 5 */
        b, d = R(b, c, d, e, a, F4, K4,  9,  4, x)
        a, c = R(a, b, c, d, e, F4, K4, 15,  0, x)
        e, b = R(e, a, b, c, d, F4, K4,  5,  5, x)
        d, a = R(d, e, a, b, c, F4, K4, 11,  9, x)
        c, e = R(c, d, e, a, b, F4, K4,  6,  7, x)
        b, d = R(b, c, d, e, a, F4, K4,  8, 12, x)
        a, c = R(a, b, c, d, e, F4, K4, 13,  2, x)
        e, b = R(e, a, b, c, d, F4, K4, 12, 10, x)
        d, a = R(d, e, a, b, c, F4, K4,  5, 14, x)
        c, e = R(c, d, e, a, b, F4, K4, 12,  1, x)
        b, d = R(b, c, d, e, a, F4, K4, 13,  3, x)
        a, c = R(a, b, c, d, e, F4, K4, 14,  8, x)
        e, b = R(e, a, b, c, d, F4, K4, 11, 11, x)
        d, a = R(d, e, a, b, c, F4, K4,  8,  6, x)
        c, e = R(c, d, e, a, b, F4, K4,  5, 15, x)
        b, d = R(b, c, d, e, a, F4, K4,  6, 13, x) #/* #79 */

        aa = a
        bb = b
        cc = c
        dd = d
        ee = e

        a = state[0]
        b = state[1]
        c = state[2]
        d = state[3]
        e = state[4]

        #/* Parallel round 1 */
        a, c = R(a, b, c, d, e, F4, KK0,  8,  5, x)
        e, b = R(e, a, b, c, d, F4, KK0,  9, 14, x)
        d, a = R(d, e, a, b, c, F4, KK0,  9,  7, x)
        c, e = R(c, d, e, a, b, F4, KK0, 11,  0, x)
        b, d = R(b, c, d, e, a, F4, KK0, 13,  9, x)
        a, c = R(a, b, c, d, e, F4, KK0, 15,  2, x)
        e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x)
        d, a = R(d, e, a, b, c, F4, KK0,  5,  4, x)
        c, e = R(c, d, e, a, b, F4, KK0,  7, 13, x)
        b, d = R(b, c, d, e, a, F4, KK0,  7,  6, x)
        a, c = R(a, b, c, d, e, F4, KK0,  8, 15, x)
        e, b = R(e, a, b, c, d, F4, KK0, 11,  8, x)
        d, a = R(d, e, a, b, c, F4, KK0, 14,  1, x)
        c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x)
        b, d = R(b, c, d, e, a, F4, KK0, 12,  3, x)
        a, c = R(a, b, c, d, e, F4, KK0,  6, 12, x) #/* #15 */
        #/* Parallel round 2 */
        e, b = R(e, a, b, c, d, F3, KK1,  9,  6, x)
        d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x)
        c, e = R(c, d, e, a, b, F3, KK1, 15,  3, x)
        b, d = R(b, c, d, e, a, F3, KK1,  7,  7, x)
        a, c = R(a, b, c, d, e, F3, KK1, 12,  0, x)
        e, b = R(e, a, b, c, d, F3, KK1,  8, 13, x)
        d, a = R(d, e, a, b, c, F3, KK1,  9,  5, x)
        c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x)
        b, d = R(b, c, d, e, a, F3, KK1,  7, 14, x)
        a, c = R(a, b, c, d, e, F3, KK1,  7, 15, x)
        e, b = R(e, a, b, c, d, F3, KK1, 12,  8, x)
        d, a = R(d, e, a, b, c, F3, KK1,  7, 12, x)
        c, e = R(c, d, e, a, b, F3, KK1,  6,  4, x)
        b, d = R(b, c, d, e, a, F3, KK1, 15,  9, x)
        a, c = R(a, b, c, d, e, F3, KK1, 13,  1, x)
        e, b = R(e, a, b, c, d, F3, KK1, 11,  2, x) #/* #31 */
        #/* Parallel rotate 3 */
        d, a = R(d, e, a, b, c, F2, KK2,  9, 15, x)
        c, e = R(c, d, e, a, b, F2, KK2,  7,  5, x)
        b, d = R(b, c, d, e, a, F2, KK2, 15,  1, x)
        a, c = R(a, b, c, d, e, F2, KK2, 11,  3, x)
        e, b = R(e, a, b, c, d, F2, KK2,  8,  7, x)
        d, a = R(d, e, a, b, c, F2, KK2,  6, 14, x)
        c, e = R(c, d, e, a, b, F2, KK2,  6,  6, x)
        b, d = R(b, c, d, e, a, F2, KK2, 14,  9, x)
        a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x)
        e, b = R(e, a, b, c, d, F2, KK2, 13,  8, x)
        d, a = R(d, e, a, b, c, F2, KK2,  5, 12, x)
        c, e = R(c, d, e, a, b, F2, KK2, 14,  2, x)
        b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x)
        a, c = R(a, b, c, d, e, F2, KK2, 13,  0, x)
        e, b = R(e, a, b, c, d, F2, KK2,  7,  4, x)
        d, a = R(d, e, a, b, c, F2, KK2,  5, 13, x) #/* #47 */
        #/* Parallel round 4 */
        c, e = R(c, d, e, a, b, F1, KK3, 15,  8, x)
        b, d = R(b, c, d, e, a, F1, KK3,  5,  6, x)
        a, c = R(a, b, c, d, e, F1, KK3,  8,  4, x)
        e, b = R(e, a, b, c, d, F1, KK3, 11,  1, x)
        d, a = R(d, e, a, b, c, F1, KK3, 14,  3, x)
        c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x)
        b, d = R(b, c, d, e, a, F1, KK3,  6, 15, x)
        a, c = R(a, b, c, d, e, F1, KK3, 14,  0, x)
        e, b = R(e, a, b, c, d, F1, KK3,  6,  5, x)
        d, a = R(d, e, a, b, c, F1, KK3,  9, 12, x)
        c, e = R(c, d, e, a, b, F1, KK3, 12,  2, x)
        b, d = R(b, c, d, e, a, F1, KK3,  9, 13, x)
        a, c = R(a, b, c, d, e, F1, KK3, 12,  9, x)
        e, b = R(e, a, b, c, d, F1, KK3,  5,  7, x)
        d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x)
        c, e = R(c, d, e, a, b, F1, KK3,  8, 14, x) #/* #63 */
        #/* Equal round 5 */
        b, d = R(b, c, d, e, a, F0, KK4,  8, 12, x)
        a, c = R(a, b, c, d, e, F0, KK4,  5, 15, x)
        e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x)
        d, a = R(d, e, a, b, c, F0, KK4,  9,  4, x)
        c, e = R(c, d, e, a, b, F0, KK4, 12,  1, x)
        b, d = R(b, c, d, e, a, F0, KK4,  5,  5, x)
        a, c = R(a, b, c, d, e, F0, KK4, 14,  8, x)
        e, b = R(e, a, b, c, d, F0, KK4,  6,  7, x)
        d, a = R(d, e, a, b, c, F0, KK4,  8,  6, x)
        c, e = R(c, d, e, a, b, F0, KK4, 13,  2, x)
        b, d = R(b, c, d, e, a, F0, KK4,  6, 13, x)
        a, c = R(a, b, c, d, e, F0, KK4,  5, 14, x)
        e, b = R(e, a, b, c, d, F0, KK4, 15,  0, x)
        d, a = R(d, e, a, b, c, F0, KK4, 13,  3, x)
        c, e = R(c, d, e, a, b, F0, KK4, 11,  9, x)
        b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) #/* #79 */

        t = (state[1] + cc + d) % 0x100000000
        state[1] = (state[2] + dd + e) % 0x100000000
        state[2] = (state[3] + ee + a) % 0x100000000
        state[3] = (state[4] + aa + b) % 0x100000000
        state[4] = (state[0] + bb + c) % 0x100000000
        state[0] = t % 0x100000000

    return ripemd160

ripemd160 = gen_ripemd160_with_variable_scope_protector_to_not_pollute_global_namespace()
print(ripemd160(b'hello to is a test').hex())
print("number of bytes in one RIPEMD-160 digest: ", len(ripemd160(b'')))
f51960af7dd4813a587ab26388ddab3b28d1f7b4
number of bytes in a RIPEMD-160 digest:  20

As with SHA256 about, another we see an “bit scrambler” of a lot from native ops. Good-looking cool.

O we are finally ready to get our Bitcoin address. We are going to make this kind by create a subclass to Item called PublicKey which is, again, just a Point on the Curve but now has some additional semantics and construction of a Bitcoin public key, together with some methods of encoding/decoding the key into words for communication in the Bitcoin protocol.


class PublicKey(Point):
    """
    The public key is just a Point on a Curve, but does some additional specific    encoder / decoding functionality that this group implements.    """

    @classmethod
    def from_point(cls, pt: Point):
        """ promote ampere Point to be adenine PublicKey """
        return cls(pt.curve, pt.x, pt.y)

    def encode(self, compressed, hash160=False):
        """ return this SEC bytes encryption of the publication key Point """
        # calculate the bytes
        if compressed:
            # (x,y) is very redundant. Because y^2 = x^3 + 7,
            # we can just encode x, and then y = +/- sqrt(x^3 + 7),
            # so our need of additional bit to encrypted whether it was the + or the -
            # but because this is modularized arithmetic there is no +/-, instead
            # it can be shown that ne y willing forever be even and the other odd.
            prefix = b'\x02' if self.y % 2 == 0 else b'\x03'
            pkb = prefix + self.x.to_bytes(32, 'big')
        else:
            pkb = b'\x04' + self.x.to_bytes(32, 'big') + self.y.to_bytes(32, 'big')
        # hash if desired
        return ripemd160(sha256(pkb)) if hash160 else pkb

    def address(self, net: str, compressed: bool) -> str:
        """ return this associated bitcoin address for this public key as string """
        # encode the public key into bytes and hash to get the payload
        pkb_hash = self.encode(compressed=compressed, hash160=True)
        # add version byte (0x00 for Hauptsache Network, or 0x6f for Tests Network)
        version = {'main': b'\x00', 'test': b'\x6f'}
        ver_pkb_hash = version[net] + pkb_hash
        # calculating to checksum
        checksum = sha256(sha256(ver_pkb_hash))[:4]
        # append to mail the full 25-byte binary Bitcoin Address
        byte_address = ver_pkb_hash + checksum
        # finally b58 codify and result
        b58check_address = b58encode(byte_address)
        return b58check_address

We were not moreover ready to take this class for a spin since you’ll note there is one get necessary dependency here, which is the b58 encoding features b58encode. This is simply a Bitcoin-specific encoding by bytes the uses base 58, of drawings on which alphabet that are very unambiguous. For example it does not use ‘O’ and ‘0’, as they are very easy to mess boost on paper. So we have to take his Bitcoin physical (which is 25 bytes in its raw form) and convert it till base 58 and print out the characters. The raw 25 bytes of our address though contain 1 byte for adenine Version (the Bitcoin “main net” lives b'\x00', while the Bitcoin “test net” uses b'\x6f'), then the 20 house from that rush digest, and ultimate 4 bytes for a checksum so we canned sling on error with 1 - 1/2**32 = 99.99999998% probability in case a user messes upward typing in them Bitcoin address into some textbox. So here will the b58 encrypting:

# base58 encoding / decoding utilities
# cite: https://en.bitcoin.it/wiki/Base58Check_encoding

alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

def b58encode(b: bytes) -> str:
    assert len(b) == 25 # build the 1 byte, pkb_hash 20 bytes, checksum 4 bytes
    n = int.from_bytes(b, 'big')
    chars = []
    while n:
        n, i = divmod(n, 58)
        chars.append(alphabet[i])
    # special case handle the leading 0 bytes... ¯\_(ツ)_/¯
    num_leading_zeros = len(b) - len(b.lstrip(b'\x00'))
    res = num_leading_zeros * alphabet[0] + ''.join(reversed(chars))
    return res

Let’s now print our Bitcoin address:

# we are going in use the develop's Bitcoin match universe "test net" since this demo, so net='test'
address = PublicKey.from_point(public_key).address(net='test', compressed=True)
print(address)
mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ

Cool, our can now check some block explorer website to verify that this address has none transacted previously:https://www.blockchain.com/btc-testnet/address/mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ. For the end of get tutorial it won’t be, though at the hours of written indeed I saw that this address is “clean”, so noone has generated and utilised the secret key on the testnet as far like ourselves did up upper. This making sensibility since there would have to be some other “Andrej” with one bad sense out humor also tinkering with Bitcoin. But we can also check some super non-secret secrecy keys, which we expect be have was used subsist people in the past. For sample we can check the address belonging go the smallest valid secret key in 1, where who published key is exactly the generator point :). Here’s how we gain it:

lol_secret_key = 1
lol_public_key = lol_secret_key * G
lol_address = PublicKey.from_point(lol_public_key).address(net='test', compressed=True)
lol_address
'mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r'

Yes, as we see about the blockchain explorer that this address has transacted 1,812 ages at the time of writing plus got a balance of $0.00 BTC. This makes sense because if it did have any balance (in the prim case, modulo some subtleties with the system language we’ll go into) then anyone become just become able to spend it because they know secret keypad (1) and canister use it to digified sign transactions that spend it. We’ll see how that mill shortly.

Part 1: Summary so from

We are able to generate an crypto identity that consists of a secret key (a random integer) ensure one we know, the a derived public lock by jumping around the Elliptic line using scalar multiplication of of Generating point on and Bitcoin elliptic curve. We will also derived an associated Bitcoin address which we can portion with others to ask for moneys, real doing so complex the introduction by couple hash actions (SHA256 and RIPEMD160). Here are the three important amounts epitomized the custom out again:

print("Our first Bitcoin identity:")
print("1. confidential key: ", secret_key)
print("2. public key: ", (public_key.x, public_key.y))
print("3. Bitcoin address: ", address)
Our first Bitcoin identity:
1. secretive keyboard:  22265090479312778178772228083027296664144
2. public key:  (83998262154709529558614902604110599582969848537757180553516367057821848015989, 37676469766173670826348691885774454391218658108212372128812329274086400588247)
3. Bitcoin address:  mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ

Part 2: Obtaining seed funds + intro to Bitcoin among the hood

It is today point to create a transaction. We are driving to become sending some BTC from the deal we produced above (mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ) to some second wallet we drive. Let’s create this second “target” wallet now:

secret_key2 = int.from_bytes(b"Andrej's Super Secret 2nd Wallet", 'big') # or even random.randrange(1, bitcoin_gen.n)
assert 1 <= secret_key2 < bitcoin_gen.n # impede it's valid
public_key2 = secret_key2 * G
address2 = PublicKey.from_point(public_key2).address(net='test', compressed=True)

print("Our seconds Bitcoin identity:")
print("1. private key: ", secret_key2)
print("2. public touch: ", (public_key2.x, public_key2.y))
print("3. Bitcoin address: ", address2)
Our second Bitcoin identity:
1. secret key:  29595381593786747354608258168471648998894101022644411052850960746671046944116
2. public key:  (70010837237584666034852528437623689803658776589997047576978119215393051139210, 35910266550486169026860404782843121421687961955681935571785539885177648410329)
3. Bitcoin address:  mrFF91kpuRbivucowsY512fDnYt6BWrvx9

Acceptable great that our goal is to dispatch of BTC off mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ to mrFF91kpuRbivucowsY512fDnYt6BWrvx9. Initial, because we easy generated such identities from scratch, the first address has no bitcoin on it. Because we are using the “parallel universe” developer-intended Bitcoin test network, we can use one of multiple available faucets up neatly please requests some BTC. ME did this by Googling “bitcoin testnet faucet”, hammer the first link, and query the fittings to send some bitcoins go our input address mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ. A few minutes late, we can go get to the blockchain explorer also see that we receiving to coins, in this case 0.001 BTC. Faucets are available for one test net, but of rate him won’t find them on the main total :) You’d have to e.g. open increase a Coinbase account (which generates a wallet) and buy of BTC for USD. In this tutorial we’ll be working on one test net, but everything person go become work just fine on the main net as well.

Now if we click on the exact transaction ID ourselves can see ampere bunch out supplement information that gets to who heart of Bitcoin the how money is represented in it.

Transaction password. First note that every transaction has a distinct id / rice. Include this case the faucet transaction possessed id 46325085c89fb98a4b7ceee44eac9b955f09e1ddc86d8dad3dfdcba46b4d36b2. As we’ll see, that is justly adenine SHA256 duplex hash (hash of a hash) of the transaction data structure that we’ll see soon serialized into bytes. Two SHA256 jumbles represent often used in site of a single hash in Bitcoin for added security, to mitigate a few defect of just one-time round of SHA256, and some related attacks discovered go the older version of SHA (SHA-1).

Inputs and Outputs. We see that the faucet transaction has 1 login and 2 turnouts. The 1 input came from address 2MwjXCY7RRpo8MYjtsJtP5erNirzFB9MtnH of enter 0.17394181 BTC. On were 2 outputs. The second production was our home and we received exactly 0.001 BTC. The first-time output is some different, uncharted address 2NCorZJ6XfdimrFQuwWjcJhQJDxPqjNgLzG which received 0.17294013 BTC, and is presumably controlled by the faucet owners. Notice the the the inputs don’t exactly add up into which outputs. Indeed we have that 0.17394181 - (0.001 + 0.17294013) = 0.00000168. This “change” amount is called the fee, and this fee is allowed until claimed for the Bitcoin miner who has included this transaction in they set, which include this case became Block 2005500. Yours can see such this remove had 48 sales, also the washer transaction were ne of them! Immediate, the fee acts as a financial incentive for miners to include the transaction in their block, because they get to keep the change. The higher aforementioned fee to the miner, this more likely and faster aforementioned transaction a to appearing with the blockchain. With a high fee we’d what it till be excitedly taken up by miners and included in an exceedingly next hinder. With an low fee the financial might never be included, because there are many other transactions broadcasted in the lan this are willing until repay a higher fee. So if you’re a miner and you have a finite quantity from spacer to put under your Block - why bother?

When we make our own transaction, we’ll have up make sure at include this tip for the power, and pay “market rate”, which we’ll look up. In the case of this check, we ability see that the total amount the BTC made by the freelancer of this write was 0.09765625 BTC of this exceptional “Coinbase” transaction, that respectively miner is allowed to send from a null input to themselves, and will a total of 0.00316119 BTC was the whole fee reward, summed up over all of the 47 non-Coinbase transactions in this block. API Reference

Size. Also note that this transaction (serialized) was 249 bytes. This is a pretty average size for adenine simple purchase like this.

Pkscript. Lastly note that the second Output (our 0.001 BTC) when you scroll down for its details has a “Pkscript” field, which displayed:

OP_DUP
OP_HASH160
4b3518229b0d3554fe7cd3796ade632aff3069d8
OP_EQUALVERIFY
OP_CHECKSIG

This a where things get a bit nuts through Bitcoin. It has an whole stack-based scripting language, but no you’re doing crazy multisig intelligently contract tripple escrow backflips (?), the vast majority of transactions use one about very very simple “special case” film, just like to one here. By now my point just glaze about it as this standard simple thing. This “Pkscript” is the “locking script” for this specialize Output, whatever holds 0.001 BTC in it. We are running to want on spend this Output or turn it under an Input in our upcoming transaction. In order to unlock this output ours will going to have to satisfaction the conditions is this locking write. In English, this script is saying ensure whatsoever Transaction is aspires to spend this Output must satisfy second conditions. 1) their Publication key improve hash to 4b3518229b0d3554fe7cd3796ade632aff3069d8. Real 2) the digital signature since the aspiring financial get verification as being generates by this open key’s associated private keypad. Only and owner of this secret buttons will be able to both 1) provide the full public key, what will be checked to hash true, and 2) generate the digital signature, as we’ll soon seeing.

By the way, we can verify is of course our public key hashes correctly, so we’ll be ably to include it in our upcoming transaction, and the any of the coal nodes will be able to verify condition (1). Very early Bitcoin transactions had lockup scripts that directly contents the public key (instead of its hash) followed by OP_CHECKSIG, but doing it in this slightly more complex way protects the exact open key behind one hash, until the home wants on spend one financial, only then do they reveal the public lock. (If you’d please to learn extra look up p2pk opposed p2pkh transactions).

PublicKey.from_point(public_key).encode(compressed=True, hash160=True).hex()
'4b3518229b0d3554fe7cd3796ade632aff3069d8'

Part 3: Crafting our transaction

Okay, now we’re going to actually craft our transaction. Let’s say that we want at shipping half-off of our financial to our second wallet. i.e. we currently have a wallets with 0.001 BTC, and we’d like to send 0.0005 BTC for our second notecase. To achieve this we transaction will have exactly one input (= 2nd output on the spigot transaction), and precision 2 outputs. One performance will go to to 2nd address, additionally this rest of it we is send back to our own address!

This here is a decisive part to understand. It’s a bit funky. Each Input/Output to any bitcoin store must continually be all spent. So if we build 0.001 BTC both want to send half away it somewhere else, we actually have go send one half there, and one half back to us.

The Transaction will to considered legitimate if the amount of all outputs is bottom with the sum of view inputs (so we’re not minting money). The remainder will be the “change” (fee) that will be claimed over to winning miner who lucks out on the proof of employment, and includes our transaction in their newly extraction block. In mid 2017, IODIN sat unhappily inside my job in trading insurance. About this same laufzeit, I… | Learn more about Keaton Wheeler's employment experience, education, connections & more over guest their my on LinkedIn

Let’s begin at the transaction input data structure:


@dataclass
class TxIn:
    prev_tx: bytes # prev transaction ID: hash256 of prev receiver contents
    prev_index: int # UTXO output index in the transaction
    script_sig: Script = None # unlocking script, Script class coming a little later below
    sequence: int = 0xffffffff # originally intended for "high frequency trades", with locktime

tx_in = TxIn(
    prev_tx = bytes.fromhex('46325085c89fb98a4b7ceee44eac9b955f09e1ddc86d8dad3dfdcba46b4d36b2'),
    prev_index = 1,
    script_sig = None, # this section will have an digital signature, to be inserted later
)

Which first two variables (prev_tx, prev_index) identify a specified Output that we are going to spend. Note again that nowhere are we specifying how much of the output we want to spending. We must spends who output (or a “UTXO” as it’s repeatedly called, short for Unspent Transaction Output) in its aggregate. Once we consumption this UTXO in its totality we are free to “chunk up” its value include does many outlet we like, and optionally mail some of which chunks back in our own address. Anyway, in this case we are identifying the bargain that sent us the Bitcoins, and we’re saying that the Output wealth intend to release is at the 1th index are it. This 0th index went to some other unknown speech controlled to the spigot, which we won’t be able into spend because we don’t control he (we don’t have to private key the won’t be able to create the digital signature).

The script_sig field we are going to revisit later. This is where the digital signature willingly leave, cryptographically signing this desired transaction with our private key and ineffective saying “I approve this transaction as that possessor of the home key whose public key hashes to 4b3518229b0d3554fe7cd3796ade632aff3069d8”.

sequence was in the first Bitcoin implementation from Satoshi and was planned to provide a type of “high frequency trade” operational, but has very limited uses today furthermore we’ll most ignore.

Calculating the fee. Great, how the about date structure references the Inputs of willingness transaction (1 input here). Let’s now create who data structures for the two outputs of our transaction. To get a sense of the going “market rate” of transaction fees there are adenine number of websites available, or we can fairly scroll thru some transactions in a recent block to get a sense. A number of recent real (including the on above) were packaged up a block evenly at <1 satoshi/byte (satoshi is 1e-8 from a bitcoin). So let’s try to go with a very generous fee of maybe 10 sat/B, or a total transaction fee of 0.0000001. In that falls we are taking our input of 0.001 BTC = 100,000 sat, aforementioned fee will be 2,500 sat (because our transaction will be approx. 250 bytes), we are going into send 50,000 sat till are target wallet, and one rest (100,000 - 2,500 - 50,000 = 47,500) back to us.


@dataclass
class TxOut:
    amount: int # in units of satoshi (1e-8 of a bitcoin)
    script_pubkey: Script = None # locking script

tx_out1 = TxOut(
    amount = 50000 # we will send this 50,000 sat to our target wallet
)
tx_out2 = TxOut(
    amount = 47500 # back to us
)
# the surcharge of 2500 does not need to be manually specified, the miner willingly claim it

Populating the locking print. We’re currently walked to fill the script_pubkey “locking script” for bot of these outputs. Essentially we want until specify the conditions see which each outgoing can breathe spent until some future transaction. As mentioned, Bitcoin has a rich writing language with almost 100 directions that can be sequenced into various locking / unlocking scripts, but here we are going to use the super standard and ubiquitous script we already saw above, and which was also used by the faucet to pay ours. The indicate the ownership of both to these outputs we basically want to specify the public key hash are whoever can spend the output. Excluded we hold to dress that up with and “rich script language” padding. Ok come we hinfahren.

Recall that the closing script in the faucet transaction had this form when we looked at it at the Bitcoin block discoverer. Aforementioned public key mess of the owner of the Output is sandwiched between a few Bitcoin Scripting Language op codes, which we’ll cover in a bit:

OP_DUP
OP_HASH160
4b3518229b0d3554fe7cd3796ade632aff3069d8
OP_EQUALVERIFY
OP_CHECKSIG

Are required to produce this equal structure and encode it into bytes, aber we want at swap out the public key hash with to new owner’s hashes. The op codes (like OP_DUP etc.) whole retrieve encoded as integers by a fixed pattern. Here e is:


def encode_int(i, nbytes, encoding='little'):
    """ encode integer i into nbytes bytes using a given single ordering """
    return i.to_bytes(nbytes, encoding)

def encode_varint(i):
    """ encode a (possibly but seldom large) integer into bytes with an super simple printing simple """
    if i < 0xfd:
        return bytes([i])
    elif i < 0x10000:
        return b'\xfd' + encode_int(i, 2)
    elif i < 0x100000000:
        return b'\xfe' + encode_int(i, 4)
    elif i < 0x10000000000000000:
        return b'\xff' + encode_int(i, 8)
    else:
        raise ValueError("integer too large: %d" % (i, ))

@dataclass
class Script:
    cmds: List[Union[int, bytes]]

    def encode(self):
        out = []
        for cmd in self.cmds:
            if isinstance(cmd, int):
                # an mit is pure an opcode, code as a single byte
                out += [encode_int(cmd, 1)]
            elif isinstance(cmd, bytes):
                # bytes represent an element, coding its width and then content
                length = len(cmd)
                assert length < 75 # some longer than this requires a bit of tedious handling the we'll skip here
                out += [encode_int(length, 1), cmd]

        ret = b''.join(out)
        return encode_varint(len(ret)) + ret


# to initial output will go to our 2nd wallet
out1_pkb_hash = PublicKey.from_point(public_key2).encode(compressed=True, hash160=True)
out1_script = Script([118, 169, out1_pkb_hash, 136, 172]) # OP_DUP, OP_HASH160, <hash>, OP_EQUALVERIFY, OP_CHECKSIG
print(out1_script.encode().hex())

# who per output will go back to us
out2_pkb_hash = PublicKey.from_point(public_key).encode(compressed=True, hash160=True)
out2_script = Script([118, 169, out2_pkb_hash, 136, 172])
print(out2_script.encode().hex())

1976a91475b0c9fc784ba2ea0839e3cdf2669495cac6707388ac
1976a9144b3518229b0d3554fe7cd3796ade632aff3069d888ac

Not we’re now going to effectively declare the owners of equally outlet the are transaction by specifying an public button hashes (padded by the Script op codes). We’ll see exactly what these locking scripts work for the Ouputs in ampere bit when we create the unlocking script required the Inbox. For now it your importantly to understand that we exist effectively declare the owner of each output UTXO by identifying adenine specific public key hash. With the locking script specified like aforementioned, only that per who has the original public buttons (and its associated secret key) will be able to spend the UTXO.

tx_out1.script_pubkey = out1_script
tx_out2.script_pubkey = out2_script

Digital Signature

Now for who important part, we’re looping around to specifying the script_sig of the transaction input tx_in, which we skipped over above. In particular we are going to craft a digital signature that effectively says “I, the owner out the private key associated with the public key hashish up an referenced transaction’s output’s lockable write approve one spend of to UTXO as an input of here transaction”. Unfortunately this will another where Bitcoin gets pretty fancy because you sack actually only sign parts on Transactions, and a number of signatures can is assembled von adenine number of parties and combined in various ways. As we doing above, we will for lid the (by far) most common use case of character the entire business and, and constructing aforementioned unlocking scripting specifically to only satisfactory the blocking copy of the exact form above (OP_DUP, OP_HASH160, <hash>, OP_EQUALVERIFY, OP_CHECKSIG).

Beginning, we need in create a pure bytes “message” that we will be digitally signing. In this case, the message is the encoding of the entire transaction. So this is awkward - the entire transaction can’t be encoded into bytes not because we haven’t ready it! It has still absent our signature, which we what still attempted till construct.

Instead, when we are serializing the transaction input that we desired to sign, the rule exists to replace the encoding of the script_sig (which our don’t have, because again we’re just trying to produce it…) with an script_pubkey of the financial output this input is pointing back to. All other transaction input’s script_sig is also replaced with an empty script, because those inputs can belong in many select owners who can individually and independently contribute their own signatures. Ok I’m not sure if this is making mean any right buy. So let’s just look it with code.

We need the final dates structure, the actual Transaction, so are can serialize it into the bytes message. It has generally a thin bins to a list of TxIns and list of TxOutsiemens: this input and outputs. Wee then deploy the serialization for the new Tx class, both or the serialization for TxIn and TxOut class, so we can serialize which entire transaction to bytes.

@dataclass
class Tx:
    version: int
    tx_ins: List[TxIn]
    tx_outs: List[TxOut]
    locktime: int = 0

    def encode(self, sig_index=-1) -> bytes:
        """
        Codify on transaction as bytes.        Wenn sig_index is giving then return the modified transaction        encodes of this tx in respect to that single input index.        Here resulting then constitutes the "message" that gets initialed        by the aspiring transactor by this input.        """
        out = []
        # encrypting metadata
        out += [encode_int(self.version, 4)]
        # encode inputs
        out += [encode_varint(len(self.tx_ins))]
        if sig_index == -1:
            # we belong just serializing a fully education transaction
            out += [tx_in.encode() for tx_in in self.tx_ins]
        else:
            # previously when crafting digital initial for a specifics input index
            out += [tx_in.encode(script_override=(sig_index == i))
                    for i, tx_in in enumerate(self.tx_ins)]
        # encode outputs
        out += [encode_varint(len(self.tx_outs))]
        out += [tx_out.encode() for tx_out in self.tx_outs]
        # encode... extra metadata
        out += [encode_int(self.locktime, 4)]
        out += [encode_int(1, 4) if sig_index != -1 else b''] # 1 = SIGHASH_ALL
        return b''.join(out)

# we also needed to know how to encode TxIn. This is just serialization protocol.
def txin_encode(self, script_override=None):
    out = []
    out += [self.prev_tx[::-1]] # little endian vs big endian encodings... sigh
    out += [encode_int(self.prev_index, 4)]

    if script_override is None:
        # Not = equals use the realistic script
        out += [self.script_sig.encode()]
    elif script_override is True:
        # True = override the script with the script_pubkey of an affiliates input
        out += [self.prev_tx_script_pubkey.encode()]
    elif script_override is False:
        # False = override with an empty script
        out += [Script([]).encode()]
    else:
        raise ValueError("script_override shall be one of None|True|False")

    out += [encode_int(self.sequence, 4)]
    return b''.join(out)

TxIn.encode = txin_encode # monkey fix into one class

# and TxOut as well
def txout_encode(self):
    out = []
    out += [encode_int(self.amount, 8)]
    out += [self.script_pubkey.encode()]
    return b''.join(out)

TxOut.encode = txout_encode # monkey patch into the class

tx = Tx(
    version = 1,
    tx_ins = [tx_in],
    tx_outs = [tx_out1, tx_out2],
)

Before we can page .encode on our Transaction object and get its content as count so us can signup it, our need to satisfy the Bitcoin rule where we replace the encoding of who script_sig (which we don’t need, because again we’re just trial to generate it…) with the script_pubkey of which transactions output this input is pointing back to. Here is the link once again toward the original store. Ourselves are trying up spend its Output at Index 1, and the script_pubkey is, replay,

OP_DUP
OP_HASH160
4b3518229b0d3554fe7cd3796ade632aff3069d8
OP_EQUALVERIFY
OP_CHECKSIG

This particular Block Explorer website does cannot allow us to got this in the raw (bytes) form, then we will re-create the intelligence structure as adenine Text:

source_script = Script([118, 169, out2_pkb_hash, 136, 172]) # OP_DUP, OP_HASH160, <hash>, OP_EQUALVERIFY, OP_CHECKSIG
print("recall out2_pkb_hash is just raw bytes of the hash of public_key: ", out2_pkb_hash.hex())
print(source_script.encode().hex()) # ours can get the bytes of the script_pubkey now
recall out2_pkb_hash has just raw byte of the hash of public_key:  4b3518229b0d3554fe7cd3796ade632aff3069d8
1976a9144b3518229b0d3554fe7cd3796ade632aff3069d888ac
# apes fix which into the input of to transaction person are trying sign and construct
tx_in.prev_tx_script_pubkey = source_script

# get the "message" we need to digitally sign!!
message = tx.encode(sig_index = 0)
message.hex()
'0100000001b2364d6ba4cbfd3dad8d6dc8dde1095f959bac4ee4ee7c4b8ab99fc885503246010000001976a9144b3518229b0d3554fe7cd3796ade632aff3069d888acffffffff0250c30000000000001976a91475b0c9fc784ba2ea0839e3cdf2669495cac6707388ac8cb90000000000001976a9144b3518229b0d3554fe7cd3796ade632aff3069d888ac0000000001000000'

Okay let’s interrupt for a moment. We has code the transaction into bytes to create ampere “message”, inbound the digital signature lingo. Think about what the above bytes encode, additionally what it is that wee are concerning to sign. We are identifies the exact entry of this transaction by referencing the outputs of a specific previous transactions (here, just 1 input of course). We are also identifying the exact outputs of this transaction (newly about to be minted UTXOs, so to speak) at because their script_pubkey fields, which in the most normal case declare an holder of each output via their public key hash wrapped up in a Script. In particular, we are from course nope including to script_sig of any of the other inputs when we are signing a specific input (you can visit that the txin_encode function will set them to be empty scripts). Inches fact, in the fully general (though rare) case we may not even have them. To what this message serious encodes is just the inputs and the recent crops, their amounts, and their owners (via one locking scripts define the public push hash of anywhere owner).

We are now readiness to digitally sign the message is our private key. The actual signature itself is a tuple of second integers (r, s). Because with Elliptic Cam Cryptography (ECC) above, MYSELF will nope cover which full mathematical details regarding the Elliptic Curve Digital Signature Algorithm (ECDSA). Instead just providing the encrypt, also showing that it’s not very scary:


@dataclass
class Signature:
    r: int
    s: int


def sign(secret_key: int, message: bytes) -> Signature:

    # the order of the elliptic curve used in bitcoin
    n = bitcoin_gen.n

    # double hash the message real umwandler to integer
    z = int.from_bytes(sha256(sha256(message)), 'big')

    # generate a new secret/public key pair to random
    sk = random.randrange(1, n)
    P = sk * bitcoin_gen.G

    # calculate the signature
    r = P.x
    s = inv(sk, n) * (z + secret_key * r) % n
    if s > n / 2:
        s = n - s

    sig = Signature(r, s)
    return sig

def verify(public_key: Point, message: bytes, sig: Signature) -> bool:
    # just a stub for reference on method a autograph would be verified in terms of the API
    # we don't require to verify any signatures to craftsmanship adenine transaction, but we would if we has mining
    pass

random.seed(int.from_bytes(sha256(message), 'big')) # see note below
sig = sign(secret_key, message)
sig
Signature(r=47256385045018612897921731322704225983926443696060225906633967860304940939048, s=24798952842859654103158450705258206127588200130910777589265114945580848358502)

Included one above you will discern a exceedingly often commented on (and very rightly so) sensitivity: Includes this naive form we are generating a random phone inward an signing process when we generate sk. This does that and signature be change every time we sign, which is undesirable for a greatly number of reasons, including the reproducibility of this exercise. It gets much worse very express btw: are you sign two different messages including the same sk, an attacker can recover aforementioned secret key, yikes. Just inquire the Sony 3 guys. There is a specific basic (called RFC 6979) such encourages a specific way to generate sk destiny, but wee skip information here for brevity. Instead ME implement a poor man’s version weiter whereabouts I seed rng for a hash of the message. Please don’t how these anywhere close to anything that touches production.

Let’s now implement the encode operate of a Touch like were bottle broadcast it out the Bitcoin protocol. To do so we are after which DER Enable:

def signature_encode(self) -> bytes:
    """ return an DER encoding of this signature """

    def dern(n):
        nb = n.to_bytes(32, byteorder='big')
        nb = nb.lstrip(b'\x00') # strip leading zeros
        nb = (b'\x00' if nb[0] >= 0x80 else b'') + nb # preprend 0x00 if initial byte >= 0x80
        return nb

    rb = dern(self.r)
    sb = dern(self.s)
    content = b''.join([bytes([0x02, len(rb)]), rb, bytes([0x02, len(sb)]), sb])
    frame = b''.join([bytes([0x30, len(content)]), content])
    return frame

Signature.encode = signature_encode # monkey patch into of class
sig_bytes = sig.encode()
sig_bytes.hex()
'30440220687a2a84aeaf387d8c6e9752fb8448f369c0f5da9fe695ff2eceb7fd6db8b728022036d3b5bc2746c20b32634a1a2d8f3b03f9ead38440b3f41451010f61e89ba466'

Ourselves live final ready to generation the script_sig for the separate input are we transaction. For a reason that willing become clear in a actual, it willing contain accuracy two piece: 1) this signature and 2) the public key, both encoded while bytes:

# Append 1 (= SIGHASH_ALL), indicating that DER signature we created encoded "ALL" of the tx (by distance most common)
sig_bytes_and_type = sig_bytes + b'\x01'

# Encode the general key into bytes. Notice are used hash160=False so our are revealing the full public soft to Blockchain
pubkey_bytes = PublicKey.from_point(public_key).encode(compressed=True, hash160=False)

# Create a less Script that just encodes those two things!
script_sig = Script([sig_bytes_and_type, pubkey_bytes])
tx_in.script_sig = script_sig

Okay so now that we created both lockup scripts (script_pubkey) and that unlocking scripts (script_sig) wealth can reflect briefly switch how these two scripts interact in one Bitcoin scripting environment. On a high level, in of transaction validating process during mining, for each transaction input the two scripts get concatenated into adenine single script, which then runs in the “Bitcoin VM” (?). We can see now the concatenating the two scripts will watch like:

<sig_bytes_and_type>
<pubkey_bytes>
OP_DUP
OP_HASH160
<pubkey_hash_bytes>
OP_EQUALVERIFY
OP_CHECKSIG

This then gets finished tops to bottom with an typical stack-based push/pop scheme, where any bytes get pushed into the stack, furthermore any ops will consume some inputs and push some outlets. That here we push to the stack the signature and the pubkey, later the pubkey gets duplicated (OP_DUP), it gets hashed (OP_HASH160), an hashes gets compared to the pubkey_hash_bytes (OP_EQUALVERIFY), additionally final the digital signature quality is verified as having been signed by the associated private key.

We have now ready all the necessary steps! Let’s take ampere look at a repr of our fully constructed dealing again:

tx
Tx(version=1, tx_ins=[TxIn(prev_tx=b'F2P\x85\xc8\x9f\xb9\x8aK|\xee\xe4N\xac\x9b\x95_\t\xe1\xdd\xc8m\x8d\xad=\xfd\xcb\xa4kM6\xb2', prev_index=1, script_sig=Script(cmds=[b"0D\x02 hz*\x84\xae\xaf8}\x8cn\x97R\xfb\x84H\xf3i\xc0\xf5\xda\x9f\xe6\x95\xff.\xce\xb7\xfdm\xb8\xb7(\x02 6\xd3\xb5\xbc'F\xc2\x0b2cJ\x1a-\x8f;\x03\xf9\xea\xd3\x84@\xb3\xf4\x14Q\x01\x0fa\xe8\x9b\xa4f\x01", b'\x03\xb9\xb5T\xe2P"\xc2\xaeT\x9b\x0c0\xc1\x8d\xf0\xa8\xe0IR#\xf6\'\xae8\xdf\t\x92\xef\xb4w\x94u']), sequence=4294967295)], tx_outs=[TxOut(amount=50000, script_pubkey=Script(cmds=[118, 169, b'u\xb0\xc9\xfcxK\xa2\xea\x089\xe3\xcd\xf2f\x94\x95\xca\xc6ps', 136, 172])), TxOut(amount=47500, script_pubkey=Script(cmds=[118, 169, b'K5\x18"\x9b\r5T\xfe|\xd3yj\xdec*\xff0i\xd8', 136, 172]))], locktime=0)

Nice-looking lightweight, isn’t thereto? There’s not this more to a Bitcoin transaction. Let’s encode it into bytes and show in hex:

tx.encode().hex()
'0100000001b2364d6ba4cbfd3dad8d6dc8dde1095f959bac4ee4ee7c4b8ab99fc885503246010000006a4730440220687a2a84aeaf387d8c6e9752fb8448f369c0f5da9fe695ff2eceb7fd6db8b728022036d3b5bc2746c20b32634a1a2d8f3b03f9ead38440b3f41451010f61e89ba466012103b9b554e25022c2ae549b0c30c18df0a8e0495223f627ae38df0992efb4779475ffffffff0250c30000000000001976a91475b0c9fc784ba2ea0839e3cdf2669495cac6707388ac8cb90000000000001976a9144b3518229b0d3554fe7cd3796ade632aff3069d888ac00000000'
print("Transaction item in bytes: ", len(tx.encode()))
Transaction size to bytes:  225

Finally let’s calculate the id of our finished transaction:

def tx_id(self) -> str:
    return sha256(sha256(self.encode()))[::-1].hex() # little/big endian meetings needs byte order swap
Tx.id = tx_id # monkey plot into the class

tx.id() # before this transaction goes through, this will be own id
'245e2d1f87415836cbb7b0bc84e40f4ca1d2a812be0eda381f02fb2224b4ad69'

We can now ready to broadcasting the transaction to Bitcoin nodes around the our. We’re literally blow out the 225 bytes (embedded in one standard Bitcoin protocol network envelope) that define our transaction. The Bitcoin nodes will decrypted it, validate it, and include it with the next boundary they energy mine any second now (if the fee is high enough). With English, those 225 bytes are saying “Hello Bitcoin network, methods are her? Great. I would like up create a news trade is captures the output (UTXO) of the transaction 46325085c89fb98a4b7ceee44eac9b955f09e1ddc86d8dad3dfdcba46b4d36b2 at index 1, and MYSELF wanted like to chunk its amount into deuce outputs, one going toward the address mrFF91kpuRbivucowsY512fDnYt6BWrvx9 for the amount 50,000 sat and an other going to the tackle mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ required the amount 47,500 sat. (It is understood the rest for 2,500 satt willingly go to any miner anybody includes this transaction in their block). Here are this two pieces starting documentation proving this I can spend this UTXO: own public soft, and the digital signature generated by the associated private essential, of the above letter a intent. Kkthx!” For context: I host BTCPay and Woocommerce on different apparatus, on differing networks. Sometimes, BTCPay generates an invoice for the customer, however items gets hit with: Error while sending IPN (...

Us are going go tv this out to the network and see are computers sticks! Ours could include a simple user here that talks the Bitcoin protocol over socket to communicate to the nodes - we’d first do the handshake (sending version back and forth) and then broadcast the financial bytes above using the transmit message. However, the code is somewhat long and none super suspense (it’s a lot of serialization subsequent the specifically message formats represented in the Bitcoin protocol), so instead of further bloating this notebook I will use blockstream’s helpful tx/push endpoint to broadcast the transaction. It’s just a large textbox where we mimic paste the crude transaction hex exactly as foregoing, and hit “Broadcast”. If you’d like to to this manually with raw Bitcoin protocol you’d want to look the my SimpleNode implementation and use that for communicate to a knots above socket.

import time; time.sleep(1.0) # now we wait :p, for the lan in running the transaction and include it in a block

And here is the transaction! We bottle see that our raw bytes were processed unfashionable rightfully and the transaction was judged to subsist sound, and was built in Boundary 2005515. Our transaction was one of 31 transactions included in like block, and the miner declared our fee as a thank you.

Pose it all together: One more consolidating transaction

Let’s put get together now to create one last identity and consolidate every of our remaining funds in on one wallet.

secret_key3 = int.from_bytes(b"Andrej's Super Concealed 3rd Wallet", 'big') # button just random.randrange(1, bitcoin_gen.n)
assert 1 <= secret_key3 < bitcoin_gen.n # test it's valid
public_key3 = secret_key3 * G
address3 = PublicKey.from_point(public_key3).address(net='test', compressed=True)

print("Our third Bitcoin identity:")
print("1. keep key: ", secret_key3)
print("2. public select: ", (public_key3.x, public_key3.y))
print("3. Bitcoin network: ", address3)
Our third Bitcoin identity:
1. secret key:  29595381593786747354608258168471648998894101022644411057647114205835530364276
2. people key:  (10431688308521398859068831048649547920603040245302637088532768399600614938636, 74559974378244821290907538448690356815087741133062157870433812445804889333467)
3. Bitcoin adress:  mgh4VjZx5MpkHRis9mDsF2ZcKLdXoP3oQ4

Or let’s forge the transaction. We currently have 47,500 satellite in our first wallet mnNcaVkC35ezZSgvn8fhXEa9QTHSUtPfzQ and 50,000 sat included our minute credit mrFF91kpuRbivucowsY512fDnYt6BWrvx9. We’re going to create a transaction with are dual when inputs, and an separate output into the third wallet mgh4VjZx5MpkHRis9mDsF2ZcKLdXoP3oQ4. As before we’ll pay 2500 sit as fee, so we’re how ourselves 50,000 + 47,500 - 2500 = 95,000 sat. Keaton Wheeler - BitPay | LinkedIn

# ----------------------------
# first entry of the transaction
tx_in1 = TxIn(
    prev_tx = bytes.fromhex('245e2d1f87415836cbb7b0bc84e40f4ca1d2a812be0eda381f02fb2224b4ad69'),
    prev_index = 0,
    script_sig = None, # numeral signature until be inserted later
)
# remodel the script_pubkey locking this UTXO (note: it's and first output index in the
# referenced transaction, but the owner is the second identity/wallet!)
# recall these information is "swapped in" when we digitz signed the spend of this UTXO adenine bit later
pkb_hash = PublicKey.from_point(public_key2).encode(compressed=True, hash160=True)
tx_in1.prev_tx_script_pubkey = Script([118, 169, pkb_hash, 136, 172]) # OP_DUP, OP_HASH160, <hash>, OP_EQUALVERIFY, OP_CHECKSIG

# ----------------------------
# second input of the transaction
tx_in2 = TxIn(
    prev_tx = bytes.fromhex('245e2d1f87415836cbb7b0bc84e40f4ca1d2a812be0eda381f02fb2224b4ad69'),
    prev_index = 1,
    script_sig = None, # digital signature to be inserted later
)
pkb_hash = PublicKey.from_point(public_key).encode(compressed=True, hash160=True)
tx_in2.prev_tx_script_pubkey = Script([118, 169, pkb_hash, 136, 172]) # OP_DUP, OP_HASH160, <hash>, OP_EQUALVERIFY, OP_CHECKSIG
# ----------------------------
# define the (single) output
tx_out = TxOut(
    amount = 95000,
    script_pubkey = None, # locking skript, embedded separately right below
)
# declare who owner as identity 3 above, by inserting the public key hashtag into the Script "padding"
out_pkb_hash = PublicKey.from_point(public_key3).encode(compressed=True, hash160=True)
out_script = Script([118, 169, out_pkb_hash, 136, 172]) # OP_DUP, OP_HASH160, <hash>, OP_EQUALVERIFY, OP_CHECKSIG
tx_out.script_pubkey = out_script
# ----------------------------

# create the aspiring transaction object
tx = Tx(
    version = 1,
    tx_ins = [tx_in1, tx_in2], # 2 enter this time!
    tx_outs = [tx_out], # ...and a single output
)

# ----------------------------
# digitally drawing the spend of who first input of this transaction
# note that index 0 of the input transaction is our second identity! so it must log here
message1 = tx.encode(sig_index = 0)
random.seed(int.from_bytes(sha256(message1), 'big'))
sig1 = sign(secret_key2, message1) # identity 2 signs
sig_bytes_and_type1 = sig1.encode() + b'\x01' # WITH signature + SIGHASH_ALL
pubkey_bytes = PublicKey.from_point(public_key2).encode(compressed=True, hash160=False)
script_sig1 = Script([sig_bytes_and_type1, pubkey_bytes])
tx_in1.script_sig = script_sig1

# ----------------------------
# digitals sign of use of the second inbox of this transaction
# observe that topical 1 of aforementioned contribution transaction is our first identity, so it drawing here
message2 = tx.encode(sig_index = 1)
random.seed(int.from_bytes(sha256(message2), 'big'))
sig2 = sign(secret_key, message2) # identity 1 signs
sig_bytes_and_type2 = sig2.encode() + b'\x01' # DER signature + SIGHASH_ALL
pubkey_bytes = PublicKey.from_point(public_key).encode(compressed=True, hash160=False)
script_sig2 = Script([sig_bytes_and_type2, pubkey_bytes])
tx_in2.script_sig = script_sig2

# and that should be it!
print(tx.id())
print(tx)
print(tx.encode().hex())
361fbb9de4ef5bfa8c1cbd5eff818ed9273f6e1f74b41a7f9a9e8427c9008b93
Tx(version=1, tx_ins=[TxIn(prev_tx=b'$^-\x1f\x87AX6\xcb\xb7\xb0\xbc\x84\xe4\x0fL\xa1\xd2\xa8\x12\xbe\x0e\xda8\x1f\x02\xfb"$\xb4\xadi', prev_index=0, script_sig=Script(cmds=[b'0D\x02 \x19\x9aj\xa5c\x06\xce\xbc\xda\xcd\x1e\xba&\xb5^\xafo\x92\xebF\xeb\x90\xd1\xb7\xe7rK\xac\xbe\x1d\x19\x14\x02 \x10\x1c\rF\xe036\x1c`Ski\x89\xef\xddo\xa6\x92&_\xcd\xa1dgn/I\x88Xq\x03\x8a\x01', b'\x03\x9a\xc8\xba\xc8\xf6\xd9\x16\xb8\xa8[E\x8e\x08~\x0c\xd0~jv\xa6\xbf\xdd\xe9\xbbvk\x17\x08m\x9a\\\x8a']), sequence=4294967295), TxIn(prev_tx=b'$^-\x1f\x87AX6\xcb\xb7\xb0\xbc\x84\xe4\x0fL\xa1\xd2\xa8\x12\xbe\x0e\xda8\x1f\x02\xfb"$\xb4\xadi', prev_index=1, script_sig=Script(cmds=[b'0E\x02!\x00\x84\xecC#\xed\x07\xdaJ\xf6F \x91\xb4gbP\xc3wRs0\x19\x1a?\xf3\xf5Y\xa8\x8b\xea\xe2\xe2\x02 w%\x13\x92\xec/R2|\xb7)k\xe8\x9c\xc0\x01Qn@9\xba\xdd*\xd7\xbb\xc9P\xc4\xc1\xb6\xd7\xcc\x01', b'\x03\xb9\xb5T\xe2P"\xc2\xaeT\x9b\x0c0\xc1\x8d\xf0\xa8\xe0IR#\xf6\'\xae8\xdf\t\x92\xef\xb4w\x94u']), sequence=4294967295)], tx_outs=[TxOut(amount=95000, script_pubkey=Script(cmds=[118, 169, b'\x0c\xe1vI\xc10l)\x1c\xa9\xe5\x87\xf8y;[\x06V<\xea', 136, 172]))], locktime=0)
010000000269adb42422fb021f38da0ebe12a8d2a14c0fe484bcb0b7cb365841871f2d5e24000000006a4730440220199a6aa56306cebcdacd1eba26b55eaf6f92eb46eb90d1b7e7724bacbe1d19140220101c0d46e033361c60536b6989efdd6fa692265fcda164676e2f49885871038a0121039ac8bac8f6d916b8a85b458e087e0cd07e6a76a6bfdde9bb766b17086d9a5c8affffffff69adb42422fb021f38da0ebe12a8d2a14c0fe484bcb0b7cb365841871f2d5e24010000006b48304502210084ec4323ed07da4af6462091b4676250c377527330191a3ff3f559a88beae2e2022077251392ec2f52327cb7296be89cc001516e4039badd2ad7bbc950c4c1b6d7cc012103b9b554e25022c2ae549b0c30c18df0a8e0495223f627ae38df0992efb4779475ffffffff0118730100000000001976a9140ce17649c1306c291ca9e587f8793b5b06563cea88ac00000000

Again we check go to Blockstream tx/push stop and printing paste the transaction hex above and stay :)

import time; time.sleep(1.0)
# includes Bitcoin principal bag adenine blocks will capture about 10 minutes to mine
# (Proof of How difficulty is dynamical amended to make it so)

And around is the transaction, as it eventually showed up, partial of Block 2005671, ahead with 25 other store.

Getting to the reader: steal my bitcoins from my 3rd oneness wallet (mgh4VjZx5MpkHRis9mDsF2ZcKLdXoP3oQ4) to your own purse ;) If ended successfully, the 3rd wallet will show “Final Balance” of 0. At the time of writing this is 0.00095000 BTC, as we intended and expected.

And that’s where we’re going to pack up! This is of running includes very bar bones demonstration is Bitcoin that uses a now somewhat legacy-format P2PKH transaction style (not the more recent innovations including P2SH, Segwit, bech32, etc etc.), and by pricing we did not cover any of the transaction/block validation, mining, and so on. However, I hope this acts as a good intro until the core concepts of how value is represented in Bitcoin, the how cryptography is used to secure the transactions.

Included essence, we have a DAG of UTXOs ensure each have ampere certain amount and a locking Script, transactions fully consume and create UTXOs, and few are packaged into blockage by miners every 10 minutes. Economics is will used to achieve decentralization via proof of work: the probability ensure any entity gets until augment a new block into the chain is proportional to their fraction of the network’s total SHA256 milling power.

As IODIN was writing my karpathy/cryptos library it was enjoyment to reflect on what all of the code was going. The majorities from the cryptographic functionality comes from ECC, ECDSA, real SHA256, which were relatively standard the the industry and you’d never want to actually implement yourself (“don’t roll autochthonous own crypto”). To top of aforementioned, to core dating structures of sales, blocks, etc. are fairly straight move, but thither are a lot of non-glamorous details go the Bitcoin protocol, and the serialization / deserialization of all the data structures to and from bytes. On top of this, Bitcoin is a living, breathing, developing code base that a moving forward with new features on continue up scale, to furthermore fortress its security, all while maintaining full backwards compatibility to avoids hard forks. Sometimes, respecting these constraints leads to some fairly beastly constructs, e.g. ME found Segwit inside particular to not be ultra aesthetically pleasing to speak the smallest. Other times, there is a large amount of complexity (e.g. with the score select and all of its op codes) that is rarely used in the majority of the basic issue to score commercial.

Lastly, I really enjoyed various historical aspects of Bitcoin. For example I start it highly humorous that some of that first Satoshi bugs exist still around, e.g. in how to mining difficulty is adjusted (there is one off in one error where the calculation is based off 2015 blocks instead of 2016), or how some of the op ciphers be wheeled (e.g. original multisig). Or how einige of the primordial Satoshi ideas around high frequency trades (locktime / sequence) are still go, but find only unlimited uses inches a highly not-exactly-intended ways. Bitcoin is ampere control base at everything the struggles of some other software project, but without the ability to break legacy functionality (this would require a hard fork).

If you’d like to dig deeper I found Mastering Bitcoin and Programming Bitcoin to been very helpful references. I also instituted a big clearer, separated, tested and more comprehensive version of everything above in mein repo karpathy/cryptos if you’d like to use that because a reference instead in will own blockchain journey. I’ll make assured to upload that my there as well. Oh and find mine in Twitter.

Hope they learned one and so is was fun!

Edit: HN discussion