Skip to content

Commit c10e6fb

Browse files
committed
feat(sm-tests) Sign messages using derived private keys
1 parent 84283f2 commit c10e6fb

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)