SI 486I Spring 2022 / Labs


This is the archived website of SI 486I from the Spring 2022 semester. Feel free to browse around; you may also find more recent offerings at my teaching page.

Lab 7: Transactions

In today’s lab, at long last, we will add transactions and actual GoatCoin to the network. Huzzah!

Mining helper program

I created a Rust program that you can use to help with nonce generation for goatcoin mining. The purpose of sharing this is not to diminish what you’ve already worked on, but to let us all mine at (close to) the same speed, so the network is more fair.

On your VM (or any other linux machine on the USNA network), run:

cargo install --git http://gitlab.usna.edu/roche/goatminer --root ~
source ~/.profile

This should automatically download, compile, and install a program called goatminer to your ~/bin directory. You should now be able to run this program goatminer from the command-line.

Here’s how the program works:

  • The number of leading zeros needed for the hash (e.g., 24) is given as a command-line argument.
  • The JSON encoding of a block (with or without the nonce field filled in) is sent to the program through standard in.
  • A JSON encoding of a completed block, with a nonce field set such that the resulting hash has the required number of leading zeros, is returned via standard out.

Here’s an example of using this directly on the command line. Here I am making a JSON string and saving it to a file directly, then redirecting that file into the goatminer program, redirecting the output to another file. Finally, I use openssl to confirm the hash at the end.

roche@cat:~$ cat >original.json
{ "version": 1,
  "prev_hash": "000000a5919b38ddc98f4fdadd10ab507141f95908c908cd6994ea58e4e034b3",
  "payload": {"chat": "heloooo"}
}

roche@cat:~$ goatminer 20 <original.json >mined.json

roche@cat:~$ cat mined.json; echo
{"payload":{"chat":"heloooo"},"prev_hash":"000000a5919b38ddc98f4fdadd10ab507141f95908c908cd6994ea58e4e034b3","version":1,"nonce":192058}

roche@cat:~$ openssl dgst -sha3-256 <mined.json
(stdin)= 000003a4e7d916e352ee019ee4b68862b3e1013ef99a39e1636cbf732301d6e3

Notice that the resulting hash at the end starts with 5 hex zeros, because we specified 20 binary zeros on the command line.

To use this in your Python program, you can use the subprocess library. I’m not going to show you exactly how to use this library to call goatminer, but here is how you could use the tr program to replace all a’s with b’s in a string in Python:

>>> import subprocess
>>> s = "I want to eat pizza."
>>> result = subprocess.run(['tr', 'a', 'b'], input=s, text=True, capture_output=True, check=True)
>>> result.stdout
'I wbnt to ebt pizzb.'

Simple transactions in GoatCoin

For now, GoatCoin transactions will be really simple: Any transaction represents exactly 1 GoatCoin, with 1 input and 1 output. There are no miner fees, and no way to split or combine coins.

This will involve a new piece of the payload field for any (version 1) block.

  • A single transaction is represented by a JSON dictionary, containing three fields, all of which are represented as strings:

    • input: a SHA3-256 hash of the previous transaction that is being spent
    • output: a hex-encoded ED25519 public key
    • message: Any string
  • This transaction must be encoded as JSON (using json.dumps) into a JSON string, which we will call a TJ for “transaction JSON”.

  • That TJ string must be signed with the private key of the input source, to produce an ED25519 signature.

  • (Separately, in some later transaction, a hash of the TJ string is what will go as the input when we want to spend this coin later on.)

  • The payload of any block will now contain a field called txns, which is a list of dictionaries. Each dictionary in the list will contain two fields: tj which contains the TJ string described above, and sig which contains a (hex-encoded) signature as described above.

