Skip to content

Commit

Permalink
feat(ecdsa): CON-1192 Add get_oldest_ecdsa_state_registry_version()
Browse files Browse the repository at this point in the history
  • Loading branch information
eichhorl committed Jan 18, 2024
1 parent c1a1563 commit e0993fb
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 4 deletions.
1 change: 1 addition & 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 rs/consensus/utils/BUILD.bazel
Expand Up @@ -24,6 +24,7 @@ DEV_DEPENDENCIES = [
"//rs/consensus/mocks",
"//rs/test_utilities",
"//rs/test_utilities/registry",
"//rs/types/ic00_types",
]

rust_library(
Expand Down
3 changes: 2 additions & 1 deletion rs/consensus/utils/Cargo.toml
Expand Up @@ -24,4 +24,5 @@ slog = { workspace = true }
ic-consensus = { path = "../" }
ic-consensus-mocks = { path = "../mocks" }
ic-test-utilities = { path = "../../test_utilities" }
ic-test-utilities-registry = { path = "../../test_utilities/registry" }
ic-test-utilities-registry = { path = "../../test_utilities/registry" }
ic-ic00-types = { path = "../../types/ic00_types" }
215 changes: 212 additions & 3 deletions rs/consensus/utils/src/lib.rs
Expand Up @@ -13,7 +13,9 @@ use ic_protobuf::registry::subnet::v1::SubnetRecord;
use ic_registry_client_helpers::subnet::{NotarizationDelaySettings, SubnetRegistry};
use ic_replicated_state::ReplicatedState;
use ic_types::{
consensus::{Block, BlockProposal, HasCommittee, HasHeight, HasRank, Rank},
consensus::{
ecdsa::EcdsaPayload, Block, BlockProposal, HasCommittee, HasHeight, HasRank, Rank,
},
crypto::{
threshold_sig::ni_dkg::{NiDkgTag, NiDkgTranscript},
CryptoHash, CryptoHashable, Signed,
Expand Down Expand Up @@ -521,13 +523,52 @@ pub fn get_subnet_record(
}
}

/// Return the oldest registry version of transcripts in the given ECDSA summary payload that are
/// referenced by the given replicated state.
pub fn get_oldest_ecdsa_state_registry_version(
ecdsa: &EcdsaPayload,
state: &ReplicatedState,
) -> Option<RegistryVersion> {
state
.sign_with_ecdsa_contexts()
.values()
.flat_map(|context| context.matched_quadruple.as_ref())
.flat_map(|(quadruple_id, _)| ecdsa.available_quadruples.get(quadruple_id))
.flat_map(|quadruple| quadruple.get_refs())
.flat_map(|transcript_ref| ecdsa.idkg_transcripts.get(&transcript_ref.transcript_id))
.map(|transcript| transcript.registry_version)
.min()
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use super::*;
use ic_consensus_mocks::{dependencies, Dependencies};
use ic_test_utilities::types::ids::node_test_id;
use ic_ic00_types::EcdsaKeyId;
use ic_replicated_state::metadata_state::subnet_call_context_manager::SignWithEcdsaContext;
use ic_test_utilities::{
mock_time,
state::ReplicatedStateBuilder,
types::{
ids::{node_test_id, subnet_test_id},
messages::RequestBuilder,
},
};
use ic_types::{
crypto::{ThresholdSigShare, ThresholdSigShareOf},
consensus::ecdsa::{
EcdsaKeyTranscript, EcdsaUIDGenerator, KeyTranscriptCreation, MaskedTranscript,
PreSignatureQuadrupleRef, QuadrupleId, UnmaskedTranscript,
},
crypto::{
canister_threshold_sig::idkg::{
IDkgMaskedTranscriptOrigin, IDkgReceivers, IDkgTranscript, IDkgTranscriptId,
IDkgTranscriptType, IDkgUnmaskedTranscriptOrigin,
},
ThresholdSigShare, ThresholdSigShareOf,
},
messages::CallbackId,
signature::ThresholdSignatureShare,
};

Expand Down Expand Up @@ -665,4 +706,172 @@ mod tests {
assert_eq!(round_robin.call_next(&calls), vec![1]);
assert_eq!(round_robin.call_next(&calls), vec![1]);
}

fn empty_ecdsa_payload() -> EcdsaPayload {
EcdsaPayload {
signature_agreements: BTreeMap::new(),
available_quadruples: BTreeMap::new(),
ongoing_signatures: BTreeMap::new(),
quadruples_in_creation: BTreeMap::new(),
uid_generator: EcdsaUIDGenerator::new(subnet_test_id(0), Height::new(0)),
idkg_transcripts: BTreeMap::new(),
ongoing_xnet_reshares: BTreeMap::new(),
xnet_reshare_agreements: BTreeMap::new(),
key_transcript: EcdsaKeyTranscript {
current: None,
next_in_creation: KeyTranscriptCreation::Begin,
key_id: EcdsaKeyId::from_str("Secp256k1:some_key").unwrap(),
},
}
}

fn fake_transcript(id: IDkgTranscriptId, registry_version: RegistryVersion) -> IDkgTranscript {
IDkgTranscript {
transcript_id: id,
receivers: IDkgReceivers::new(BTreeSet::from_iter([node_test_id(0)])).unwrap(),
registry_version,
verified_dealings: Default::default(),
transcript_type: IDkgTranscriptType::Unmasked(
IDkgUnmaskedTranscriptOrigin::ReshareMasked(fake_transcript_id(0)),
),
algorithm_id: ic_types::crypto::AlgorithmId::EcdsaSecp256k1,
internal_transcript_raw: vec![],
}
}

fn fake_transcript_id(id: u64) -> IDkgTranscriptId {
IDkgTranscriptId::new(subnet_test_id(0), id, Height::from(0))
}

// Create a fake quadruple, it will use transcripts with ids
// id, id+1, id+2, and id+3.
fn fake_quadruple(id: u64) -> PreSignatureQuadrupleRef {
let temp_rv = RegistryVersion::from(0);
let kappa_unmasked = fake_transcript(fake_transcript_id(id), temp_rv);
let mut lambda_masked = kappa_unmasked.clone();
lambda_masked.transcript_id = fake_transcript_id(id + 1);
lambda_masked.transcript_type =
IDkgTranscriptType::Masked(IDkgMaskedTranscriptOrigin::Random);
let mut kappa_times_lambda = lambda_masked.clone();
kappa_times_lambda.transcript_id = fake_transcript_id(id + 2);
let mut key_times_lambda = lambda_masked.clone();
key_times_lambda.transcript_id = fake_transcript_id(id + 3);
let h = Height::from(0);
PreSignatureQuadrupleRef {
kappa_unmasked_ref: UnmaskedTranscript::try_from((h, &kappa_unmasked)).unwrap(),
lambda_masked_ref: MaskedTranscript::try_from((h, &lambda_masked)).unwrap(),
kappa_times_lambda_ref: MaskedTranscript::try_from((h, &kappa_times_lambda)).unwrap(),
key_times_lambda_ref: MaskedTranscript::try_from((h, &key_times_lambda)).unwrap(),
key_unmasked_ref: None,
}
}

fn fake_context(quadruple_id: Option<QuadrupleId>) -> SignWithEcdsaContext {
SignWithEcdsaContext {
request: RequestBuilder::new().build(),
key_id: EcdsaKeyId::from_str("Secp256k1:some_key").unwrap(),
message_hash: [0; 32],
derivation_path: vec![],
pseudo_random_id: [0; 32],
matched_quadruple: quadruple_id.map(|qid| (qid, Height::from(0))),
nonce: None,
batch_time: mock_time(),
}
}

fn fake_state_with_contexts(contexts: Vec<SignWithEcdsaContext>) -> ReplicatedState {
let mut state = ReplicatedStateBuilder::default().build();
let iter = contexts
.into_iter()
.enumerate()
.map(|(i, context)| (CallbackId::from(i as u64), context));
state
.metadata
.subnet_call_context_manager
.sign_with_ecdsa_contexts = BTreeMap::from_iter(iter);
state
}

// Create an ECDSA payload with 10 quadruples, each using registry version 2, 3 or 4.
fn ecdsa_payload_with_quadruples() -> EcdsaPayload {
let mut ecdsa = empty_ecdsa_payload();
let key_id = ecdsa.key_transcript.key_id.clone();
let mut rvs = [
RegistryVersion::from(2),
RegistryVersion::from(3),
RegistryVersion::from(4),
]
.into_iter()
.cycle();
for i in (0..40).step_by(4) {
let quadruple = fake_quadruple(i as u64);
let rv = rvs.next().unwrap();
for r in quadruple.get_refs() {
ecdsa
.idkg_transcripts
.insert(r.transcript_id, fake_transcript(r.transcript_id, rv));
}
ecdsa
.available_quadruples
.insert(QuadrupleId(i as u64, Some(key_id.clone())), quadruple);
}
ecdsa
}

#[test]
fn test_empty_state_should_return_no_registry_version() {
let ecdsa = ecdsa_payload_with_quadruples();
let state = fake_state_with_contexts(vec![]);
assert_eq!(
None,
get_oldest_ecdsa_state_registry_version(&ecdsa, &state)
);
}

#[test]
fn test_state_without_matches_should_return_no_registry_version() {
let ecdsa = ecdsa_payload_with_quadruples();
let state = fake_state_with_contexts(vec![fake_context(None)]);
assert_eq!(
None,
get_oldest_ecdsa_state_registry_version(&ecdsa, &state)
);
}

#[test]
fn test_should_return_oldest_registry_version() {
let ecdsa = ecdsa_payload_with_quadruples();
// create contexts for all quadruples, but only create a match for
// quadruples with registry version >= 3 (not 2!). Thus the oldest
// registry version referenced by the state should be 3.
let contexts = ecdsa
.available_quadruples
.iter()
.map(|(id, quad)| {
let t_id = quad.lambda_masked_ref.as_ref().transcript_id;
let transcript = ecdsa.idkg_transcripts.get(&t_id).unwrap();
(transcript.registry_version.get() >= 3).then_some(id.clone())
})
.map(fake_context)
.collect();
let state = fake_state_with_contexts(contexts);
assert_eq!(
Some(RegistryVersion::from(3)),
get_oldest_ecdsa_state_registry_version(&ecdsa, &state)
);

let mut ecdsa_without_transcripts = ecdsa.clone();
ecdsa_without_transcripts.idkg_transcripts = BTreeMap::new();
assert_eq!(
None,
get_oldest_ecdsa_state_registry_version(&ecdsa_without_transcripts, &state)
);

let mut ecdsa_without_quadruples = ecdsa.clone();
ecdsa_without_quadruples.available_quadruples = BTreeMap::new();
assert_eq!(
None,
get_oldest_ecdsa_state_registry_version(&ecdsa_without_quadruples, &state)
);
}
}

0 comments on commit e0993fb

Please sign in to comment.