Skip to content

Commit e0993fb

Browse files
committed
feat(ecdsa): CON-1192 Add get_oldest_ecdsa_state_registry_version()
1 parent c1a1563 commit e0993fb

File tree

4 files changed

+216
-4
lines changed

4 files changed

+216
-4
lines changed

Cargo.lock

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

rs/consensus/utils/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ DEV_DEPENDENCIES = [
2424
"//rs/consensus/mocks",
2525
"//rs/test_utilities",
2626
"//rs/test_utilities/registry",
27+
"//rs/types/ic00_types",
2728
]
2829

2930
rust_library(

rs/consensus/utils/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ slog = { workspace = true }
2424
ic-consensus = { path = "../" }
2525
ic-consensus-mocks = { path = "../mocks" }
2626
ic-test-utilities = { path = "../../test_utilities" }
27-
ic-test-utilities-registry = { path = "../../test_utilities/registry" }
27+
ic-test-utilities-registry = { path = "../../test_utilities/registry" }
28+
ic-ic00-types = { path = "../../types/ic00_types" }

rs/consensus/utils/src/lib.rs

Lines changed: 212 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use ic_protobuf::registry::subnet::v1::SubnetRecord;
1313
use ic_registry_client_helpers::subnet::{NotarizationDelaySettings, SubnetRegistry};
1414
use ic_replicated_state::ReplicatedState;
1515
use ic_types::{
16-
consensus::{Block, BlockProposal, HasCommittee, HasHeight, HasRank, Rank},
16+
consensus::{
17+
ecdsa::EcdsaPayload, Block, BlockProposal, HasCommittee, HasHeight, HasRank, Rank,
18+
},
1719
crypto::{
1820
threshold_sig::ni_dkg::{NiDkgTag, NiDkgTranscript},
1921
CryptoHash, CryptoHashable, Signed,
@@ -521,13 +523,52 @@ pub fn get_subnet_record(
521523
}
522524
}
523525

526+
/// Return the oldest registry version of transcripts in the given ECDSA summary payload that are
527+
/// referenced by the given replicated state.
528+
pub fn get_oldest_ecdsa_state_registry_version(
529+
ecdsa: &EcdsaPayload,
530+
state: &ReplicatedState,
531+
) -> Option<RegistryVersion> {
532+
state
533+
.sign_with_ecdsa_contexts()
534+
.values()
535+
.flat_map(|context| context.matched_quadruple.as_ref())
536+
.flat_map(|(quadruple_id, _)| ecdsa.available_quadruples.get(quadruple_id))
537+
.flat_map(|quadruple| quadruple.get_refs())
538+
.flat_map(|transcript_ref| ecdsa.idkg_transcripts.get(&transcript_ref.transcript_id))
539+
.map(|transcript| transcript.registry_version)
540+
.min()
541+
}
542+
524543
#[cfg(test)]
525544
mod tests {
545+
use std::str::FromStr;
546+
526547
use super::*;
527548
use ic_consensus_mocks::{dependencies, Dependencies};
528-
use ic_test_utilities::types::ids::node_test_id;
549+
use ic_ic00_types::EcdsaKeyId;
550+
use ic_replicated_state::metadata_state::subnet_call_context_manager::SignWithEcdsaContext;
551+
use ic_test_utilities::{
552+
mock_time,
553+
state::ReplicatedStateBuilder,
554+
types::{
555+
ids::{node_test_id, subnet_test_id},
556+
messages::RequestBuilder,
557+
},
558+
};
529559
use ic_types::{
530-
crypto::{ThresholdSigShare, ThresholdSigShareOf},
560+
consensus::ecdsa::{
561+
EcdsaKeyTranscript, EcdsaUIDGenerator, KeyTranscriptCreation, MaskedTranscript,
562+
PreSignatureQuadrupleRef, QuadrupleId, UnmaskedTranscript,
563+
},
564+
crypto::{
565+
canister_threshold_sig::idkg::{
566+
IDkgMaskedTranscriptOrigin, IDkgReceivers, IDkgTranscript, IDkgTranscriptId,
567+
IDkgTranscriptType, IDkgUnmaskedTranscriptOrigin,
568+
},
569+
ThresholdSigShare, ThresholdSigShareOf,
570+
},
571+
messages::CallbackId,
531572
signature::ThresholdSignatureShare,
532573
};
533574

@@ -665,4 +706,172 @@ mod tests {
665706
assert_eq!(round_robin.call_next(&calls), vec![1]);
666707
assert_eq!(round_robin.call_next(&calls), vec![1]);
667708
}
709+
710+
fn empty_ecdsa_payload() -> EcdsaPayload {
711+
EcdsaPayload {
712+
signature_agreements: BTreeMap::new(),
713+
available_quadruples: BTreeMap::new(),
714+
ongoing_signatures: BTreeMap::new(),
715+
quadruples_in_creation: BTreeMap::new(),
716+
uid_generator: EcdsaUIDGenerator::new(subnet_test_id(0), Height::new(0)),
717+
idkg_transcripts: BTreeMap::new(),
718+
ongoing_xnet_reshares: BTreeMap::new(),
719+
xnet_reshare_agreements: BTreeMap::new(),
720+
key_transcript: EcdsaKeyTranscript {
721+
current: None,
722+
next_in_creation: KeyTranscriptCreation::Begin,
723+
key_id: EcdsaKeyId::from_str("Secp256k1:some_key").unwrap(),
724+
},
725+
}
726+
}
727+
728+
fn fake_transcript(id: IDkgTranscriptId, registry_version: RegistryVersion) -> IDkgTranscript {
729+
IDkgTranscript {
730+
transcript_id: id,
731+
receivers: IDkgReceivers::new(BTreeSet::from_iter([node_test_id(0)])).unwrap(),
732+
registry_version,
733+
verified_dealings: Default::default(),
734+
transcript_type: IDkgTranscriptType::Unmasked(
735+
IDkgUnmaskedTranscriptOrigin::ReshareMasked(fake_transcript_id(0)),
736+
),
737+
algorithm_id: ic_types::crypto::AlgorithmId::EcdsaSecp256k1,
738+
internal_transcript_raw: vec![],
739+
}
740+
}
741+
742+
fn fake_transcript_id(id: u64) -> IDkgTranscriptId {
743+
IDkgTranscriptId::new(subnet_test_id(0), id, Height::from(0))
744+
}
745+
746+
// Create a fake quadruple, it will use transcripts with ids
747+
// id, id+1, id+2, and id+3.
748+
fn fake_quadruple(id: u64) -> PreSignatureQuadrupleRef {
749+
let temp_rv = RegistryVersion::from(0);
750+
let kappa_unmasked = fake_transcript(fake_transcript_id(id), temp_rv);
751+
let mut lambda_masked = kappa_unmasked.clone();
752+
lambda_masked.transcript_id = fake_transcript_id(id + 1);
753+
lambda_masked.transcript_type =
754+
IDkgTranscriptType::Masked(IDkgMaskedTranscriptOrigin::Random);
755+
let mut kappa_times_lambda = lambda_masked.clone();
756+
kappa_times_lambda.transcript_id = fake_transcript_id(id + 2);
757+
let mut key_times_lambda = lambda_masked.clone();
758+
key_times_lambda.transcript_id = fake_transcript_id(id + 3);
759+
let h = Height::from(0);
760+
PreSignatureQuadrupleRef {
761+
kappa_unmasked_ref: UnmaskedTranscript::try_from((h, &kappa_unmasked)).unwrap(),
762+
lambda_masked_ref: MaskedTranscript::try_from((h, &lambda_masked)).unwrap(),
763+
kappa_times_lambda_ref: MaskedTranscript::try_from((h, &kappa_times_lambda)).unwrap(),
764+
key_times_lambda_ref: MaskedTranscript::try_from((h, &key_times_lambda)).unwrap(),
765+
key_unmasked_ref: None,
766+
}
767+
}
768+
769+
fn fake_context(quadruple_id: Option<QuadrupleId>) -> SignWithEcdsaContext {
770+
SignWithEcdsaContext {
771+
request: RequestBuilder::new().build(),
772+
key_id: EcdsaKeyId::from_str("Secp256k1:some_key").unwrap(),
773+
message_hash: [0; 32],
774+
derivation_path: vec![],
775+
pseudo_random_id: [0; 32],
776+
matched_quadruple: quadruple_id.map(|qid| (qid, Height::from(0))),
777+
nonce: None,
778+
batch_time: mock_time(),
779+
}
780+
}
781+
782+
fn fake_state_with_contexts(contexts: Vec<SignWithEcdsaContext>) -> ReplicatedState {
783+
let mut state = ReplicatedStateBuilder::default().build();
784+
let iter = contexts
785+
.into_iter()
786+
.enumerate()
787+
.map(|(i, context)| (CallbackId::from(i as u64), context));
788+
state
789+
.metadata
790+
.subnet_call_context_manager
791+
.sign_with_ecdsa_contexts = BTreeMap::from_iter(iter);
792+
state
793+
}
794+
795+
// Create an ECDSA payload with 10 quadruples, each using registry version 2, 3 or 4.
796+
fn ecdsa_payload_with_quadruples() -> EcdsaPayload {
797+
let mut ecdsa = empty_ecdsa_payload();
798+
let key_id = ecdsa.key_transcript.key_id.clone();
799+
let mut rvs = [
800+
RegistryVersion::from(2),
801+
RegistryVersion::from(3),
802+
RegistryVersion::from(4),
803+
]
804+
.into_iter()
805+
.cycle();
806+
for i in (0..40).step_by(4) {
807+
let quadruple = fake_quadruple(i as u64);
808+
let rv = rvs.next().unwrap();
809+
for r in quadruple.get_refs() {
810+
ecdsa
811+
.idkg_transcripts
812+
.insert(r.transcript_id, fake_transcript(r.transcript_id, rv));
813+
}
814+
ecdsa
815+
.available_quadruples
816+
.insert(QuadrupleId(i as u64, Some(key_id.clone())), quadruple);
817+
}
818+
ecdsa
819+
}
820+
821+
#[test]
822+
fn test_empty_state_should_return_no_registry_version() {
823+
let ecdsa = ecdsa_payload_with_quadruples();
824+
let state = fake_state_with_contexts(vec![]);
825+
assert_eq!(
826+
None,
827+
get_oldest_ecdsa_state_registry_version(&ecdsa, &state)
828+
);
829+
}
830+
831+
#[test]
832+
fn test_state_without_matches_should_return_no_registry_version() {
833+
let ecdsa = ecdsa_payload_with_quadruples();
834+
let state = fake_state_with_contexts(vec![fake_context(None)]);
835+
assert_eq!(
836+
None,
837+
get_oldest_ecdsa_state_registry_version(&ecdsa, &state)
838+
);
839+
}
840+
841+
#[test]
842+
fn test_should_return_oldest_registry_version() {
843+
let ecdsa = ecdsa_payload_with_quadruples();
844+
// create contexts for all quadruples, but only create a match for
845+
// quadruples with registry version >= 3 (not 2!). Thus the oldest
846+
// registry version referenced by the state should be 3.
847+
let contexts = ecdsa
848+
.available_quadruples
849+
.iter()
850+
.map(|(id, quad)| {
851+
let t_id = quad.lambda_masked_ref.as_ref().transcript_id;
852+
let transcript = ecdsa.idkg_transcripts.get(&t_id).unwrap();
853+
(transcript.registry_version.get() >= 3).then_some(id.clone())
854+
})
855+
.map(fake_context)
856+
.collect();
857+
let state = fake_state_with_contexts(contexts);
858+
assert_eq!(
859+
Some(RegistryVersion::from(3)),
860+
get_oldest_ecdsa_state_registry_version(&ecdsa, &state)
861+
);
862+
863+
let mut ecdsa_without_transcripts = ecdsa.clone();
864+
ecdsa_without_transcripts.idkg_transcripts = BTreeMap::new();
865+
assert_eq!(
866+
None,
867+
get_oldest_ecdsa_state_registry_version(&ecdsa_without_transcripts, &state)
868+
);
869+
870+
let mut ecdsa_without_quadruples = ecdsa.clone();
871+
ecdsa_without_quadruples.available_quadruples = BTreeMap::new();
872+
assert_eq!(
873+
None,
874+
get_oldest_ecdsa_state_registry_version(&ecdsa_without_quadruples, &state)
875+
);
876+
}
668877
}

0 commit comments

Comments
 (0)