Threshold ECDSA Signatures

Suppose n=3f+1 and let C be a set of f+1 corrupt parties. Partition the remaining set of 2f honest parties into two sets A and B, each of size f. C+A can sign and certify state_1 while C+B can sign and certify state_2. This is how we end up with two different certified states, each signed by 2f+1 parties.


I have a half finished book somewhere with a number of thoughts that propose that voluntary submission to financial violence is a solution to eliminate physical violence (which we’ve see through rule of law for decades so it isn’t really new thoughts). Crypto doesn’t make this new, but it makes it enforceable by math rather than the whims of human enforcement.

The NNS sort of reverts this back to mob mentality over math. The one thing that tips it to a workable system is the locking mechanism. It works because people generally won’t vote to make decisions that will hurt the value of the network because their exposure to the network is locked in.(this is why a neuron marketplace would tip the scales back to mob mentality).

For some reason node providers aren’t subjected to this. Why isn’t there a significant stake explicitly tied to hosting a node? While the cpus/system provided are specialized, they are hardly axis with only one purpose and could easily be repurposed to other productive economic activity.

My understanding is that a bit of a sweetheart deal was offered to get the network off the ground which is completely understandable, but we should have a timeline for that being corrected even if the first movers are grandfathered in for stability. Eventually those should be dwarfed by new nodes and they should be exposed to the network value volatility. Note: we may have to pay for this through additional rewards. It has to be profitable to contribute in an honest way. Perhaps a slowly increasing percentage of node rewards should be paid as locked neurons in a way that makes economic sense for the providers.


I absolutely agree this should happen, but I believe the situation is considerably worse than you have painted when it comes to BTC integration. The NNS could, at best, ensure node providers have skin in the ICP game and are therefore incentivised not to tank that token’s price. But these node providers will potentially have the opportunity to steal millions worth of BTC. Since BTC value will be relatively unaffected by a crisis in ICP, they can happily plot to make off with enough BTC to outweigh what they will lose through the drop in value of ICP consequent to the steal.
Which means we fall back on two ways to protect the network that do not involve crypto. Firstly, binding contracts which make node providers severely liable in their respective jurisdictions for any breach of trust. Second the hope that node providers are kept honest by the fear that a lot of bad hombres have invested in both ICP and ckBTC.

1 Like

Yep…agree 100%:

Perhaps tecdsa needs its own collateral/rewards mechanism.

1 Like

Hello everyone!

I have a practical question. How do I verify that a public key was derived from a canister_id non-interactively?

An example in Rust would be much appreciated. Thanks in advance!

Is your question about:

  • How to derive subkeys of a canister given its “master canister public key” and a chain-code?
  • How to verify the master public key of a canister?

For the first point, you can derive sub keys using extended BIP32 derivation path. You can find a rust crate for the extended bip32 here and the method implementing the derivation here. To use this you need the canister “master public key” and a chain code. These can be obtained by calling (once) the ecdsa_public_key API using the canister ID and an empty derivation path. TLDR, if you can get hold of the master key of a canister you can verify every other canister key using the extended bip32 library.

The second question it is a bit more complicated. If you had the master public key of the subnet available, then you could use the extended-BIP32 derivation to compute every canister master public key (and thus any other subkey). This is done by using the canister_id as derivation path and using the 0* string as the initial chain code. However, the subnet master key is not directly accessible to canisters at the moment. The registry actually contains the IDKG dealings that were used to generate the keys, so one could actually try to recompute the subnet master public key from there, but it would require some extra work.


The question is “having a message M, a principal P and a signature S, verify that P is the issuer of S”.

I’m working on a way to certify data with an alternative certification flow that uses tECDSA instead of set_certified_data API. So, the second question is what I was initially asking.

My motivation for this is that ECDSA libraries are much more accessible, comparing to BLS, which will make it much easier to create client-side verification libraries for these alternative certificates in other languages.

