Signing t-ECDSA messages
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_path
s.
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_key
: 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.
- Motoko
- rust
// 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" };
});
use ic_cdk::api::management_canister::ecdsa::{sign_with_ecdsa, EcdsaCurve, EcdsaKeyId, SignWithECDSA};
let message = "Hello, World!";
// sha256 needs to be imported!
let message_hash = sha256(&message).to_vec();
let request = SignWithECDSA {
message_hash,
derivation_path: vec![],
key_id: EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: "dfx_test_key".to_string(),
},
let (response,) = sign_with_ecdsa(request)
.await
.expect("sign_with_ecdsa failed");
let signature = response.signature;
}
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.
- Motoko
- Rust
// 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" };
});
use ic_cdk::api::management_canister::ecdsa::{
ecdsa_public_key, EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgument,
};
let request = ECDSAPublicKey {
canister_id: None,
derivation_path: vec![],
key_id: EcdsaKeyId {
curve: EcdsaCurve::Secp256k1,
name: "dfx_test_key".to_string(),
},
};
let (response,) = ecdsa_public_key(request)
.await
.expect("ecdsa_public_key failed");
let public_key = response.public_key;