Skip to content

Commit

Permalink
Merge branch 'jack/crp-1429' into 'master'
Browse files Browse the repository at this point in the history
chore(crypto): CRP-1429 Add further tests of IDKG verify_transcript

 

See merge request dfinity-lab/public/ic!11748
  • Loading branch information
randombit committed Apr 19, 2023
2 parents 3268a6b + 03ebbf2 commit 6b8c3d0
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 16 deletions.
89 changes: 89 additions & 0 deletions rs/crypto/test_utils/canister_threshold_sigs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,60 @@ pub fn create_and_verify_signed_dealing(
signed_dealing
}

pub fn swap_two_dealings_in_transcript(
params: &IDkgTranscriptParams,
transcript: IDkgTranscript,
env: &CanisterThresholdSigTestEnvironment,
a: NodeIndex,
b: NodeIndex,
) -> IDkgTranscript {
assert!(a != b);

let a_id = transcript.dealer_id_for_index(a).unwrap();
let b_id = transcript.dealer_id_for_index(b).unwrap();

let dealing_a = transcript
.verified_dealings
.get(&a)
.expect("Dealing exists")
.clone();

let dealing_b = transcript
.verified_dealings
.get(&b)
.expect("Dealing exists")
.clone();

let dealing_ba = dealing_b
.content
.into_builder()
.with_dealer_id(a_id)
.build_and_sign_from(params, env, a_id);

let dealing_ab = dealing_a
.content
.into_builder()
.with_dealer_id(b_id)
.build_and_sign_from(params, env, b_id);

let dealing_ab_signed = add_support_from_all_receivers(env, params, dealing_ab);

let dealing_ba_signed = add_support_from_all_receivers(env, params, dealing_ba);

let mut transcript = transcript;

assert!(transcript
.verified_dealings
.insert(a, dealing_ba_signed)
.is_some());
assert!(transcript
.verified_dealings
.insert(b, dealing_ab_signed)
.is_some());

transcript
}

