Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Signing Transactions

Before a transaction can be sent to the Dogecoin network, each input must be signed. Canisters can sign transactions with threshold ECDSA through the sign_with_ecdsa system API endpoint.

Threshold ECDSA

The following snippet shows a simplified example of how to sign a Dogecoin transaction where all UTXOs are owned by own_address and own_address is a P2PKH address.

pub async fn sign_transaction<SignFun, Fut>(
    ctx: &DogecoinContext,
    own_public_key: &PublicKey,
    own_address: &Address,
    mut transaction: Transaction,
    derivation_path: Vec<Vec<u8>>,
    signer: SignFun,
) -> Transaction
where
    SignFun: Fn(String, Vec<Vec<u8>>, Vec<u8>) -> Fut,
    Fut: std::future::Future<Output = SecpSignature>,
{
    assert_eq!(
        own_address.address_type(),
        Some(AddressType::P2pkh),
        "Only P2PKH addresses are supported"
    );

    let transaction_clone = transaction.clone();
    let sighash_cache = SighashCache::new(&transaction_clone);

    for (index, input) in transaction.input.iter_mut().enumerate() {
        let sighash = sighash_cache
            .legacy_signature_hash(
                index,
                &own_address.script_pubkey(),
                EcdsaSighashType::All.to_u32(),
            )
            .unwrap();

        let signature = signer(
            ctx.key_name.to_string(),
            derivation_path.clone(),
            sighash.as_byte_array().to_vec(),
        )
        .await;

        let mut signature = signature.serialize_der().to_vec();
        signature.push(EcdsaSighashType::All.to_u32() as u8);

        let sig_bytes = PushBytesBuf::try_from(signature).unwrap();
        let pubkey_bytes = PushBytesBuf::try_from(own_public_key.to_bytes()).unwrap();

        input.script_sig = Builder::new()
            .push_slice(sig_bytes)
            .push_slice(pubkey_bytes)
            .into_script();
    }

    transaction
}

View the source on GitHub: p2pkh.rs

The signature function signer: SignFun passed as argument is shown below. This function makes a call to the sign_with_ecdsa system API endpoint to sign the provided transaction sighash.

/// Signs a 32-byte message hash using the ECDSA key derived from the given path.
///
/// This function uses the ICP ECDSA signing API to produce a compact, 64-byte signature.
pub async fn sign_with_ecdsa(
    key_name: String,
    derivation_path: Vec<Vec<u8>>,
    message_hash: Vec<u8>,
) -> Signature {
    let signature = management_canister::sign_with_ecdsa(&SignWithEcdsaArgs {
        message_hash,
        derivation_path,
        key_id: EcdsaKeyId {
            curve: EcdsaCurve::Secp256k1,
            name: key_name,
        },
    })
    .await
    .unwrap()
    .signature;

    Signature::from_compact(&signature).unwrap()
}

View the source on GitHub: ecdsa.rs

Resources