Skip to main content

Signing t-ECDSA messages

Advanced
Tutorial

Overview

The threshold ECDSA API allows canisters to securely sign messages and transactions without anyone ever having direct access to the corresponding private keys. Each canister can control an unlimited number of keys by specifying different key derivation_paths. The API provides two methods:

  • sign_with_ecdsa: Used to sign messages.

  • ecdsa_public_key: Used to obtain public keys.

Signing messages

To sign a message, the sign_with_ecdsa method is used. It uses the following Candid interface:

type sign_with_ecdsa_args = record {
message_hash : blob; // The hash of the message to sign.
derivation_path : vec blob; // An optional derivation path
key_id : record { curve : ecdsa_curve; name : text };
};

type sign_with_ecdsa_result = record {
signature : blob;
};

service: {
sign_with_ecdsa : (sign_with_ecdsa_args) -> (sign_with_ecdsa_result);
}

The sign_with_ecdsa method allows specifying an optional derivation_path which allows a canister to request signatures for different keys controlled by the canister. The key_id field specifies the curve and name of the key to sign with. Currently only the secp256k1 curve and the following key names are supported:

  • dfx_test_key1: Only available on the local replica started by dfx.
  • test_key_1: Test key available on the ICP mainnet.
  • key_1: Production key available on the ICP mainnet.

// Define the interface for the management canister. Only the `sign_with_ecdsa` method is used in this example:
type IC = actor {
sign_with_ecdsa : ({
message_hash : Blob;
derivation_path : [Blob];
key_id : { curve: { #secp256k1; } ; name: Text };
}) -> async ({ signature : Blob });
};

// Declare "ic" to be the management canister, which is evoked by `actor("aaaaa-aa")`:
let ic : IC = actor("aaaaa-aa");

let message: Text = "Hello, World!";
// SHA256 needs to be imported!
let message_hash: Blob = Blob.fromArray(SHA256.sha256(Blob.toArray(Text.encodeUtf8(message))));

Cycles.add(10_000_000_000);

let { signature } = await ic.sign_with_ecdsa({
message_hash;
derivation_path = [];
key_id = { curve = #secp256k1; name = "dfx_test_key" };
});

Cost

The sign_with_ecdsa method requires cycles to be attached. When the method in the Rust CDK is used, the required cycles are automatically attached. In Motoko, the cycles need to be added manually. Learn more about threshold ECDSA costs.

Obtaining public keys

To verify a signature, the public key that corresponds to the private key that was used to sign the message is needed. Furthermore, the public key can be used to derive addresses controlled by the canister on various blockchain networks. ICP provides the ecdsa_public_key method to obtain public keys. This method uses the following Candid interface:

type ecdsa_public_key_args = record {
canister_id : opt canister_id;
derivation_path : vec blob;
key_id : record { curve : ecdsa_curve; name : text };
};

type ecdsa_public_key_result = record {
public_key : blob;
chain_code : blob;
};

service : {
ecdsa_public_key : (ecdsa_public_key_args) -> (ecdsa_public_key_result);
};

The ecdsa_public_key method can be used to obtain public keys controlled by canisters. In contrast to the sign_with_ecdsa method, it allows specifying an optional canister_id, i.e. the caller can request public keys controlled by other canisters, but not signatures. The chain_code in the result can be used to derive child keys using a derivation path without calling the ecdsa_public_key repeatedly.


// Define the interface for the management canister. Only the `ecdsa_public_key` method is used in this example:
type IC = actor {
ecdsa_public_key : ({
canister_id : ?Principal;
derivation_path : [Blob];
key_id : { curve: { #secp256k1; } ; name: Text };
}) -> async ({ public_key : Blob; chain_code : Blob; });
};

// Declare "ic" to be the management canister, which is evoked by `actor("aaaaa-aa")`:
let ic : IC = actor("aaaaa-aa");

// Make a call to the management canister to request an ECDSA public key:
let { public_key } = await ic.ecdsa_public_key({
canister_id = null;
derivation_path = [];
key_id = { curve = #secp256k1; name = "test_key_1" };
});

Resources