pub fn create_and_verify_signed_dealings(
params: &IDkgTranscriptParams,
crypto_components: &BTreeMap<NodeId, TempCryptoComponent>,
Expand Down Expand Up @@ -185,6 +239,19 @@ pub fn batch_sign_signed_dealings(
.collect()
}

pub fn add_support_from_all_receivers(
env: &CanisterThresholdSigTestEnvironment,
params: &IDkgTranscriptParams,
dealing: SignedIDkgDealing,
) -> BatchSignedIDkgDealing {
batch_signature_from_signers(
params.registry_version(),
&env.crypto_components,
dealing,
params.receivers().get(),
)
}

pub fn create_transcript(
params: &IDkgTranscriptParams,
crypto_components: &BTreeMap<NodeId, TempCryptoComponent>,
Expand Down Expand Up @@ -621,6 +688,18 @@ pub fn random_dealer_id_excluding(transcript: &IDkgTranscript, exclusion: NodeId
.expect("dealer index not in transcript")
}

pub fn n_random_dealer_indexes(transcript: &IDkgTranscript, n: usize) -> Vec<NodeIndex> {
let mut rng = thread_rng();

assert!(transcript.verified_dealings.len() >= n);

transcript
.verified_dealings
.keys()
.cloned()
.choose_multiple(&mut rng, n)
}

pub fn random_crypto_component_not_in_receivers(
env: &CanisterThresholdSigTestEnvironment,
receivers: &IDkgReceivers,
Expand Down Expand Up @@ -1034,6 +1113,16 @@ impl SignedIDkgDealingBuilder {
self.build()
}

pub fn build_and_sign_from(
self,
params: &IDkgTranscriptParams,
env: &CanisterThresholdSigTestEnvironment,
signer_id: NodeId,
) -> SignedIDkgDealing {
let crypto = crypto_for(signer_id, &env.crypto_components);
self.build_with_signature(params, crypto, signer_id)
}

pub fn corrupt_signature(mut self) -> Self {
self.signature = self.signature.clone_with_bit_flipped();
self
Expand Down
183 changes: 167 additions & 16 deletions rs/crypto/tests/canister_threshold_sigs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,29 @@ use ic_crypto_internal_threshold_sig_ecdsa::{EccScalar, IDkgDealingInternal, MEG
use ic_crypto_tecdsa::derive_tecdsa_public_key;
use ic_crypto_temp_crypto::TempCryptoComponent;
use ic_crypto_test_utils::{crypto_for, dkg::dummy_idkg_transcript_id_for_tests};
use ic_crypto_test_utils_canister_threshold_sigs::load_previous_transcripts_and_create_signed_dealings;
use ic_crypto_test_utils_canister_threshold_sigs::random_crypto_component_not_in_receivers;
use ic_crypto_test_utils_canister_threshold_sigs::sig_share_from_each_receiver;
use ic_crypto_test_utils_canister_threshold_sigs::IntoBuilder;
use ic_crypto_test_utils_canister_threshold_sigs::{
batch_sign_signed_dealings, batch_signature_from_signers, build_params_from_previous,
create_and_verify_signed_dealing, create_and_verify_signed_dealings, create_signed_dealing,
generate_key_transcript, generate_presig_quadruple, load_input_transcripts, load_transcript,
node_id, random_dealer_id, random_dealer_id_excluding, random_node_id_excluding,
random_receiver_for_inputs, random_receiver_id, random_receiver_id_excluding,
run_idkg_and_create_and_verify_transcript, run_tecdsa_protocol,
CanisterThresholdSigTestEnvironment,
add_support_from_all_receivers, batch_sign_signed_dealings, batch_signature_from_signers,
build_params_from_previous, create_and_verify_signed_dealing,
create_and_verify_signed_dealings, create_signed_dealing, generate_key_transcript,
generate_presig_quadruple, load_input_transcripts,
load_previous_transcripts_and_create_signed_dealings, load_transcript, n_random_dealer_indexes,
node_id, random_crypto_component_not_in_receivers, random_dealer_id,
random_dealer_id_excluding, random_node_id_excluding, random_receiver_for_inputs,
random_receiver_id, random_receiver_id_excluding, run_idkg_and_create_and_verify_transcript,
run_tecdsa_protocol, sig_share_from_each_receiver, swap_two_dealings_in_transcript,
CanisterThresholdSigTestEnvironment, IntoBuilder,
};
use ic_interfaces::crypto::{IDkgProtocol, ThresholdEcdsaSigVerifier, ThresholdEcdsaSigner};
use ic_types::crypto::canister_threshold_sig::error::IDkgVerifyInitialDealingsError;
use ic_types::crypto::canister_threshold_sig::error::{
IDkgCreateDealingError, IDkgCreateTranscriptError, IDkgOpenTranscriptError,
IDkgVerifyComplaintError, IDkgVerifyDealingPublicError, IDkgVerifyOpeningError,
ThresholdEcdsaCombineSigSharesError, ThresholdEcdsaSignShareError,
IDkgVerifyComplaintError, IDkgVerifyDealingPublicError, IDkgVerifyInitialDealingsError,
IDkgVerifyOpeningError, IDkgVerifyTranscriptError, ThresholdEcdsaCombineSigSharesError,
ThresholdEcdsaSignShareError,
};
use ic_types::crypto::canister_threshold_sig::idkg::InitialIDkgDealings;
use ic_types::crypto::canister_threshold_sig::idkg::{
BatchSignedIDkgDealing, IDkgComplaint, IDkgMaskedTranscriptOrigin, IDkgReceivers,
IDkgTranscript, IDkgTranscriptOperation, IDkgTranscriptParams, IDkgTranscriptType,
IDkgUnmaskedTranscriptOrigin, SignedIDkgDealing,
IDkgUnmaskedTranscriptOrigin, InitialIDkgDealings, SignedIDkgDealing,
};
use ic_types::crypto::canister_threshold_sig::{
ExtendedDerivationPath, PreSignatureQuadruple, ThresholdEcdsaSigInputs,
Expand Down Expand Up @@ -811,6 +809,159 @@ mod verify_transcript {
let key_transcript = generate_key_transcript(&env, AlgorithmId::ThresholdEcdsaSecp256k1);
generate_presig_quadruple(&env, AlgorithmId::ThresholdEcdsaSecp256k1, &key_transcript);
}

#[test]
fn should_verify_transcript_accept_random_transcript_with_dealings_swapped() {
/*
This behavior may seem strange but it follows from how random dealings
are combined, and the fact that we are really checking that the
transcript is *consistent* with the set of dealings, rather than
checking that there is a 1:1 correspondence between the dealings
and the transcript
*/

let mut rng = thread_rng();

let subnet_size = rng.gen_range(4..10);
let env = CanisterThresholdSigTestEnvironment::new(subnet_size);

let params = env.params_for_random_sharing(AlgorithmId::ThresholdEcdsaSecp256k1);

let transcript = run_idkg_and_create_and_verify_transcript(&params, &env.crypto_components);

let node_ids = n_random_dealer_indexes(&transcript, 2);

let transcript =
swap_two_dealings_in_transcript(&params, transcript, &env, node_ids[0], node_ids[1]);

let r = crypto_for(random_receiver_id(&params), &env.crypto_components)
.verify_transcript(&params, &transcript);

assert_matches!(r, Ok(()));
}

#[test]
fn should_verify_transcript_reject_reshared_transcript_with_dealings_swapped() {
let subnet_size = thread_rng().gen_range(4..10);
let env = CanisterThresholdSigTestEnvironment::new(subnet_size);

let masked_key_params = env.params_for_random_sharing(AlgorithmId::ThresholdEcdsaSecp256k1);

let masked_key_transcript =
run_idkg_and_create_and_verify_transcript(&masked_key_params, &env.crypto_components);

let params = build_params_from_previous(
masked_key_params,
IDkgTranscriptOperation::ReshareOfMasked(masked_key_transcript),
);

let transcript = run_idkg_and_create_and_verify_transcript(&params, &env.crypto_components);

let node_ids = n_random_dealer_indexes(&transcript, 2);

let transcript =
swap_two_dealings_in_transcript(&params, transcript, &env, node_ids[0], node_ids[1]);

let r = crypto_for(random_receiver_id(&params), &env.crypto_components)
.verify_transcript(&params, &transcript);

assert_matches!(r, Err(IDkgVerifyTranscriptError::InvalidTranscript));
}

#[test]
fn should_verify_transcript_reject_random_transcript_with_dealing_replaced() {
let subnet_size = thread_rng().gen_range(4..10);
let env = CanisterThresholdSigTestEnvironment::new(subnet_size);

let params = env.params_for_random_sharing(AlgorithmId::ThresholdEcdsaSecp256k1);

let mut transcript =
run_idkg_and_create_and_verify_transcript(&params, &env.crypto_components);

let node_ids = n_random_dealer_indexes(&transcript, 2);

let node0_idx = node_ids[0];
let node1_idx = node_ids[1];

let node1_id = transcript.dealer_id_for_index(node1_idx).unwrap();

let dealing = transcript
.verified_dealings
.get(&node0_idx)
.expect("Dealing exists")
.clone();

let dealing_resigned = dealing
.content
.into_builder()
.with_dealer_id(node1_id)
.build_and_sign_from(&params, &env, node1_id);

let dealing = add_support_from_all_receivers(&env, &params, dealing_resigned);

assert!(transcript
.verified_dealings
.insert(node1_idx, dealing)
.is_some());

let r = crypto_for(random_receiver_id(&params), &env.crypto_components)
.verify_transcript(&params, &transcript);

assert_matches!(r, Err(IDkgVerifyTranscriptError::InvalidTranscript));
}

#[test]
fn should_verify_transcript_reject_transcript_with_insufficient_dealings() {
let subnet_size = thread_rng().gen_range(4..10);
let env = CanisterThresholdSigTestEnvironment::new(subnet_size);

let params = env.params_for_random_sharing(AlgorithmId::ThresholdEcdsaSecp256k1);

let mut transcript =
run_idkg_and_create_and_verify_transcript(&params, &env.crypto_components);

while transcript.verified_dealings.len() >= params.collection_threshold().get() as usize {
transcript.verified_dealings.pop_first();
}

let r = crypto_for(random_receiver_id(&params), &env.crypto_components)
.verify_transcript(&params, &transcript);

assert_matches!(r, Err(IDkgVerifyTranscriptError::InvalidArgument(msg))
if msg.starts_with("failed to verify transcript against params: insufficient number of dealings"));
}

#[test]
fn should_verify_transcript_reject_transcript_with_corrupted_internal_data() {
let subnet_size = thread_rng().gen_range(4..10);
let env = CanisterThresholdSigTestEnvironment::new(subnet_size);

let params = env.params_for_random_sharing(AlgorithmId::ThresholdEcdsaSecp256k1);

let mut transcript =
run_idkg_and_create_and_verify_transcript(&params, &env.crypto_components);

let mut rng = thread_rng();

let raw_len = transcript.internal_transcript_raw.len();
let corrupted_idx = rng.gen::<usize>() % raw_len;
transcript.internal_transcript_raw[corrupted_idx] ^= 1;

let r = crypto_for(random_receiver_id(&params), &env.crypto_components)
.verify_transcript(&params, &transcript);

// Since the corruption is randomized, we might corrupt the CBOR or the commitments
// and thus different errors may result
match r {
Err(IDkgVerifyTranscriptError::InvalidTranscript) => {}

Err(IDkgVerifyTranscriptError::SerializationError(msg)) => {
assert!(msg.starts_with("failed to deserialize internal transcript"))
}
Err(e) => panic!("Unexpected error {:?}", e),
Ok(()) => panic!("Unexpected success"),
}
}
}

mod sign_share {
Expand Down

0 comments on commit 6b8c3d0

Please sign in to comment.