Note: the toycoin series of posts is for learning / illustrative purposes only; no part of it should be considered secure or useful for real-world purposes.

Transactions are the raison d’être of the original blockchain. Described in Section of Nakamoto’s whitepaper, a transaction is simply a transfer of coin from one owner to the next. (And the whole point of the blockchain being the mechanism to check that such transactions are valid and the coins are not being double-spent, without relying on a central authority.)

Data Model

We could consider modeling a simple transaction as follows, with what seems like the minimally required fields:

Address = bytes

class Transaction(TypedDict):
    sender: Address
    receiver: Address
    amount: float
    signature: signature.Signature

It turns out that sender is not required explicitly, since each transaction is signed by the sender. Omitting it saves space, and maybe there’s something philosophically aligned with the anonymours nature of the blockchain (even though, technically, all transactions are public and forever traceable to original owners). So we have:

class Transaction(TypedDict):
    receiver: Address
    amount: float
    signature: signature.Signature

Transactions

Generating a transaction (sending coins) is a direct construction of the TypedDict specified above:

def send(receiver: bytes,
         sender: rsa.RSAPrivateKey,
         amount: float,
         previous_txn: Transaction
         ) -> Transaction:
    """Generate a new transaction."""
    h = hash_txn(previous_txn)
    return {'receiver': receiver,
            'amount': amount,
            'signature': signature.sign(sender, h + receiver)}

Note that the construction of a valid transaction requires a prior construction. Presumably, there is a special genesis transaction in blockchains, just as there is a genesis block. (For testing purposes, any signature can be used for the signature of the genesis transaction, since it doesn’t need to be verified (probably?)).

The constructions requires a hash_transaction function:

def hash_txn(txn: Transaction) -> Hash:
    """Hash Transaction."""
    return hash.hash(txn['receiver'] +
                     bytes(str(txn['amount']).encode('utf-8')) +
                     txn['signature'])

… which seems like a reasonable way to do it, but could be terribly insecure! (Also, it feels like htere should be a better way to hash floats than hashing the string represetation?)

Validation is essentially just checking the signature (see Part 1 of this series). valid operates on a list or transactions and returns None if the list is too short – since no validation can be performed without a prior transaction.

def valid(txns: List[Transaction]) -> Optional[bool]:
    """Verify signatures of a list of transactions."""
    if len(txns) <= 1:
        return None

    pairs = zip(txns, txns[1:])
    return all(valid_pair(prev, this) for prev, this in pairs)


def valid_pair(previous: Transaction, this: Transaction) -> bool:
    """Verify signature of one transaction."""
    return signature.verify(this['signature'],
                            signature.load_pub_key_bytes(previous['receiver']),
                            hash_txn(previous) + this['receiver'])


Example

Let’s generate some RSA keys and a genesis transaction…

In [102]: a_priv, b_priv = signature.gen_priv_key(), signature.gen_priv_key()

In [103]: c_priv, d_priv = signature.gen_priv_key(), signature.gen_priv_key()

In [104]: txn0 = {'receiver': signature.get_pub_key_bytes(a_priv),
     ...:        'amount': 100.0,
     ...:        'signature': b'genesis_signature'
     ...:        }

Now we call the send interface:

In [105]: txn1 = transaction.send(signature.get_pub_key_bytes(b_priv),
     ...:                         a_priv,
     ...:                         99.0,
     ...:                         txn0)
     ...: txn2 = transaction.send(signature.get_pub_key_bytes(c_priv),
     ...:                         b_priv,
     ...:                         99.0,
     ...:                         txn1)

And finally, testing valid on the chain of transactions:

In [107]: transaction.valid([txn0, txn1, txn2])
Out[107]: True

In [108]: transaction.valid([txn0, txn2, txn1])
Out[108]: False

Tests

Tests codify an example like the above, with a few more edge cases.

Wrapping Up

As usual, even with very simple modules, there is quite a bit of detail that leads to questions and design decisions.

It’s still unclear to me how transactions are handled by the network. e.g.:

  • do blockahin users send transactions to full nodes for construction and broadcasting, since presumably there will be a lot more users of the blockchain compared to maintainers?
  • what does it mean for a payee to verify signatures, if they’re just a user and not a full node?
  • what if two valid transactions are received in different order by different full codes?

But presumably those are all questions for the network protocol and orthogonal to this post. ALso, we’ve kept transcations so simple that they can easily be modified as necessary.

Code on Github

References