Skip to content

How to sign transaction

Jiang Jinyang edited this page Nov 11, 2019 · 6 revisions

Signing

Currently, CKB has two lock scripts in the genesis block, The address code hash index 0x00 is for pay to pubkey hash and 0x01 is for pay to multisig, see RFC to learn details.

This document demonstrates how to sign P2PH transactions.

Before continuing, make sure you already understand the transaction structure. (The details in this RFC maybe outdated, but still worth to read, you can check the newest transaction structure from the schema)

P2PH

We need the following arguments to sign a tx:

* pk, secp256k1 private key
* witnesses, contains signatures of the tx.

Before sign a tx, we need to know some basic concepts:

Input group - For a tx, instead verify each input, CKB group the inputs by compute the script_hash of the lock field of input, then run the unlock script by the group.

For example, here we have some inputs:

inputs = [g1, g2, g1]

We have 3 inputs, the gN notation represents the group, the inputs[0] and inputs[2] are in the same group g1, the inputs[1] is in g2 which is another group.

CKB run lock script on g1 and g2 separately. You may notice, the same script_hash requirement also implies the lock field of inputs is also the same, so we only need to provide one signature for each group.

The default locks P2SH and multisig assume for each input group there exists a witness to unlock the input(which means the witness contains the signature), the default locks will try to read signature from the index of the first input. For the g1 the lock script will try to read signature from witnesses[0], because the index of the first input of g1 is 0, and g2 will try to read the witnesses[1].

For example:

For inputs [g1, g2, g1], we need 2 witnesses [witness1, witness2]. For inputs [g1, g2, g1, g3], we need 4 witnesses includes an empty bytes to align witness3 for g3 [witness1, witness2, (empty bytes) ,witness3]. For inputs [g1, g2, g2, g1], we need witnesses [witness1, witness2].

This helper function group inputs and return the indexes by the input group.

def compute_input_groups(inputs):
    groups = defaultdict(list)
    for i, input in enumerate(inputs):
        script_hash = blake256(serialize(input.lock))
        groups[script_hash].append(i)

    return groups.values()

Witness - Tx's witnesses field is originally designed to put signatures. P2PH lock script will try to read signatures from witnesses.

WitnessArgs - In CKB the concept of witnesses is extended to allow a user to put any one-time data, these data are only required for the current tx verification.

The flexible of CKB contract also brings a problem, tx's lock and type contract maybe require different data on the same witness, neither scripts can get satisfied at the same time, the tx may be locked forever.

To dismiss the potential conflict, CKB provides a structure WitnessArgs, lock script and type script read data from different fields of WitnessArgs, the default P2PH and the multisig scripts require user use WitnessArgs on the first witness of each group.

# Pseudo code to sign a tx of P2PH
def sign_tx(pk, tx):
    input_groups = compute_input_groups(tx.inputs)
    # signing by groups.
    for indexes in input_groups:
        group_index = indexes[0]
        # make a reserve for the lock field of first witness in group
        dummy_lock = [0] * 65
        witness = tx.witnesses[group_index]
        witness_args = witness.deserialize()
        witness_args.lock = dummy_lock
        hasher = new_blake2b()
        # hash the tx hash
        hasher.update(tx.hash())
        # hash the first witness
        witness_len_bytes = len(serialize(witness_args)).to_le()
        assert(len(witness_len_bytes), 8)
        # hash the length of witness
        hasher.update(witness_len_bytes)
        hasher.update(serialize(witness_args))
        # hash the other witnesses in the group
        for i in indexes[1:]:
            witness = tx.witnesses[i]
            witness_len_bytes = len(witness).to_le()
            assert(len(witness_len_bytes), 8)
            hasher.update(witness_len_bytes)
            hasher.update(witness)
        # hash witnesses which do not in any input group
        for witness in tx.witnesses[len(tx.inputs):]
            witness_len_bytes = len(witness).to_le()
            assert(len(witness_len_bytes), 8)
            hasher.update(witness_len_bytes)
            hasher.update(witness)
        sig_hash = hasher.finalize()
        # sign tx
        signature = pk.sign(sig_hash)
        # put the signature back to the first witness
        witness_args.lock = signature
        tx.witnesses[group_index] = serialize(witness_args)

Multisig

WIP

Clone this wiki locally