Signing transactions
Advanced
Bitcoin
Tutorial
Overview
Before a transaction can be sent to the Bitcoin network, each input must be
signed. Canisters can sign by using the sign_with_ecdsa
API.
Signing transactions
The following snippet shows a simplified example of how to sign a Bitcoin transaction for the special case where all the inputs are referencing outpoints that are owned by own_address
and own_address
is a P2PKH
address.
- Motoko
- Rust
public func sign_transaction(
own_public_key : [Nat8],
own_address : BitcoinAddress,
transaction : Transaction,
key_name : Text,
derivation_path : [Blob],
signer : SignFun,
) : async [Nat8] {
// Obtain the scriptPubKey of the source address which is also the
// scriptPubKey of the Tx output being spent.
switch (Address.scriptPubKey(#p2pkh own_address)) {
case (#ok scriptPubKey) {
let scriptSigs = Array.init<Script>(transaction.txInputs.size(), []);
// Obtain scriptSigs for each Tx input.
for (i in Iter.range(0, transaction.txInputs.size() - 1)) {
let sighash = transaction.createSignatureHash(
scriptPubKey, Nat32.fromIntWrap(i), SIGHASH_ALL);
let signature_sec = await signer(key_name, derivation_path, Blob.fromArray(sighash));
let signature_der = Blob.toArray(Der.encodeSignature(signature_sec));
// Append the sighash type.
let encodedSignatureWithSighashType = Array.tabulate<Nat8>(
signature_der.size() + 1, func (n) {
if (n < signature_der.size()) {
signature_der[n]
} else {
Nat8.fromNat(Nat32.toNat(SIGHASH_ALL))
};
});
// Create Script Sig which looks like:
// ScriptSig = <Signature> <Public Key>.
let script = [
#data encodedSignatureWithSighashType,
#data own_public_key
];
scriptSigs[i] := script;
};
// Assign ScriptSigs to their associated TxInputs.
for (i in Iter.range(0, scriptSigs.size() - 1)) {
transaction.txInputs[i].script := scriptSigs[i];
};
};
// Verify that our own address is P2PKH.
case (#err msg)
Debug.trap("This example supports signing p2pkh addresses only.");
};
transaction.toBytes()
};
View in the full example.
async fn sign_transaction<SignFun, Fut>(
own_public_key: &[u8],
own_address: &Address,
mut transaction: Transaction,
key_name: String,
derivation_path: Vec<Vec<u8>>,
signer: SignFun,
) -> Transaction
where
SignFun: Fn(String, Vec<Vec<u8>>, Vec<u8>) -> Fut,
Fut: std::future::Future<Output = Vec<u8>>,
{
// Verify that our own address is P2PKH.
assert_eq!(
own_address.address_type(),
Some(AddressType::P2pkh),
"This example supports signing p2pkh addresses only."
);
let txclone = transaction.clone();
for (index, input) in transaction.input.iter_mut().enumerate() {
let sighash =
txclone.signature_hash(index, &own_address.script_pubkey(), SIG_HASH_TYPE.to_u32());
let signature = signer(key_name.clone(), derivation_path.clone(), sighash.to_vec()).await;
// Convert signature to DER.
let der_signature = sec1_to_der(signature);
let mut sig_with_hashtype = der_signature;
sig_with_hashtype.push(SIG_HASH_TYPE.to_u32() as u8);
input.script_sig = Builder::new()
.push_slice(sig_with_hashtype.as_slice())
.push_slice(own_public_key)
.into_script();
input.witness.clear();
}
transaction
}
View in the full example.
Learn more about using the Threshold signing API.
Next steps
Now that your transaction is signed, it can be submitted to the Bitcoin network.