From ebf09057fb53cb18b2e9d269ed837c9bb36d901d Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Tue, 25 Jan 2022 10:21:04 -0800 Subject: [PATCH 01/11] System API for threshold ECDSA --- spec/ic.did | 10 ++++++++++ spec/index.adoc | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/spec/ic.did b/spec/ic.did index 5b64725d1..ce82f14be 100644 --- a/spec/ic.did +++ b/spec/ic.did @@ -44,6 +44,16 @@ service ic : { deposit_cycles : (record {canister_id : canister_id}) -> (); raw_rand : () -> (blob); + // Threshold ECDSA signature + get_ecdsa_public_key : (record { + canister_id : opt canister_id; + derivation_path : vec blob; + }) -> (blob); + sign_with_ecdsa : (record { + message_hash : blob; + derivation_path : vec blob; + }) -> (blob); + // provisional interfaces for the pre-ledger world provisional_create_canister_with_cycles : (record { amount: opt nat; diff --git a/spec/index.adoc b/spec/index.adoc index b081cd498..2711e1f3e 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1654,6 +1654,22 @@ There is no restriction on who can invoke this method. This method takes no input and returns 32 pseudo-random bytes to the caller. The return value is unknown to any part of the IC at time of the submission of this call. A new return value is generated for each call to this method. +[#ic-get_ecdsa_public_key] +=== IC method `get_ecdsa_public_key` + +This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The derivation path is a vector of bytes. + +This call requires that the ecdsa feature is enabled and working properly, and the `canister_id` meets the requirement of a canister id. +Otherwise it will be rejected. + +[#ic-sign_with_ecdsa] +=== IC method `sign_with_ecdsa` + +This method returns a new threshold ECDSA signature of the given `message_hash` that can be separately verified against the caller's ECDSA public key derived from the given derivation path. + +This call requires that the ecdsa feature is enabled and working properly, the caller is a canister, and `message_hash` is of 32-bytes long. +Otherwise it will be rejected. + [#ic-provisional_create_canister_with_cycles] === IC method `provisional_create_canister_with_cycles` From 8f28b7b5a7db16f5688e837c9fdcaaeed1558740 Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Tue, 25 Jan 2022 10:57:23 -0800 Subject: [PATCH 02/11] Minor rewording --- spec/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.adoc b/spec/index.adoc index 2711e1f3e..248ecca02 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1657,7 +1657,7 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The [#ic-get_ecdsa_public_key] === IC method `get_ecdsa_public_key` -This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The derivation path is a vector of bytes. +This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The derivation path is a vector of variable length byte strings. This call requires that the ecdsa feature is enabled and working properly, and the `canister_id` meets the requirement of a canister id. Otherwise it will be rejected. From 8f44a3d61cdb64a5381ce5434f164921dbe9a492 Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Fri, 4 Feb 2022 11:25:59 -0800 Subject: [PATCH 03/11] Change return value to record instead of tuple --- spec/ic.did | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ic.did b/spec/ic.did index ce82f14be..e60b53635 100644 --- a/spec/ic.did +++ b/spec/ic.did @@ -48,7 +48,7 @@ service ic : { get_ecdsa_public_key : (record { canister_id : opt canister_id; derivation_path : vec blob; - }) -> (blob); + }) -> (record { public_key: blob; chain_code: blob; }); sign_with_ecdsa : (record { message_hash : blob; derivation_path : vec blob; From 5890ce0d10a4b4e08a297ef5f8b85a426fa511a6 Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Fri, 4 Feb 2022 11:26:14 -0800 Subject: [PATCH 04/11] Document derivation path and BIP 32 compatibility --- spec/index.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index 248ecca02..cd2516273 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1658,16 +1658,17 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The === IC method `get_ecdsa_public_key` This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The derivation path is a vector of variable length byte strings. +For https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032] compatibility, each byte string (`blob`) must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^ (non-hardened). -This call requires that the ecdsa feature is enabled and working properly, and the `canister_id` meets the requirement of a canister id. +This call requires that the ECDSA feature is enabled and working properly, and the `canister_id` meets the requirement of a canister id. Otherwise it will be rejected. [#ic-sign_with_ecdsa] === IC method `sign_with_ecdsa` -This method returns a new threshold ECDSA signature of the given `message_hash` that can be separately verified against the caller's ECDSA public key derived from the given derivation path. +This method returns a new threshold ECDSA signature of the given `message_hash` that can be separately verified against the caller's ECDSA public key derived from the caller's canister id and the given derivation path. -This call requires that the ecdsa feature is enabled and working properly, the caller is a canister, and `message_hash` is of 32-bytes long. +This call requires that the ECDSA feature is enabled and working properly, the caller is a canister, and `message_hash` is of 32-bytes long. Otherwise it will be rejected. [#ic-provisional_create_canister_with_cycles] From f07b980334bcc4259c2f91ebb43668daed426033 Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Fri, 4 Feb 2022 11:37:28 -0800 Subject: [PATCH 05/11] Add key_id as an argument --- spec/ic.did | 2 ++ spec/index.adoc | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/ic.did b/spec/ic.did index e60b53635..099580006 100644 --- a/spec/ic.did +++ b/spec/ic.did @@ -48,10 +48,12 @@ service ic : { get_ecdsa_public_key : (record { canister_id : opt canister_id; derivation_path : vec blob; + key_id : text; }) -> (record { public_key: blob; chain_code: blob; }); sign_with_ecdsa : (record { message_hash : blob; derivation_path : vec blob; + key_id : text; }) -> (blob); // provisional interfaces for the pre-ledger world diff --git a/spec/index.adoc b/spec/index.adoc index cd2516273..e36e945c1 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1657,8 +1657,10 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The [#ic-get_ecdsa_public_key] === IC method `get_ecdsa_public_key` -This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The derivation path is a vector of variable length byte strings. +This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. +The `derivation_path` is a vector of variable length byte strings. For https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032] compatibility, each byte string (`blob`) must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^ (non-hardened). +The only supported `key_id` value at the moment is `"seckp256k1"`. This call requires that the ECDSA feature is enabled and working properly, and the `canister_id` meets the requirement of a canister id. Otherwise it will be rejected. @@ -1666,7 +1668,8 @@ Otherwise it will be rejected. [#ic-sign_with_ecdsa] === IC method `sign_with_ecdsa` -This method returns a new threshold ECDSA signature of the given `message_hash` that can be separately verified against the caller's ECDSA public key derived from the caller's canister id and the given derivation path. +This method returns a new threshold ECDSA signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. +This public key can be obtained by calling `get_ecdsa_public_key` with the caller's `canister_id`, and the same `derivation_path` and `key_id` used here. This call requires that the ECDSA feature is enabled and working properly, the caller is a canister, and `message_hash` is of 32-bytes long. Otherwise it will be rejected. From fb0a446d03490df76b6045a62a05669dd97761dd Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Fri, 4 Feb 2022 11:43:47 -0800 Subject: [PATCH 06/11] Explain chain code --- spec/ic.did | 2 +- spec/index.adoc | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/ic.did b/spec/ic.did index 099580006..391cfd188 100644 --- a/spec/ic.did +++ b/spec/ic.did @@ -49,7 +49,7 @@ service ic : { canister_id : opt canister_id; derivation_path : vec blob; key_id : text; - }) -> (record { public_key: blob; chain_code: blob; }); + }) -> (record { public_key : blob; chain_code : blob; }); sign_with_ecdsa : (record { message_hash : blob; derivation_path : vec blob; diff --git a/spec/index.adoc b/spec/index.adoc index e36e945c1..b19362687 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1660,7 +1660,9 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. For https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032] compatibility, each byte string (`blob`) must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^ (non-hardened). -The only supported `key_id` value at the moment is `"seckp256k1"`. +The only supported `key_id` value at the moment is `"secp256k1"`. + +The return result also contains a `chain_code` which can be used for key derivation purpose if needed. This call requires that the ECDSA feature is enabled and working properly, and the `canister_id` meets the requirement of a canister id. Otherwise it will be rejected. From b221a2997b3908b7009c547ee6d4fb5845fdf100 Mon Sep 17 00:00:00 2001 From: Paul Liu Date: Tue, 8 Feb 2022 10:57:04 -0800 Subject: [PATCH 07/11] Update spec/index.adoc Co-authored-by: Andrea Cerulli <19587477+andreacerulli@users.noreply.github.com> --- spec/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.adoc b/spec/index.adoc index b19362687..3f861eb63 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1657,7 +1657,7 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The [#ic-get_ecdsa_public_key] === IC method `get_ecdsa_public_key` -This method returns the threshold ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. +This method returns the ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. For https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032] compatibility, each byte string (`blob`) must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^ (non-hardened). The only supported `key_id` value at the moment is `"secp256k1"`. From 2a37bb015c441940cf728eae8053fae85bbce154 Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Tue, 8 Feb 2022 11:02:08 -0800 Subject: [PATCH 08/11] Apply suggestions from reviews --- spec/index.adoc | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index 3f861eb63..cec17b177 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1657,23 +1657,26 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The [#ic-get_ecdsa_public_key] === IC method `get_ecdsa_public_key` -This method returns the ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. +This method returns a https://www.secg.org/sec1-v2.pdf[SEC1] encoded ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. -For https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032] compatibility, each byte string (`blob`) must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^ (non-hardened). The only supported `key_id` value at the moment is `"secp256k1"`. -The return result also contains a `chain_code` which can be used for key derivation purpose if needed. +For `key_id="secp256k1"`, the public key is derived using a generalization of BIP32 (see https://ia.cr/2021/1330[ia.cr/2021/1330, Appendix D]). To derive (non-hardened) https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032]-compatible public keys, each byte string (`blob`) in the `derivation_path` must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^. -This call requires that the ECDSA feature is enabled and working properly, and the `canister_id` meets the requirement of a canister id. +The return result is an extended public key consisting of an ECDSA `public_key`, encoded in https://www.secg.org/sec2-v2.pdf[SEC1] compressed form, and a `chain_code`, which can be used to deterministically derive child keys of the `public_key`. + +This call requires that the ECDSA feature is enabled, and the `canister_id` meets the requirement of a canister id. Otherwise it will be rejected. [#ic-sign_with_ecdsa] === IC method `sign_with_ecdsa` -This method returns a new threshold ECDSA signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. +This method returns a new https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf[ECDSA] signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. This public key can be obtained by calling `get_ecdsa_public_key` with the caller's `canister_id`, and the same `derivation_path` and `key_id` used here. -This call requires that the ECDSA feature is enabled and working properly, the caller is a canister, and `message_hash` is of 32-bytes long. +The signatures are encoded as the concatenation of the https://www.secg.org/sec2-v2.pdf[SEC1] encodings of the two values r and s. For key_id="secp256k1", this corresponds to 32-byte big-endian encoding. + +This call requires that the ECDSA feature is enabled, the caller is a canister, and `message_hash` is 32 bytes long. Otherwise it will be rejected. [#ic-provisional_create_canister_with_cycles] From 7050c06c7e0bd79f24cb556a9ffa01dd5e6e83ec Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Thu, 10 Feb 2022 23:23:26 -0800 Subject: [PATCH 09/11] Return signature as a record --- spec/ic.did | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ic.did b/spec/ic.did index 391cfd188..592153ef2 100644 --- a/spec/ic.did +++ b/spec/ic.did @@ -54,7 +54,7 @@ service ic : { message_hash : blob; derivation_path : vec blob; key_id : text; - }) -> (blob); + }) -> (record { signature : blob }); // provisional interfaces for the pre-ledger world provisional_create_canister_with_cycles : (record { From aa19fc09412a16f47dd35a221d415dad32baaf53 Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Thu, 10 Mar 2022 08:11:01 -0800 Subject: [PATCH 10/11] Rename get_ecdsa_public_key to ecdsa_public_key --- spec/ic.did | 2 +- spec/index.adoc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/ic.did b/spec/ic.did index e4efa0ce0..cb4f40177 100644 --- a/spec/ic.did +++ b/spec/ic.did @@ -73,7 +73,7 @@ service ic : { }) -> (variant { Ok : http_response; Err: opt http_request_error }); // Threshold ECDSA signature - get_ecdsa_public_key : (record { + ecdsa_public_key : (record { canister_id : opt canister_id; derivation_path : vec blob; key_id : text; diff --git a/spec/index.adoc b/spec/index.adoc index e4c8dc627..ae50c6445 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1657,8 +1657,8 @@ There is no restriction on who can invoke this method. This method takes no input and returns 32 pseudo-random bytes to the caller. The return value is unknown to any part of the IC at time of the submission of this call. A new return value is generated for each call to this method. -[#ic-get_ecdsa_public_key] -=== IC method `get_ecdsa_public_key` +[#ic-ecdsa_public_key] +=== IC method `ecdsa_public_key` This method returns a https://www.secg.org/sec1-v2.pdf[SEC1] encoded ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. @@ -1675,7 +1675,7 @@ Otherwise it will be rejected. === IC method `sign_with_ecdsa` This method returns a new https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf[ECDSA] signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. -This public key can be obtained by calling `get_ecdsa_public_key` with the caller's `canister_id`, and the same `derivation_path` and `key_id` used here. +This public key can be obtained by calling `ecdsa_public_key` with the caller's `canister_id`, and the same `derivation_path` and `key_id` used here. The signatures are encoded as the concatenation of the https://www.secg.org/sec2-v2.pdf[SEC1] encodings of the two values r and s. For key_id="secp256k1", this corresponds to 32-byte big-endian encoding. From d7b49e2c2a7de1104eed0967851b93195ee3d539 Mon Sep 17 00:00:00 2001 From: "Paul H. Liu" Date: Wed, 6 Apr 2022 13:43:52 -0700 Subject: [PATCH 11/11] Specify key_id as a struct --- spec/ic.did | 6 ++++-- spec/index.adoc | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/ic.did b/spec/ic.did index cb4f40177..cb889b33e 100644 --- a/spec/ic.did +++ b/spec/ic.did @@ -35,6 +35,8 @@ type http_request_error = variant { conn_timeout; }; +type ecdsa_curve = variant { secp256k1; }; + service ic : { create_canister : (record { settings : opt canister_settings @@ -76,12 +78,12 @@ service ic : { ecdsa_public_key : (record { canister_id : opt canister_id; derivation_path : vec blob; - key_id : text; + key_id : record { curve: ecdsa_curve; name: text }; }) -> (record { public_key : blob; chain_code : blob; }); sign_with_ecdsa : (record { message_hash : blob; derivation_path : vec blob; - key_id : text; + key_id : record { curve: ecdsa_curve; name: text }; }) -> (record { signature : blob }); // provisional interfaces for the pre-ledger world diff --git a/spec/index.adoc b/spec/index.adoc index ae50c6445..dad035de0 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -1662,9 +1662,10 @@ This method takes no input and returns 32 pseudo-random bytes to the caller. The This method returns a https://www.secg.org/sec1-v2.pdf[SEC1] encoded ECDSA public key for the given canister using the given derivation path. If the `canister_id` is unspecified, it will default to the canister id of the caller. The `derivation_path` is a vector of variable length byte strings. -The only supported `key_id` value at the moment is `"secp256k1"`. +The `key_id` is a struct specifying both a curve and a name. +The availability of a particular `key_id` depends on implementation. -For `key_id="secp256k1"`, the public key is derived using a generalization of BIP32 (see https://ia.cr/2021/1330[ia.cr/2021/1330, Appendix D]). To derive (non-hardened) https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032]-compatible public keys, each byte string (`blob`) in the `derivation_path` must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^. +For curve `secp256k1`, the public key is derived using a generalization of BIP32 (see https://ia.cr/2021/1330[ia.cr/2021/1330, Appendix D]). To derive (non-hardened) https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP-0032]-compatible public keys, each byte string (`blob`) in the `derivation_path` must be a 4-byte big-endian encoding of an unsigned integer less than 2^31^. The return result is an extended public key consisting of an ECDSA `public_key`, encoded in https://www.secg.org/sec2-v2.pdf[SEC1] compressed form, and a `chain_code`, which can be used to deterministically derive child keys of the `public_key`. @@ -1677,7 +1678,7 @@ Otherwise it will be rejected. This method returns a new https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf[ECDSA] signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. This public key can be obtained by calling `ecdsa_public_key` with the caller's `canister_id`, and the same `derivation_path` and `key_id` used here. -The signatures are encoded as the concatenation of the https://www.secg.org/sec2-v2.pdf[SEC1] encodings of the two values r and s. For key_id="secp256k1", this corresponds to 32-byte big-endian encoding. +The signatures are encoded as the concatenation of the https://www.secg.org/sec2-v2.pdf[SEC1] encodings of the two values r and s. For curve `secp256k1`, this corresponds to 32-byte big-endian encoding. This call requires that the ECDSA feature is enabled, the caller is a canister, and `message_hash` is 32 bytes long. Otherwise it will be rejected.