Understanding Btcd: Part 3 - How to Sign Bitcoin Transaction
In this tutorial, we will dive in btcd source code to see how a transaction in bitcoin got signed.
1. Struct of a Transaction
There is no variable to hold an account value in Bitcoin. Your balance is just a sum of all unspent transactions that sent to you. You have money because someone else has sent it to you. Spending money is simply passing that value to another one.
When creating a new transaction, you need to define the inputs (which unspent transaction you have) and outputs (where you want to send the money to).
In btcd, the structure of a Bitcoin transaction is defined in wire package.
// TxIn defines a bitcoin transaction input.
type TxIn struct {
PreviousOutPoint OutPoint
SignatureScript []byte
Witness TxWitness
Sequence uint32
}
// TxOut defines a bitcoin transaction output.
type TxOut struct {
Value int64
PkScript []byte
}
// A transaction
type MsgTx struct {
Version int32
TxIn []*TxIn
TxOut []*TxOut
LockTime uint32
}
2. How to sign a Transaction
Signing a transaction must guarantee 2 things:
- The signature would provide that the transaction creator owning these coin, that he has the right to spend these UTXOs - Unspent Transaction Outputs.
- The signature would guarantee that it can not be modified by any other actor on the network. If anything is modified, like changing receiver address or the amount, the transaction shall be invalid.
Pay to Public Key Hash (P2PKH) Script
A clever design of bitcoin is Pay-To-PubkeyHash Script Language. In which, locking and unlock UTXOs will be performed by evaluating a very simple script. The code in this Script Language is just a sequence of data and operators.
For example:
4 5 OP_ADD 9 OP_EQUAL
4, 5 and 9 are data. OP_ADD and OP_EQUAL are operators. This script will be evaluated by put into a stack from left to right: data will be put into a stack, the operator will be applied to the top stack elements. This is like Postfix Notation, which uses a FILO (First Input Last Output) memory storage to calculate a math.
In Bitcoin, lock and unlock UTXOs will be performed by scriptPubKey and scriptSig, as example below:
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig: <sig> <pubKey>
<signature> <pubKey>, is stored in input’s ScriptSig field. The second piece, OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG is stored in output’s scriptPubKey. ScriptPubKey define unlocking logic and scriptSig provide data to unlock.
This script is called Pay to Public Key Hash (P2PKH), the two parts scriptSig and scriptPubKey then combined together to check the if the transaction is valid.
Let’s run the script above:
-
Stack: empty
Script: <signature> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
-
Stack: <signature>
Script: <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
-
Stack: <signature> <pubKey>
Script: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
-
Stack: <signature> <pubKey> <pubKey>
Script: OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
-
Stack: <signature> <pubKey> <pubKeyHash>
Script: <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
-
Stack: <signature> <pubKey> <pubKeyHash> <pubKeyHash>
Script: OP_EQUALVERIFY OP_CHECKSIG
-
Stack: <signature> <pubKey>
Script: OP_CHECKSIG
-
Stack: true or false. Script: empty.
P2PKH is not the only type of Script this language can process. This Script can be flexible, enable to Bitcoin to have more types of transaction. Such as Multinature Transactions, Anyone-Can-Spend Outputs, Freezing funds until a time in the future,…
In this tutorial, we only discuss P2PSK and how to sign it.
The signing data of a transaction is the <sig> in this scriptSig. Because a transaction has many inputs TxIns, it will have to sign these txIns individually.
How does btcd sign a transaction?
If you want to look into btcd source code to find the signing function in Bitcoin, you could first look into sign.SignTxOutput() in txtscript package. This function will look like this:
func SignTxOutput(chainParams *chaincfg.Params, tx *wire.MsgTx, idx int, pkScript []byte, hashType SigHashType, kdb KeyDB, sdb ScriptDB,
previousScript []byte) ([]byte, error)
Leave all the meta data we don’t need to care, shorter footprint of this function is:
func SignTxOutput(tx *wire.MsgTx, idx int)
This function will sign the txIn at index idx from transaction tx *wire.MsgTx.
Follow down the call reference of this function, we will end up at this:
// RawTxInSignature returns the serialized ECDSA signature for the input idx of
// the given transaction, with hashType appended to it.
func RawTxInSignature(tx *wire.MsgTx, idx int, subScript []byte,
hashType SigHashType, key *btcec.PrivateKey) ([]byte, error) {
hash, err := CalcSignatureHash(subScript, hashType, tx, idx)
if err != nil {
return nil, err
}
signature, err := key.Sign(hash)
if err != nil {
return nil, fmt.Errorf("cannot sign tx input: %s", err)
}
return append(signature.Serialize(), byte(hashType)), nil
}
What does this function do? First, it gets the Hash of the transaction, then signs it with the private key.
Then the signature will be put back into the transaction txIn like this:
sig, err := txscript.RawTxInSignature(...)
tx.TxIn[0].SignatureScript = pushDataScript(sig,
redeemScript)
The signing process will repeat for all TxIn inside the transaction. So the missing point here is how the function CalcSignatureHash works. From the Bitcoin Developer Guide we got this:
As illustrated in the figure above, the data Bob signs include the txid and output index of the previous transaction, the previous output’s pubkey script, the pubkey script Bob creates which will let the next recipient spend this transaction’s output, and the amount of satoshis to spend to the next recipient. In essence, the entire transaction is signed except for any signature scripts, which hold the full public keys and secp256k1 signatures.
Basically “the entire transaction is signed except for any signature scripts, which hold the full public keys and secp256k1 signatures”.
In btcd code, it works like this:
- First create a ShallowCopy of the transaction, contains only the important part of the transaction need to sign.
- Hash the binary data of that copy transaction. This is the data need to sign.
func shallowCopyTx(tx *wire.MsgTx) wire.MsgTx {
// As an additional memory optimization, use contiguous backing arrays
// for the copied inputs and outputs and point the final slice of
// pointers into the contiguous arrays. This avoids a lot of small
// allocations.
txCopy := wire.MsgTx{
Version: tx.Version,
TxIn: make([]*wire.TxIn, len(tx.TxIn)),
TxOut: make([]*wire.TxOut, len(tx.TxOut)),
LockTime: tx.LockTime,
}
txIns := make([]wire.TxIn, len(tx.TxIn))
for i, oldTxIn := range tx.TxIn {
txIns[i] = *oldTxIn
txCopy.TxIn[i] = &txIns[i]
}
txOuts := make([]wire.TxOut, len(tx.TxOut))
for i, oldTxOut := range tx.TxOut {
txOuts[i] = *oldTxOut
txCopy.TxOut[i] = &txOuts[i]
}
return txCopy
}
txCopy := shallowCopyTx(tx)
for i := range txCopy.TxIn {
if i == idx {
// UnparseScript cannot fail here because removeOpcode
// above only returns a valid script.
sigScript, _ := unparseScript(script)
txCopy.TxIn[idx].SignatureScript = sigScript
} else {
txCopy.TxIn[i].SignatureScript = nil
}
}
txCopy.SerializeNoWitness(wbuf)
binary.Write(wbuf, binary.LittleEndian, hashType)
return chainhash.DoubleHashB(wbuf.Bytes())
You can follow this function in the txtscript package, script.CalcSignatureHash function.
In the next tutorial, we will create and sign a real bitcoin transaction then broadcast it to the TestNet. Stay tune ☺️
###3. Refercences
https://en.wikipedia.org/wiki/Reverse_Polish_notation
https://en.bitcoin.it/wiki/Script
https://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx