Transaction signatures

Users interact with Bitcoin through pseudonyms, which are public keys or addresses (namely, hashes of public keys). Users can obtain as many pseudonyms as they want, by generating pairs of public/private keys. Pseudonyms are used in transactions to specify who receives bitcoins. Balzac allows users to generate keys and addresses through the sidebar of the web editor.

In Balzac, keys and addresses are typed: the type is pubkey for public keys, key for private keys, and address for addresses.

// Alice's private key
const kA = key:cUSH4x3Uq9uMgeZGdpTFvr5gVGYcAg4vrTNe9QvWsU8Dq3deym6Z
// Alice's public key
const kApub = pubkey:033806fe039c8d149c8e68f4665b4a479acab773b20bddf7df0df59ba4f0567341
// Alice's address
const addrA = address:my6NmTELHBMVUsAWb34iRoGYDQpcYJvVZV

// Bob's private key
const kB = key:cUwtWVskxp5T31DxrQukxSxQ1Hj7VB53FrE52THe32bF4GN5QvtL
// Bob's public key
const kBpub = kB.toPubkey

// Carl's private key
const kC = key:cVhDA3Yxkeacnci8WUokAfQT6Nv4tGpmy1GzSYtJdYqDDwZipPPB
// Carl's public key
const kCpub = kB.toPubkey

Within transactions, users certify their identity with the function sig, and verify other users’ identity with the predicate versig.

Verifying signatures

The predicate versig(kpub; x) takes two parameters: a public key kpub and the signature x of the redeeming transaction. The predicate is true if the signature x has been made with the private key corresponding to kpub. For instance, the following transaction transfers 1 BTC to a transaction signed by Alice:

// tx redeemable with Alice's private key
transaction A_funds {
    input = _
    output = 1 BTC: fun(x). versig(kApub; x)
}

One can use versig to check multiple signatures. For instance, in the following transaction the predicate versig(kApub, kBpub; x, y) is true if x is Alice’s signature and y is Bob’s.

// tx redeemable with two signatures: Alice's and Bob's
transaction T {
        input = _
        output = 1 BTC: fun(x, y). versig(kApub, kBpub; x, y)
}

In cases (like the one above) where versig checks multiple signatures, one cannot use addresses.

In general, versig (lk;ls) verifies the list ls of signatures against the list lk of keys. The order of elements in these lists matters. Indeed, versig tries to verify the last signature in ls with the last key in lk. If they match, it verifies the previous signature in the list against the previous key; otherwise it verifies the same signature with the previous key.

In this way, versig can model complex conditions, like a 2-of-3 multi signature scheme:

transaction T {
        input = _
        output = 1 BTC: fun(x, y). versig(kApub, kBpub, kCpub; x, y)
}

The predicate versig(kApub, kBpub, kCpub; x, y) is true if x and y can match two of the three keys. For instance, if sigC and sigB are Carl’s and Bob’s signatures, then versig(kApub, kBpub, kCpub; sigB, sigC) is true, while versig(kApub, kBpub, kCpub; sigC, sigB) is false.

Signing transactions

Assume we have a transaction A_funds as defined in the previous section. We can redeem A_funds with a transaction TA made as follows:

transaction TA {
        input = A_funds : sig(kA)                // Alice's signature of TA
        output = 1 BTC: fun(x). versig(kApub; x) // any condition
}

The value sig(kA) within the input field is the signature of Alice on TA. The signature applies to all the fields of the transaction but the witnesses. The actual signature is generated when compiling the transaction.

Alternatively, we can use sig(kA) of TA to generate the signature outside the transaction:

transaction T {
        input = A_funds : _                      // unspecified witness
        output = 1 BTC: fun(x). versig(kApub; x) // any condition
}

// Alice's signature of T
const sigA = sig(kA) of T

transaction TA {
        input = A_funds : sigA                   // Alice's signature of T
        output = 1 BTC: fun(x). versig(kApub; x)  // any condition
}

Note that the witness in TA is Alice’s signature of T: indeed, the two transactions have the same signature, since their input and output fields are the same.

The construct sig(k) of T also applies to parametric transactions. This is especially useful when the parameter is the witness, like in the following example:

// template for a parametric transaction
transaction T_template(s:signature) {
        input = A_funds : s
        output = 1 BTC: fun(x). versig(kApub; x) // any condition
}

// signs T_template, without providing an argument
const sigA = sig(kA) of T_template(_)

// instantiates T_template with the needed argument
const TA  =  T_template(sigA)

The witness in T_template is a parameter s, which must be instantiated with Alice’s signature. Alice first signs T_template, and then she instantiates the parameter of T_template with her signature. The obtained transaction TA can redeem A_funds.

When a transaction needs the signatures of many participants, each of them signs a template of the transaction, and sends the signature to a participant who collects them.

For instance, assume that T_ABC requires the signatures of Alice, Bob and Carl:

//needs three signatures to redeem  1 bitcoin
transaction T_ABC{
    input = _
    output = 1 BTC: fun(x, y, z). versig(kApub, kBpub, kCpub; x, y, z)
}

First, all participants agree on a parametric transaction to redeem T_ABC:

transaction T_template (sA:signature, sB:signature, sC:signature){
    input = T_ABC: sA sB sC
    output = 1 BTC: fun(x). versig(kApub; x)
}

Then, each participant signs T_template. For instance, Alice performs the following actions:

//Alice's signature
const sigA = sig(kA) of T_template(_,_,_)

//prints the signature
eval sigA

The compiler outputs a pair, containing the signature and the public key:

sigA
sig:30450...3cdb01

Now, all participants send their pair to (say) Alice, who uses them to instantiate T_template with the actual signatures:

//signature of T_template made by Alice plus Alice's public key
const sigA = sig:304502...b01
//signature of T_template made by Bob plus Bob's public key
const sigB = sig:956232...c12
//signature of T_template made by Carl plus Carl's public key
const sigC = sig:f3h5d6...cdb

eval T_template(sigA, sigB, sigC)

Finally, the instantiated T_template can be appended to the blockchain to redeem T_ABC.