All of the above rules apply for “normal” transactions. The first transaction in a block can be a special coinbase transaction which gets 1 GoatCoin block reward. This transaction will be the same as any other transaction except:

  • The input field must still be there as part of the TJ string, but it will be ignored. It should be some random hash, i.e., a 32-byte hex string that is randomly generated.

  • The signature sig should be the string coinbase (instead of a real hex-encoded ED25519

Whew! That seems complicated but it’s really not too bad. Here is a complete example block:

{
  "payload": {
    "txns": [
      {
        "sig": "coinbase",
        "tj": "{\"input\": \"af9ab22c6240ac91070a9593d61354417ceef08c8cddfed3b92140bb55093903\", \"output\": \"5e42953773000a66d1554135ff88cc57619631eeac7884c8300b135cf9f284b2\", \"message\": \"Pay me!\"}"
      }
    ]
  },
  "prev_hash": "0000005953ce5f58ceb8f8fbd13251bbe94aab191492881697e16582d142d2eb",
  "version": 1,
  "nonce": 33016957
}

This block has one transaction, which is a coinbase transaction that pays the block reward of 1 GoatCoin to the public key that starts with 5e4295...

The hash of the tj string in the example above starts with 675e449..., which is the transaction id that we can use to refer to this coin when we want to spend it later.

Here’s another block where this coin gets spent (and a new coin is mined again):

{
  "payload": {
    "txns": [
      {
        "sig": "coinbase",
        "tj": "{\"input\": \"ae9dcdb4cf8051fcdb019019e3aab0f7a7666d404f441ca6e1a12d68b79cef0e\", \"output\": \"5e42953773000a66d1554135ff88cc57619631eeac7884c8300b135cf9f284b2\", \"message\": \"Mine mine\"}"
      },
      {
        "sig": "c18a0f2b7f849d6e600f0427ec10529de526ccde29a45261c2f7ef62c99631b3e20adbd799a6feb7a4040034a309935382ad554be2bc85ccceed2191e6bb5b07",
        "tj": "{\"input\": \"675e449d7a2f51d6402f26b0485d3046ff258ed864f197a654890f6f879f8b4e\", \"output\": \"310fa0cf4c5ce45864fccf09c407c18da9716800a81875ca49d41b5d89dd38e2\", \"message\": \"full spend\"}"
      }
    ]
  },
  "prev_hash": "0000000a9625319c204469a24541b4b2e54d3cccf0abf48ec1486190eb42aa97",
  "version": 1,
  "nonce": 14164503
}

Check server

As with last week’s lab, the horse server can be used to check your work.

On port 5002, horse is running a working GoatCoin node with a few dozen blocks, including a few blocks with valid transactions. This server will also accept /push requests and validate new transactions, but its head node will never change. This way you can check that your code works without having to deal with a chain of thousands of blocks.

On port 5000, horse is running a check server that will check your server is working properly. To use it, on the command line enter

curl http://horse:5000/check/HOST/PORT

Your tasks

Here’s what you need to do:

  1. Modify your server.py so that it looks at any transaction information in payloads of push requests and verifies the transactions before accepting the new block.

  2. Write a program send_tx.py, similar to send_chat.py, that mines a block and includes a coinbase transaction that pays out to your own key.

  3. Modify send_tx.py further so that it can also include additional transactions besides the coinbase transaction. Use that to make a transfer from a previously-mined coin.

There are a lot of checks you need to make to validate a transaction! We looked at these things in class:

  • Properly formed transaction: contains tj and sig fields, and the string in tj is a valid JSON-encoded block with input, output, and message.

  • Both input and output should be 32-byte (64-character) hex strings, corresponding respectively to a hash digest and a ED25519 verification key.

    (Note, you can’t really check that it’s a “real” hash digest or “real” verification key without knowing the hash input or the private key. So you’re really just checking that it’s a 32-byte hex string.)

  • If the sig is "coinbase", then he have to make sure it’s a valid coinbase transaction:

    • Must be the first transaction in the block. (This ensures there is never more than one coinbase in a block — you can’t just print infinite money!)
  • Otherwise, the sig must be a 64-byte hex string, and:

    • The input must be the hash digest of some previous transaction’s tj string on the same blockchain. (This is called a transaction id.)

      Call this previous transaction the input txn.

    • The input txn must not be already spent. (Meaning, the same input should not appear as the input of any existing transaction on the same blockchain.)

      This makes sure the owner of a GoatCoin can’t spend it more than once.

    • The sig on this transaction must be a valid signature over the tj on this transaction and the public key specified by the output of the input txn.

      This ensures that the person spending this GoatCoin actually owns it!

To make this work, you probably want to write some loop that goes backwards in the blockchain looking for either (1) the same input, meaning a double-spend and this transaction is invalid; or (2) the matching hash of the transaction id, meaning you’ve found the input txn and can check the signature.

Work carefully and use the examples you have here and on the horse server. You can do it! Go make that coin!