Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/backend-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: |
set -eExuo pipefail
export CARGO_TERM_COLOR=always # ensure output has colors
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister -p ic-vetkeys-canisters-tests
cargo test
cargo-test-backend-darwin:
runs-on: macos-15
Expand All @@ -38,5 +38,5 @@ jobs:
run: |
set -eExuo pipefail
export CARGO_TERM_COLOR=always # ensure output has colors
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister
cargo build --release --target wasm32-unknown-unknown -p ic-vetkeys-manager-canister -p ic-vetkeys-encrypted-maps-canister -p ic-vetkeys-canisters-tests
cargo test
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"backend/rs/ic_vetkeys_test_utils",
"backend/rs/canisters/ic_vetkeys_encrypted_maps_canister",
"backend/rs/canisters/ic_vetkeys_manager_canister",
"backend/rs/canisters/tests",
"examples/basic_ibe/backend",
"examples/basic_timelock_ibe/backend",
"examples/password_manager_with_metadata/backend"
Expand Down
25 changes: 25 additions & 0 deletions backend/rs/canisters/tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "ic-vetkeys-canisters-tests"
authors.workspace = true
description.workspace = true
documentation.workspace = true
edition.workspace = true
version.workspace = true
license.workspace = true

[lib]
path = "src/lib.rs"
crate-type = ["cdylib"]

[dependencies]
candid = { workspace = true }
ic-cdk = { workspace = true }
ic-cdk-macros = { workspace = true }
ic-dummy-getrandom-for-wasm = { workspace = true }
ic-vetkeys = { path = "../../ic_vetkeys" }
serde = { workspace = true }

[dev-dependencies]
ic-vetkeys-test-utils = { path = "../../ic_vetkeys_test_utils" }
pocket-ic = { workspace = true }
rand = { workspace = true }
9 changes: 9 additions & 0 deletions backend/rs/canisters/tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.PHONY: build
.SILENT: build
build:
cargo build --release --target wasm32-unknown-unknown

.PHONY: test
.SILENT: test
test: build
cargo test
3 changes: 3 additions & 0 deletions backend/rs/canisters/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Canister tests

Currently, we only test `ic_vetkeys::management_canister::derive_public_vetkey`.
Comment thread
altkdf marked this conversation as resolved.
Outdated
65 changes: 65 additions & 0 deletions backend/rs/canisters/tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use ic_cdk::update;
use ic_vetkeys::vetkd_api_types::{
VetKDDeriveKeyReply, VetKDDeriveKeyRequest, VetKDKeyId, VetKDPublicKeyReply,
VetKDPublicKeyRequest,
};

#[update]
async fn sign_with_bls(input: Vec<u8>, context: Vec<u8>, key_id: VetKDKeyId) -> Vec<u8> {
ic_vetkeys::management_canister::sign_with_bls(input, context, key_id)
.await
.expect("derive_public_vetkey call failed")
Comment thread
altkdf marked this conversation as resolved.
Outdated
}

#[update]
async fn vetkd_derive_key(
input: Vec<u8>,
context: Vec<u8>,
key_id: VetKDKeyId,
transport_public_key: Vec<u8>,
) -> Vec<u8> {
let request = VetKDDeriveKeyRequest {
input,
context,
key_id,
// Encryption with the G1 generator produces unencrypted vetKeys
Comment thread
altkdf marked this conversation as resolved.
Outdated
transport_public_key,
};

let reply: (VetKDDeriveKeyReply,) = ic_cdk::api::call::call_with_payment128(
candid::Principal::management_canister(),
"vetkd_derive_key",
(request,),
26_153_846_153,
)
.await
.expect("vetkd_derive_key call failed");

reply.0.encrypted_key
}

#[update]
async fn bls_public_key(context: Vec<u8>, key_id: VetKDKeyId) -> Vec<u8> {
Comment thread
altkdf marked this conversation as resolved.
Outdated
ic_vetkeys::management_canister::bls_public_key(None, context, key_id)
.await
.expect("bls_public_key call failed")
}

#[update]
async fn get_verification_key(context: Vec<u8>, key_id: VetKDKeyId) -> Vec<u8> {
Comment thread
altkdf marked this conversation as resolved.
Outdated
let request = VetKDPublicKeyRequest {
canister_id: None,
context,
key_id,
};

let reply: (VetKDPublicKeyReply,) = ic_cdk::api::call::call(
candid::Principal::management_canister(),
"vetkd_public_key",
(request,),
)
.await
.expect("vetkd_public_key call failed");

reply.0.public_key
}
140 changes: 140 additions & 0 deletions backend/rs/canisters/tests/tests/sign_with_bls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use candid::{decode_one, encode_args, CandidType, Principal};
use ic_vetkeys::vetkd_api_types::{VetKDCurve, VetKDKeyId};
use ic_vetkeys::{DerivedPublicKey, EncryptedVetKey, TransportSecretKey};
use ic_vetkeys_test_utils::{git_root_dir, reproducible_rng};
use pocket_ic::{PocketIc, PocketIcBuilder};
use rand::{CryptoRng, Rng};
use std::path::Path;

