Skip to content

Commit 5523dd1

Browse files
committed
Merge branch 'leo-sm-derive' into 'master'
feat(sm-tests) Sign messages using derived private keys In the sm tests, we currently sign all the tECDSA messages using the same private key. With this change we will use the derived private key, which is a better representation of tECDSA signing. Closes FI-724. Closes FI-724 See merge request dfinity-lab/public/ic!12490
2 parents e2ac704 + c10e6fb commit 5523dd1

File tree

5 files changed

+101
-8
lines changed

5 files changed

+101
-8
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rs/state_machine_tests/BUILD.bazel

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ DEPENDENCIES = [
77
"//rs/config",
88
"//rs/constants",
99
"//rs/crypto/ecdsa_secp256k1",
10+
"//rs/crypto/extended_bip32",
1011
"//rs/crypto/internal/crypto_lib/seed",
1112
"//rs/crypto/internal/crypto_lib/threshold_sig/bls12_381",
1213
"//rs/crypto/internal/crypto_lib/types",
@@ -51,7 +52,10 @@ DEPENDENCIES = [
5152

5253
rust_library(
5354
name = "state_machine_tests",
54-
srcs = ["src/lib.rs"],
55+
srcs = [
56+
"src/lib.rs",
57+
"src/tests.rs",
58+
],
5559
crate_name = "ic_state_machine_tests",
5660
version = "0.8.0",
5761
deps = DEPENDENCIES,
@@ -101,3 +105,11 @@ rust_test(
101105
"@crate_index//:serde_bytes",
102106
],
103107
)
108+
109+
rust_test(
110+
name = "state_machine_unit_test",
111+
crate = ":state_machine_tests",
112+
deps = [
113+
"@crate_index//:proptest",
114+
],
115+
)

rs/state_machine_tests/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ic-config = { path = "../config" }
1414
ic-constants = { path = "../constants" }
1515
ic-crypto = { path = "../crypto" }
1616
ic-crypto-ecdsa-secp256k1 = { path = "../crypto/ecdsa_secp256k1" }
17+
ic-crypto-extended-bip32 = { path = "../crypto/extended_bip32" }
1718
ic-crypto-internal-seed = { path= "../crypto/internal/crypto_lib/seed" }
1819
ic-crypto-internal-threshold-sig-bls12381 = { path= "../crypto/internal/crypto_lib/threshold_sig/bls12_381" }
1920
ic-crypto-internal-types = { path= "../crypto/internal/crypto_lib/types" }
@@ -54,3 +55,6 @@ tempfile = "3.1.0"
5455
tokio = { version = "1.15.0", features = ["full"] }
5556
wat = "1.0.52"
5657
maplit = "1.0.2"
58+
59+
[dev-dependencies]
60+
proptest = "1.0"

rs/state_machine_tests/src/lib.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use ic_config::{
44
subnet_config::{SubnetConfig, SubnetConfigs},
55
};
66
use ic_constants::{MAX_INGRESS_TTL, PERMITTED_DRIFT, SMALL_APP_SUBNET_MAX_SIZE};
7-
use ic_crypto_ecdsa_secp256k1::PrivateKey;
7+
use ic_crypto_ecdsa_secp256k1::{PrivateKey, PublicKey};
8+
use ic_crypto_extended_bip32::{DerivationIndex, DerivationPath};
89
use ic_crypto_internal_seed::Seed;
910
use ic_crypto_internal_threshold_sig_bls12381::api::{
1011
combine_signatures, combined_public_key, generate_threshold_key, sign_message,
@@ -109,6 +110,9 @@ use std::{fmt, io};
109110
use tempfile::TempDir;
110111
use tokio::runtime::Runtime;
111112

113+
#[cfg(test)]
114+
mod tests;
115+
112116
struct FakeVerifier;
113117

114118
impl Verifier for FakeVerifier {
@@ -766,13 +770,19 @@ impl StateMachine {
766770
.sign_with_ecdsa_contexts
767771
.clone();
768772
for (id, ecdsa_context) in sign_with_ecdsa_contexts {
769-
// TODO Here we use the same key to sign every message.
770-
// Once https://dfinity.atlassian.net/browse/CRP-2029 is closed,
771-
// we should use the derived private key to sign the message.
772-
let signature = self
773-
.ecdsa_secret_key
774-
.sign_message(&ecdsa_context.message_hash);
773+
// The chain code is an additional input used during the key derivation process
774+
// to ensure deterministic generation of child keys from the master key.
775+
// We are using an array with 32 zeros by default.
776+
777+
let signature = sign_message_with_derived_key(
778+
&self.ecdsa_secret_key,
779+
&ecdsa_context.message_hash,
780+
&ecdsa_context.derivation_path,
781+
&[0; 32],
782+
);
783+
775784
let reply = SignWithECDSAReply { signature };
785+
776786
payload.consensus_responses.push(Response {
777787
originator: CanisterId::ic_00(),
778788
respondent: CanisterId::ic_00(),
@@ -1791,6 +1801,36 @@ impl StateMachine {
17911801
}
17921802
}
17931803

1804+
fn sign_message_with_derived_key(
1805+
ecdsa_secret_key: &PrivateKey,
1806+
message_hash: &[u8],
1807+
derivation_path: &[Vec<u8>],
1808+
chain_code: &[u8],
1809+
) -> Vec<u8> {
1810+
let public_key = ecdsa_secret_key.public_key();
1811+
let derivation_path = DerivationPath::new(
1812+
derivation_path
1813+
.iter()
1814+
.cloned()
1815+
.map(DerivationIndex)
1816+
.collect(),
1817+
);
1818+
let derived_public_key_bytes = derivation_path
1819+
.public_key_derivation(&public_key.serialize_sec1(true), chain_code)
1820+
.expect("couldn't derive ecdsa public key");
1821+
let derived_private_key_bytes = derivation_path
1822+
.private_key_derivation(&ecdsa_secret_key.serialize_sec1(), chain_code)
1823+
.expect("couldn't derive ecdsa private key");
1824+
let derived_private_key =
1825+
PrivateKey::deserialize_sec1(&derived_private_key_bytes.derived_private_key)
1826+
.expect("couldn't deserialize to sec1 ecdsa private key");
1827+
let signature = derived_private_key.sign_message(message_hash);
1828+
let pk = PublicKey::deserialize_sec1(&derived_public_key_bytes.derived_public_key)
1829+
.expect("couldn't deserialize sec1");
1830+
assert!(pk.verify_signature(message_hash, &signature));
1831+
signature
1832+
}
1833+
17941834
#[derive(Clone)]
17951835
pub struct PayloadBuilder {
17961836
expiry_time: Time,

rs/state_machine_tests/src/tests.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use ic_crypto_ecdsa_secp256k1::{PrivateKey, PublicKey};
2+
use ic_crypto_extended_bip32::{DerivationIndex, DerivationPath};
3+
use proptest::{collection::vec as pvec, prelude::*, prop_assert, proptest};
4+
5+
proptest! {
6+
#[test]
7+
fn test_derivation_prop(
8+
derivation_path_bytes in pvec(pvec(any::<u8>(), 1..10), 1..10),
9+
message_hash in pvec(any::<u8>(), 32),
10+
chain_code in pvec(any::<u8>(), 32)
11+
) {
12+
let private_key_bytes =
13+
hex::decode("fb7d1f5b82336bb65b82bf4f27776da4db71c1ef632c6a7c171c0cbfa2ea4920").unwrap();
14+
15+
let ecdsa_secret_key: PrivateKey =
16+
PrivateKey::deserialize_sec1(private_key_bytes.as_slice()).unwrap();
17+
18+
let signature = crate::sign_message_with_derived_key(&ecdsa_secret_key, &message_hash, &derivation_path_bytes, &chain_code);
19+
20+
let public_key = ecdsa_secret_key.public_key();
21+
22+
let derivation_path = DerivationPath::new(
23+
derivation_path_bytes
24+
.into_iter()
25+
.map(DerivationIndex)
26+
.collect(),
27+
);
28+
let derived_public_key_bytes = derivation_path
29+
.public_key_derivation(&public_key.serialize_sec1(true), &chain_code)
30+
.expect("couldn't derive ecdsa public key");
31+
let derived_public_key = PublicKey::deserialize_sec1(&derived_public_key_bytes.derived_public_key)
32+
.expect("couldn't deserialize sec1");
33+
prop_assert!(derived_public_key.verify_signature(&message_hash, &signature));
34+
}
35+
}

0 commit comments

Comments
 (0)