Skip to content

Commit 47c5225

Browse files
authored
chore(crypto): CRP-2629 Add VetKD to the state machine tests (#4625)
Adds the ability to write state machine tests that perform VetKD calls, and adds some tests in execution which make use of the state machine test framework.
1 parent 5d40b0f commit 47c5225

File tree

14 files changed

+352
-9
lines changed

14 files changed

+352
-9
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ members = [
112112
"rs/crypto/test_utils/metrics",
113113
"rs/crypto/test_utils/ni-dkg",
114114
"rs/crypto/test_utils/tls",
115+
"rs/crypto/test_utils/vetkd",
115116
"rs/crypto/tls_interfaces",
116117
"rs/crypto/tls_interfaces/mocks",
117118
"rs/crypto/tree_hash",

packages/ic-vetkd-utils/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ impl EncryptedVetKey {
267267
&self,
268268
tsk: &TransportSecretKey,
269269
derived_public_key: &DerivedPublicKey,
270-
context: &[u8],
270+
input: &[u8],
271271
) -> Result<VetKey, String> {
272272
// Check that c1 and c2 have the same discrete logarithm
273273

@@ -286,7 +286,7 @@ impl EncryptedVetKey {
286286
let k = G1Affine::from(G1Projective::from(&self.c3) - self.c1 * tsk.secret_key);
287287

288288
// Check that the VetKey is a valid BLS signature
289-
let msg = augmented_hash_to_g1(&derived_public_key.point, context);
289+
let msg = augmented_hash_to_g1(&derived_public_key.point, input);
290290
let dpk_prep = G2Prepared::from(derived_public_key.point);
291291

292292
use pairing::group::Group;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test_suite")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
rust_library(
6+
name = "vetkd",
7+
testonly = True,
8+
srcs = glob(["src/**"]),
9+
crate_name = "ic_crypto_test_utils_vetkd",
10+
version = "0.1.0",
11+
deps = [
12+
"//rs/crypto/internal/crypto_lib/bls12_381/type",
13+
"//rs/crypto/internal/crypto_lib/bls12_381/vetkd",
14+
"@crate_index//:rand_chacha",
15+
],
16+
)
17+
18+
rust_test_suite(
19+
name = "vetkd_tests",
20+
srcs = glob(["tests/**/*.rs"]),
21+
deps = [
22+
":vetkd",
23+
"//packages/ic-vetkd-utils",
24+
"//rs/crypto/internal/crypto_lib/bls12_381/type",
25+
"//rs/crypto/internal/crypto_lib/bls12_381/vetkd",
26+
"@crate_index//:rand",
27+
"@crate_index//:rand_chacha",
28+
],
29+
)

rs/crypto/test_utils/vetkd/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "ic-crypto-test-utils-vetkd"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
ic-crypto-internal-bls12-381-type = { path = "../../internal/crypto_lib/bls12_381/type" }
8+
ic-crypto-internal-bls12-381-vetkd = { path = "../../internal/crypto_lib/bls12_381/vetkd" }
9+
rand_chacha = { workspace = true }
10+
11+
[dev-dependencies]
12+
ic-vetkd-utils = { path = "../../../../packages/ic-vetkd-utils" }
13+
rand = { workspace = true }

rs/crypto/test_utils/vetkd/src/lib.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use ic_crypto_internal_bls12_381_type::{G1Affine, G2Affine, Scalar};
2+
use ic_crypto_internal_bls12_381_vetkd::{
3+
DerivationContext, EncryptedKey, EncryptedKeyShare, TransportPublicKey,
4+
};
5+
use rand_chacha::rand_core::SeedableRng;
6+
7+
pub fn dummy_transport_public_key() -> [u8; 48] {
8+
G1Affine::generator().serialize()
9+
}
10+
11+
pub struct PrivateKey {
12+
secret_key: Scalar,
13+
public_point: G2Affine,
14+
pk_bytes: Vec<u8>,
15+
}
16+
17+
impl PrivateKey {
18+
pub fn generate(seed: &[u8]) -> Self {
19+
let secret_key = Scalar::hash(b"ic-crypto-vetkd-test-utils-generate-test-key", seed);
20+
Self::from_scalar(secret_key)
21+
}
22+
23+
fn from_scalar(secret_key: Scalar) -> Self {
24+
let public_point = G2Affine::from(G2Affine::generator() * &secret_key);
25+
let pk_bytes = public_point.serialize().to_vec();
26+
Self {
27+
secret_key,
28+
public_point,
29+
pk_bytes,
30+
}
31+
}
32+
33+
pub fn public_key_bytes(&self) -> Vec<u8> {
34+
self.pk_bytes.clone()
35+
}
36+
37+
pub fn vetkd_protocol(
38+
&self,
39+
canister_id: &[u8],
40+
context: &[u8],
41+
input: &[u8],
42+
tpk: &[u8],
43+
seed: &[u8; 32],
44+
) -> Vec<u8> {
45+
let dc = DerivationContext::new(canister_id, context);
46+
47+
let tpk =
48+
TransportPublicKey::deserialize(tpk).expect("Failed to deserialize TransportPublicKey");
49+
50+
let mut rng = rand_chacha::ChaCha20Rng::from_seed(*seed);
51+
52+
let eks = EncryptedKeyShare::create(
53+
&mut rng,
54+
&self.public_point,
55+
&self.secret_key,
56+
&tpk,
57+
&dc,
58+
input,
59+
);
60+
61+
let ek = EncryptedKey::combine_all(&[(0, eks)], 1, &self.public_point, &tpk, &dc, input)
62+
.expect("Failed to combine single EncryptedKeyShare to an EncryptedKey");
63+
64+
ek.serialize().to_vec()
65+
}
66+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use ic_crypto_test_utils_vetkd::*;
2+
use ic_vetkd_utils::{DerivedPublicKey, EncryptedVetKey, TransportSecretKey};
3+
use rand::Rng;
4+
use rand_chacha::rand_core::SeedableRng;
5+
6+
#[test]
7+
fn should_generate_valid_bls_signature() {
8+
let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(42);
9+
10+
let pk = PrivateKey::generate(&rng.gen::<[u8; 32]>());
11+
12+
let canister_id = rng.gen::<[u8; 32]>();
13+
let context = rng.gen::<[u8; 32]>();
14+
let input = rng.gen::<[u8; 32]>();
15+
16+
let tsk = TransportSecretKey::from_seed(rng.gen::<[u8; 32]>().to_vec()).unwrap();
17+
18+
let ek_bytes = pk.vetkd_protocol(
19+
&canister_id,
20+
&context,
21+
&input,
22+
&tsk.public_key(),
23+
&rng.gen::<[u8; 32]>(),
24+
);
25+
26+
let ek = EncryptedVetKey::deserialize(&ek_bytes).unwrap();
27+
28+
let dpk = DerivedPublicKey::deserialize(&pk.public_key_bytes())
29+
.unwrap()
30+
.derive_sub_key(&canister_id)
31+
.derive_sub_key(&context);
32+
33+
assert!(ek.decrypt_and_verify(&tsk, &dpk, &input).is_ok());
34+
}

rs/execution_environment/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ MACRO_DEPENDENCIES = []
6363

6464
DEV_DEPENDENCIES = [
6565
# Keep sorted.
66+
"//rs/crypto/test_utils/vetkd",
6667
"//rs/interfaces/state_manager/mocks",
6768
"//rs/rust_canisters/canister_test",
6869
"//rs/state_machine_tests",

rs/execution_environment/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ assert_matches = { workspace = true }
6666
canister-test = { path = "../rust_canisters/canister_test" }
6767
criterion = { workspace = true }
6868
execution-environment-bench = { path = "benches/lib" }
69+
ic-crypto-test-utils-vetkd = { path = "../crypto/test_utils/vetkd" }
6970
ic-interfaces-state-manager-mocks = { path = "../interfaces/state_manager/mocks" }
7071
ic-management-canister-types-private = { path = "../types/management_canister_types" }
7172
ic-state-machine-tests = { path = "../state_machine_tests" }

rs/execution_environment/tests/threshold_signatures.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use ic_management_canister_types_private::{
44
self as ic00, CanisterInstallMode, DerivationPath, ECDSAPublicKeyResponse, EcdsaCurve,
55
EcdsaKeyId, MasterPublicKeyId, Method, Payload as Ic00Payload, SchnorrAlgorithm, SchnorrKeyId,
66
SchnorrPublicKeyResponse, SignWithBip341Aux, SignWithECDSAReply, SignWithSchnorrAux,
7-
SignWithSchnorrReply,
7+
SignWithSchnorrReply, VetKdCurve, VetKdDeriveKeyResult, VetKdKeyId, VetKdPublicKeyResult,
88
};
99
use ic_registry_subnet_type::SubnetType;
1010
use ic_state_machine_tests::{StateMachine, StateMachineBuilder, UserError};
@@ -48,6 +48,13 @@ fn make_bip340_key(name: &str) -> MasterPublicKeyId {
4848
})
4949
}
5050

51+
fn make_vetkd_key(name: &str) -> MasterPublicKeyId {
52+
MasterPublicKeyId::VetKd(VetKdKeyId {
53+
curve: VetKdCurve::Bls12_381_G2,
54+
name: name.to_string(),
55+
})
56+
}
57+
5158
fn into_inner_ecdsa(key_id: MasterPublicKeyId) -> EcdsaKeyId {
5259
match key_id {
5360
MasterPublicKeyId::Ecdsa(key) => key,
@@ -62,6 +69,13 @@ fn into_inner_schnorr(key_id: MasterPublicKeyId) -> SchnorrKeyId {
6269
}
6370
}
6471

72+
fn into_inner_vetkd(key_id: MasterPublicKeyId) -> VetKdKeyId {
73+
match key_id {
74+
MasterPublicKeyId::VetKd(key) => key,
75+
_ => panic!("unexpected key_id type"),
76+
}
77+
}
78+
6579
fn compute_initial_threshold_key_dealings_payload(
6680
method: Method,
6781
key_id: MasterPublicKeyId,
@@ -107,6 +121,17 @@ fn sign_with_threshold_key_payload(method: Method, key_id: MasterPublicKeyId) ->
107121
}
108122
}
109123
.encode(),
124+
Method::VetKdDeriveKey => {
125+
let key_id = into_inner_vetkd(key_id);
126+
127+
ic00::VetKdDeriveKeyArgs {
128+
context: vec![],
129+
input: vec![],
130+
key_id,
131+
transport_public_key: ic_crypto_test_utils_vetkd::dummy_transport_public_key(),
132+
}
133+
}
134+
.encode(),
110135
_ => panic!("unexpected method"),
111136
}
112137
}
@@ -125,6 +150,12 @@ fn threshold_public_key_payload(method: Method, key_id: MasterPublicKeyId) -> Ve
125150
key_id: into_inner_schnorr(key_id),
126151
}
127152
.encode(),
153+
Method::VetKdPublicKey => ic00::VetKdPublicKeyArgs {
154+
canister_id: None,
155+
context: vec![],
156+
key_id: into_inner_vetkd(key_id),
157+
}
158+
.encode(),
128159
_ => panic!("unexpected method"),
129160
}
130161
}
@@ -422,6 +453,7 @@ fn test_sign_with_threshold_key_fee_charged() {
422453
let contexts = match method {
423454
Method::SignWithECDSA => env.sign_with_ecdsa_contexts(),
424455
Method::SignWithSchnorr => env.sign_with_schnorr_contexts(),
456+
Method::VetKdDeriveKey => env.vetkd_derive_key_contexts(),
425457
_ => panic!("Unexpected method"),
426458
};
427459
let (_, context) = contexts.iter().next().unwrap();
@@ -435,6 +467,7 @@ fn test_sign_with_threshold_key_fee_charged() {
435467
let signature = match method {
436468
Method::SignWithECDSA => expect_reply::<SignWithECDSAReply>(result).signature,
437469
Method::SignWithSchnorr => expect_reply::<SignWithSchnorrReply>(result).signature,
470+
Method::VetKdDeriveKey => expect_reply::<VetKdDeriveKeyResult>(result).encrypted_key,
438471
_ => panic!("Unexpected method"),
439472
};
440473
// Expect non-empty signature.
@@ -512,6 +545,11 @@ fn test_sign_with_threshold_key_unknown_key_rejected() {
512545
make_bip340_key("correct_key"),
513546
make_bip340_key("wrong_key"),
514547
),
548+
(
549+
Method::VetKdDeriveKey,
550+
make_vetkd_key("correct_key"),
551+
make_vetkd_key("wrong_key"),
552+
),
515553
];
516554
for (method, correct_key, wrong_key) in test_cases {
517555
let own_subnet = subnet_test_id(1);
@@ -624,6 +662,12 @@ fn test_signing_disabled_vs_unknown_key_on_public_key_and_signing_requests() {
624662
make_bip340_key("signing_disabled_key"),
625663
make_bip340_key("unknown_key"),
626664
),
665+
(
666+
Method::VetKdPublicKey,
667+
Method::VetKdDeriveKey,
668+
make_vetkd_key("signing_disabled_key"),
669+
make_vetkd_key("unknown_key"),
670+
),
627671
];
628672
for (public_key_method, sign_with_method, signing_disabled_key, unknown_key) in test_cases {
629673
let own_subnet = subnet_test_id(1);
@@ -654,6 +698,10 @@ fn test_signing_disabled_vs_unknown_key_on_public_key_and_signing_requests() {
654698
let response = expect_reply::<SchnorrPublicKeyResponse>(result);
655699
assert!(!response.public_key.is_empty() && !response.chain_code.is_empty());
656700
}
701+
Method::VetKdPublicKey => {
702+
let response = expect_reply::<VetKdPublicKeyResult>(result);
703+
assert!(!response.public_key.is_empty());
704+
}
657705
_ => panic!("Unexpected method"),
658706
}
659707

@@ -710,6 +758,11 @@ fn test_threshold_key_public_key_req_with_unknown_key_rejected() {
710758
make_bip340_key("correct_key"),
711759
make_bip340_key("wrong_key"),
712760
),
761+
(
762+
Method::VetKdPublicKey,
763+
make_vetkd_key("correct_key"),
764+
make_vetkd_key("wrong_key"),
765+
),
713766
];
714767
for (method, correct_key, wrong_key) in test_cases {
715768
let own_subnet = subnet_test_id(1);
@@ -742,6 +795,7 @@ fn test_sign_with_threshold_key_fee_ignored_for_nns() {
742795
(Method::SignWithECDSA, make_ecdsa_key("some_key")),
743796
(Method::SignWithSchnorr, make_ed25519_key("some_key")),
744797
(Method::SignWithSchnorr, make_bip340_key("some_key")),
798+
(Method::VetKdDeriveKey, make_vetkd_key("some_key")),
745799
];
746800
for (method, key_id) in test_cases {
747801
let fee = 1_000_000;
@@ -778,6 +832,7 @@ fn test_sign_with_threshold_key_fee_ignored_for_nns() {
778832
let contexts = match method {
779833
Method::SignWithECDSA => env.sign_with_ecdsa_contexts(),
780834
Method::SignWithSchnorr => env.sign_with_schnorr_contexts(),
835+
Method::VetKdDeriveKey => env.vetkd_derive_key_contexts(),
781836
_ => panic!("Unexpected method"),
782837
};
783838
let (_, context) = contexts.iter().next().unwrap();
@@ -791,6 +846,7 @@ fn test_sign_with_threshold_key_queue_fills_up() {
791846
(Method::SignWithECDSA, make_ecdsa_key("some_key"), 20),
792847
(Method::SignWithSchnorr, make_ed25519_key("some_key"), 20),
793848
(Method::SignWithSchnorr, make_bip340_key("some_key"), 20),
849+
(Method::VetKdDeriveKey, make_vetkd_key("some_key"), 20),
794850
];
795851
for (method, key_id, max_queue_size) in test_cases {
796852
let fee = 1_000_000;
@@ -804,11 +860,14 @@ fn test_sign_with_threshold_key_queue_fills_up() {
804860
.with_nns_subnet_id(nns_subnet)
805861
.with_ecdsa_signature_fee(fee)
806862
.with_schnorr_signature_fee(fee)
863+
.with_vetkd_derive_key_fee(fee)
807864
.with_chain_key(key_id.clone())
808865
// Turn off automatic ECDSA signatures to fill up the queue.
809866
.with_ecdsa_signing_enabled(false)
810867
// Turn off automatic Schnorr signatures to fill up the queue.
811868
.with_schnorr_signing_enabled(false)
869+
// Turn off automatic VetKey derivation to fill up the queue.
870+
.with_vetkd_enabled(false)
812871
.build();
813872

814873
let canister_id = create_universal_canister(&env);

0 commit comments

Comments
 (0)