#[test]
fn bls_signature_should_be_equal_to_decrypted_vetkey() {
let rng = &mut reproducible_rng();
let env = TestEnvironment::new();
let input = random_bytes(rng, 10);
let context = random_bytes(rng, 10);
let key_id = VetKDKeyId {
curve: VetKDCurve::Bls12_381_G2,
name: "dfx_test_key".to_string(),
};
let transport_secret_key = random_transport_key(rng);
let transport_public_key = transport_secret_key.public_key();

let bls_signature: Vec<u8> = env.update(
Principal::anonymous(),
"sign_with_bls",
encode_args((input.clone(), context.clone(), key_id.clone())).unwrap(),
);

let verification_key: Vec<u8> = env.update(
Principal::anonymous(),
"get_verification_key",
encode_args((context.clone(), key_id.clone())).unwrap(),
);
let encrypted_vetkey_bytes: Vec<u8> = env.update(
Principal::anonymous(),
"vetkd_derive_key",
encode_args((input.clone(), context, key_id, transport_public_key)).unwrap(),
);
let encrypted_vetkey = EncryptedVetKey::deserialize(encrypted_vetkey_bytes.as_ref()).unwrap();
let derived_public_key = DerivedPublicKey::deserialize(verification_key.as_ref()).unwrap();
let decrypted_vetkey = encrypted_vetkey
.decrypt_and_verify(&transport_secret_key, &derived_public_key, &input)
.unwrap();

assert_eq!(bls_signature, decrypted_vetkey.signature_bytes().to_vec());
}

#[test]
fn bls_public_key_should_be_equal_to_verification_key() {
let rng = &mut reproducible_rng();
let env = TestEnvironment::new();
let context = random_bytes(rng, 10);
let key_id = VetKDKeyId {
curve: VetKDCurve::Bls12_381_G2,
name: "dfx_test_key".to_string(),
};
let bls_public_key: Vec<u8> = env.update(
Principal::anonymous(),
"bls_public_key",
encode_args((context.clone(), key_id.clone())).unwrap(),
);
let verification_key: Vec<u8> = env.update(
Principal::anonymous(),
"get_verification_key",
encode_args((context.clone(), key_id.clone())).unwrap(),
);
assert_eq!(bls_public_key, verification_key);
}
struct TestEnvironment {
pic: PocketIc,
canister_id: Principal,
}

impl TestEnvironment {
fn new() -> Self {
let pic = PocketIcBuilder::new()
.with_application_subnet()
.with_ii_subnet()
.with_fiduciary_subnet()
.with_nonmainnet_features(true)
.build();

let canister_id = pic.create_canister();
pic.add_cycles(canister_id, 2_000_000_000_000);

let wasm_bytes = load_canister_wasm();
pic.install_canister(canister_id, wasm_bytes, vec![], None);

// Make sure the canister is properly initialized
fast_forward(&pic, 5);

Self { pic, canister_id }
}

fn update<T: CandidType + for<'de> candid::Deserialize<'de>>(
&self,
caller: Principal,
method_name: &str,
args: Vec<u8>,
) -> T {
let reply = self
.pic
.update_call(self.canister_id, caller, method_name, args);
match reply {
Ok(data) => decode_one(&data).expect("failed to decode reply"),
Err(user_error) => panic!("canister returned a user error: {user_error}"),
}
}
}

fn load_canister_wasm() -> Vec<u8> {
let wasm_path_string = match std::env::var("CUSTOM_WASM_PATH") {
Ok(path) if !path.is_empty() => path,
_ => format!(
"{}/target/wasm32-unknown-unknown/release/ic_vetkeys_canisters_tests.wasm",
git_root_dir()
),
};
let wasm_path = Path::new(&wasm_path_string);
std::fs::read(wasm_path)
.expect("wasm does not exist - run `cargo build --release --target wasm32-unknown-unknown`")
}

fn random_transport_key<R: Rng + CryptoRng>(rng: &mut R) -> TransportSecretKey {
let mut seed = vec![0u8; 32];
rng.fill_bytes(&mut seed);
TransportSecretKey::from_seed(seed).unwrap()
}

fn random_bytes<R: Rng + CryptoRng>(rng: &mut R, max_length: usize) -> Vec<u8> {
let length = rng.gen_range(0..max_length);
let mut bytes = vec![0u8; length];
rng.fill_bytes(&mut bytes);
bytes
}

fn fast_forward(ic: &PocketIc, ticks: u64) {
for _ in 0..ticks - 1 {
ic.tick();
}
}
2 changes: 2 additions & 0 deletions backend/rs/ic_vetkeys/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use ic_stable_structures::{
};
use serde::{Deserialize, Serialize};

pub type CanisterId = candid::Principal;

pub type KeyName = Blob<32>;
pub type MapName = KeyName;
pub type MapId = KeyId;
Expand Down
Loading