Plus, set_certified_data API is a little bit inconvenient, since it requires us to use two separate canister calls (first, you have to execute an update call, to add a certificate to the state tree, then you have to use a query call to fetch the new certificate). With tECDSA this can be done in a single update call, that signs the certificate and immediately returns it to the user.

What is missing now is the ability to pack the certificate with everything you need in order to verify it off-chain, which is the subnet’s master key, as I see from your answer.

I’m not blocked by this, since there are alternative solutions.

  1. I can verify signatures interactively, asking a canister to fetch the public key by canister_id for me.
  2. All the canisters which issue signatures are currently in my project’s perimeter, so I can have a root-of-trust canister that will issue delegations to other canisters and each delegation can have this principal -> public key mapping embedded. Basically mimicking the same flow you do with the chain-key.

But it would be easier if that functionality was available out of the box.

Thank you for your answer!

Your approach seems reasonable! One way to address this could be to have the ECDSA subnet to add the master key to the certified state, so that users could have a reliable way to obtain the key. In your application you could then either directly use the subnet key as root of trust, or potentially even chain it back to the NNS public key (although this may partially collide with some of your motivations).


After some more thought, I think the easiest for now would be for us to manually compute the master public key and then provide some code to check that the derivation is correct. I’ll post an update in this thread in the next days with the public key and some example code.


Hey @senior.joinu, I was able to get the master ECDSA public keys from mainnet:

let production_public_key = "02121bc3a5c38f38ca76487c72007ebbfd34bc6c4cb80a671655aa94585bbd0a02";
let test_public_key = "02f9ac345f6be6db51e1c5612cddb59e72c3d0d493c994d12035cf13257e3b1fa7"

You can derive any canister key from the master key using something along these lines:

fn derive_public_key(
    canister_id: Principal,
    derivation_path: Vec<Vec<u8>>,
) -> Result<EcdsaPublicKeyResponse, String> {
    use ic_crypto_extended_bip32::{
        DerivationIndex, DerivationPath,
    let master_key_hex = "02121bc3a5c38f38ca76487c72007ebbfd34bc6c4cb80a671655aa94585bbd0a02";

    let master_key = hex::decode(master_key_hex).expect("Master key could not be deserialized");
    let master_chain_code = [0u8; 32];

    let mut path = vec![];
    let derivation_index = DerivationIndex(canister_id.as_slice().to_vec());

    for index in derivation_path {
    let derivation_path = DerivationPath::new(path);

    let res = derivation_path
        .map_err(|err| format!("Internal Error: {:?}", err))?;

    Ok(EcdsaPublicKeyResponse {
        public_key: res.derived_public_key,
        chain_code: res.derived_chain_code,

I also wrote a small test to verify the derivation of the ckbtc master public key and chain-code (which you could see from the first lines of the logs in here):

fn check_ckbtc_key_locally() {
    let ckbtc_minter_id = Principal::from_str("mqygn-kiaaa-aaaar-qaadq-cai").unwrap();
    let ckbtc_public_key = "0222047a81d4f8a067031c89273d241b79a5a007c04dfaf36d07963db0b99097eb";
    let ckbtc_chain_code = "821aebb643bd97d319d2fd0b2e483d4e7de2ea9039ff67568b693e6abc14a03b";

    let derived_key = derive_public_key(ckbtc_minter_id, vec![]);

    assert!(derived_key.is_ok(), "{}", derived_key.unwrap_err());
    let derived_key = derived_key.unwrap();

    assert_eq!(hex::encode(derived_key.public_key), ckbtc_public_key);
    assert_eq!(hex::encode(derived_key.chain_code), ckbtc_chain_code);

Hope this helps!



This is awesome!
Thank you so much for this!

1 Like

By the way @saikatdas0790 - this is the way you can use to differentiate between canisters from your project and other canisters.

Just introduce a publicly known root canister and make this root issue certificates to each of your canisters. Then check for these certificates, when you want to restrict access.

You can even implement capabilities this way.