diff --git a/tinycloud-threshold/README.md b/tinycloud-threshold/README.md index 292bc51a..64526814 100644 --- a/tinycloud-threshold/README.md +++ b/tinycloud-threshold/README.md @@ -4,7 +4,7 @@ BLS12-381 identity-based wrapping of 32-byte data keys and context-bound encrypt The crate is a workspace member with `publish = false`. No other workspace crate depends on it; the node server remains the default member. Release-plz neither releases nor publishes it. -## Implemented surface through S0.2 +## Implemented surface through S0.3 - Bounded, canonical scalar, point, identity, ciphertext, request and share encodings. - Boneh–Franklin FullIdent wrapping and unwrapping with the TinyCloud domains in [SPEC.md](SPEC.md). @@ -14,8 +14,10 @@ The crate is a workspace member with `publish = false`. No other workspace crate - Owner-authorized ceremony envelopes and closed rounds with signed recipient/dealer cohorts and retained authenticated payload-decode outcomes. - Externally anchored certificates, pinned succession and certificate-bound candidate shares. - Signed response fault classification, readiness receipts and all-holder or explicit outage-budget activation verification. +- Ceremony-scoped RFC 9180 sealed dealings. +- Genesis DKG (GJKR New-DKG) over owner-closed rounds: consume-self member states for dealing, complaints, answers, Feldman extraction, the common R5 round and R6 reconstruction, and `dkg_verify_transcript`, which recomputes the outcome from the raw signed round records. -`VerifiedCertificate` supplies `KeyContext` and `PublicKeySet`; `KeyShare::bind` validates a candidate against its certificate. Raw candidate-scalar construction remains crate-private, and the dealer exists only under `#[cfg(test)]`. DKG/redistribution rounds, HPKE dealings, durable storage and node-runtime paths remain separate slices. Their contracts and the implemented canonical byte layouts are recorded in [SPEC.md](SPEC.md). +`VerifiedCertificate` supplies `KeyContext` and `PublicKeySet`; `KeyShare::bind` validates a candidate against its certificate. Raw candidate-scalar construction remains crate-private, and the dealer exists only under `#[cfg(test)]`. Redistribution rounds, durable storage and node-runtime paths remain separate slices. Their contracts and the implemented canonical byte layouts are recorded in [SPEC.md](SPEC.md). The §3.6 request-digest amendment is explicit in SPEC.md. It makes an identity or transport-key transplant a context mismatch before share equations are evaluated. @@ -37,7 +39,7 @@ cargo deny --manifest-path tinycloud-threshold/Cargo.toml --locked --config tiny The `threshold` job in `.github/workflows/rust.yml` runs the full release suite, the explicitly selected cargo-deny policy and the independent primitive checker. Workspace test, build, formatting and clippy jobs also include this member. -The checkpoint prints nine executed outcomes. Required RFC tests are separately named `rfc9380_appendix_j_g1_sswu_ro`, `rfc9380_appendix_j_g2_sswu_ro` and `rfc9380_appendix_k1_expand_xmd_sha256`. +The `checkpoint_2of3` filter runs two checkpoints: the test-dealer checkpoint prints nine executed outcomes, and `checkpoint_2of3_dkg` prints five outcomes for a signed 2-of-3 DKG with one reconstructed dealer, its certificate binding and threshold derivation. Required RFC tests are separately named `rfc9380_appendix_j_g1_sswu_ro`, `rfc9380_appendix_j_g2_sswu_ro` and `rfc9380_appendix_k1_expand_xmd_sha256`. ## Vectors @@ -51,6 +53,12 @@ TC_THRESHOLD_REGEN_VECTORS=1 cargo test -p tinycloud-threshold primitive_vectors `vectors/v1/cert-core.json` contains 18 certificate, lineage, share-binding, fault and activation cases. The certificate vector test replays these cases through the verification APIs with real test signatures. +`vectors/v1/dkg-genesis.json` contains six genesis DKG transcripts from fixed seeds: the signed ceremony statement, the initial transcript link, every owner closure and member message, and either the recomputed QUAL, disqualification reasons, reconstructed dealers, commitments, public shares and transcript hash, or the expected error. It lists the Ed25519 test keys for its DIDs. `dkg_vectors_regenerate_byte_for_byte_and_replay` regenerates the file in memory, compares its complete bytes and replays every case through `dkg_verify_transcript`. To update it deliberately: + +```sh +TC_THRESHOLD_REGEN_VECTORS=1 cargo test -p tinycloud-threshold dkg_vectors_regenerate_byte_for_byte_and_replay +``` + ## Dependencies and licence This crate uses the workspace's [TinyCloud EGPL v1.5](../LICENSE.md) through `license-file`. Production dependencies: `blst` (Apache-2.0), `sha2` (MIT OR Apache-2.0), `zeroize` (Apache-2.0 OR MIT), `subtle` (BSD-3-Clause), `rand_core` (MIT OR Apache-2.0), and `thiserror` (MIT OR Apache-2.0). Development dependencies: `proptest`, `rand_chacha`, `serde`, `serde_json`, and `hex` (MIT OR Apache-2.0). diff --git a/tinycloud-threshold/SPEC.md b/tinycloud-threshold/SPEC.md index 5f38e99f..1fa5b5ac 100644 --- a/tinycloud-threshold/SPEC.md +++ b/tinycloud-threshold/SPEC.md @@ -220,7 +220,7 @@ All integer fields below are big-endian. `lp` is u32 length followed by bytes. - No S0.1 public decoder consumes a member list; lists introduced in later slices use the §3.2 sorted/count rules. - An availability budget counts all unavailable ready signers, including dishonest claims: `q - b >= t′`. All four ready holders in 3-of-4 support one unavailable holder, not two. `WithOutageBudget(0)` is not accepted. -## Implemented surface through S0.2 +## Implemented surface through S0.3 S0.1 implements §§3.1–3.6 and the typed parameters in §3.0. S0.2 adds the authenticated-context boundaries in §3.0, the certificate and fault-proof APIs in §3.9, and the receipt/activation verification in §3.12: @@ -231,7 +231,9 @@ S0.1 implements §§3.1–3.6 and the typed parameters in §3.0. S0.2 adds the a - Fault verification checks certificate context, request/share binding and the member signature before classifying a failed share equation. - Readiness and activation APIs construct the defined preimages and verify every supplied receipt signature before quorum evaluation, receipt hashing or sorting. -The dealer remains test-only. DKG and redistribution round implementations in §§3.7–3.8, HPKE dealings in §3.10, durable storage/retirement and node-runtime integration are separate slices. The crate remains an unwired workspace member. +S0.3 adds the §3.10 sealed dealings with ceremony-scoped recipient keys and the §3.7 genesis DKG: consume-self member states over owner-closed rounds R1–R6, and `dkg_verify_transcript`, which recomputes the outcome from the raw signed round records. Its byte layouts and deterministic rules are in the S0.3 genesis DKG appendix below. + +The dealer remains test-only. Redistribution rounds in §3.8, durable storage/retirement and node-runtime integration are separate slices. The crate remains an unwired workspace member. ## S0.2 canonical byte appendix @@ -352,3 +354,87 @@ activation.bytes = TH(ACTIVATE, The owner signs exactly those 32 activation bytes. Unlike ceremony/closure statements, activation `SignedStatement.bytes` already contains the domain-separated signing preimage rather than a raw body. The default quorum is all `n` holders. `WithOutageBudget(b)` requires `b > 0` and checked `q = t+b <= n`; at least `q` unique verified members must be ready. The budget includes all unavailable ready signers. Receipts are signed installation statements; durable storage, monotonic activation state and retirement remain integration operations. + +## S0.3 genesis DKG appendix + +This appendix records the byte layouts and deterministic rules that members and `dkg_verify_transcript` apply to §3.7. Integers are unsigned big-endian; `lp`, `TH`, point and scalar encodings are as in §3.2 and the S0.2 appendix. Rounds are numbered R1 = 1 through R6 = 6. Every DKG message uses the S0.2 member envelope with cohort `1` (authorized recipients); genesis has no dealer cohort. + +### Initial link and transcript + +```text +H0 = TH("TC-THRESHOLD-V1-DKG-START", authorized_ceremony_digest:32) +``` + +`dkg_initial_transcript(c)` returns `H0`. The tag is specific to this link and is not part of the §3.3 table; it is distinct from, and not a prefix of, every §3.3 tag. Closed R1 names `H0` as its previous hash; each later round names the S0.2 transcript update of the round before it. The DKG transcript hash is the transcript update after closed R5, or after closed R6 when R6 is required. + +Each member transition first requires the closed round's authorized-ceremony digest, round number and previous hash to equal the values the consuming state holds; otherwise it returns `InvalidRound` before reading any message. + +`DkgTranscript` is the list of raw round records: the exact owner closure (body and signature) and the exact member statements in owner order. `dkg_verify_transcript` first sums the byte lengths of every closure body, closure signature, member envelope and member signature and returns `LimitExceeded` above 4194304 bytes, before any signature check. It then verifies R1–R5 as closed rounds, verifies R6 exactly when the R5 rules require it, and rejects missing or additional records with `InvalidRound`. It derives the outcome from the authenticated records only. + +### Pedersen generator and dealing context + +`h = hash_to_curve_G2("pedersen-h", PEDERSEN_H DST)` as in §3.3. The commitment-vector hash used as HPKE AAD is: + +```text +TH(COMMITMENTS, t:u16 || C_0:96 || … || C_(t−1):96) +``` + +HPKE `info` is `DEALING || authorized_ceremony_digest:32 || 01 || dealer:u16 || recipient:u16`. The sealed plaintext is exactly `s_ij:32 || s′_ij:32`, both canonical scalars. + +Dealers draw randomness in this order: the t coefficients of f_i (constant first), then the t coefficients of f′_i, each as 64 bytes reduced mod r and redrawn if zero; then one HPKE encapsulation per recipient in index order. + +### Payloads + +```text +R1 deal = 01 || t:u16 || [C_k:96] || n:u16 || [(recipient:u16 || lp(sealed_dealing))] +R2 complaints = 01 || count:u16 || [dealer:u16] +R3 answers = 01 || count:u16 || [(recipient:u16 || s:32 || s′:32)] +R4 feldman = 01 || t:u16 || [A_k:96] +R5 extraction = 01 || count:u16 || [(dealer:u16 || s:32 || s′:32)] +R6 values = 01 || count:u16 || [(dealer:u16 || s:32 || s′:32)] +``` + +`sealed_dealing` is the §3.10 envelope `01 || enc:32 || lp(ciphertext)`. Every count is at most 16 and is checked before allocation; index lists are strictly increasing member indices 1..=16; point coefficients are canonical G2 encodings and individual coefficients may be the identity (§3.2 role table); scalars are canonical. Trailing bytes and other versions are rejected. A payload that fails this decoding is *malformed*. + +### Classifying a round + +Within a closed round, authenticated messages are grouped by sender. Messages with byte-identical envelopes are one message regardless of their signature bytes. Two messages from one sender with different envelope bytes are *conflicting*; this compares the signed content, including malformed payloads. A sender with no message is *missing*. + +### R1–R3 and QUAL + +For each member index i, in this order, the first applicable reason disqualifies dealer i: + +1. `MissingDeal`: no R1 from i. +2. `Equivocated { round: 1 }`: conflicting R1. +3. `MalformedDeal`: R1 is malformed, has a commitment count other than t, or its recipient list is not exactly 1..=n. +4. `Equivocated { round: 2 }`: conflicting R2 from i. +5. `Equivocated { round: 3 }`: conflicting R3 from i. +6. `Complaints`: at least t distinct complainers counted against i (below). +7. `InvalidAnswer`: i's decoded R3 contains any answer that fails `s·g2 + s′·h = Σ_k j^k·C_ik` at its recipient j, whether or not that answer was required. +8. `MissingAnswer`: a required answer is absent: i's R3 is missing or malformed, or omits a required recipient. + +R2 counting: a complaint by j against i counts once per distinct j, only from a non-conflicting, decodable R2 whose dealer indices are all at most n, and never when j = i. A malformed R2 supplies no complaints; a conflicting R2 supplies no complaints and disqualifies its sender as a dealer under rule 4. The required answers for dealer i are exactly the counted complainers against i. + +R3 rules: an R3 is used only if it decodes and all recipient indices are at most n; otherwise it is malformed. A missing or malformed R3 disqualifies i only when i has required answers. A valid answer that was not required is ignored and creates no complaint. A valid required answer replaces the complainer's private pair from i. + +QUAL is every dealer not disqualified, fixed after closed R3. If |QUAL| < t the ceremony returns `InsufficientQualified`. A member that lacks a Pedersen-valid pair from some QUAL dealer after R3 returns `LocalShareUnavailable`; this only arises when a member's own complaint is absent from closed R2. + +Members complain in R2 about every dealer whose valid R1 dealing to them fails to open, decode or satisfy the Pedersen relation. A dealer answers in R3 every counted complaint against it when its own R1 was accepted. Every member sends R2 and R3, possibly empty. + +### R4–R6 and output + +For each i in QUAL, an *extraction fault* is a missing, conflicting or malformed R4, or an R4 whose coefficient count is not t. R4 from a sender outside QUAL is ignored. + +Every member sends R5, possibly empty, listing each other QUAL dealer i whose valid R4 fails `s_ij·g2 = Σ_k j^k·A_ik` for its own pair. An R5 entry by sender j against dealer i is *upheld* when i ≠ j, i is in QUAL without an extraction fault, the pair satisfies the Pedersen relation for C_i at j, and the Feldman relation for A_i at j fails. Other entries are ignored. Missing, malformed and conflicting R5 messages count as empty lists. No member finishes before consuming closed R5. + +The *reconstructed* set is the extraction faults plus every dealer with an upheld R5 entry. If it is empty the ceremony finishes after R5. Otherwise every member sends R6 with its Pedersen-valid pair for each reconstructed dealer, and the ceremony finishes after R6. For each reconstructed dealer i, the values used are those from non-conflicting, decodable R6 messages whose entry for i satisfies the Pedersen relation at the sender index; the t lowest sender indices are interpolated to the coefficients of f_i, and A_ik = a_ik·g2 replaces i's published vector. Dealer i stays in QUAL. Fewer than t such values return `InsufficientRecovery`. Missing, malformed and conflicting R6 messages contribute no values. + +Output: A_k = Σ_{i∈QUAL} A_ik; mpk = A_0; pk_j = Σ_k j^k·A_k; identity mpk, A_(t−1) or pk_j is rejected (§3.2). The member's candidate scalar is Σ_{i∈QUAL} s_ij using R3 replacements. `DkgOutput` carries the public material, QUAL, the disqualification reasons, the reconstructed set and the transcript hash. + +### Scope + +Agreement among members and the verifier relies on the §3.0 owner relay closing each round once and delivering every conforming member's message. Round equivocation compares conflicting authenticated message content from the same sender; a different signature encoding alone is not equivocation. A relay that omits or substitutes messages produces the failure outcomes above, not agreement; the protocol does not implement reliable broadcast. + +### In-memory lifecycle + +`CeremonyHpkeSecretKey` is dropped when R1 has been processed. Polynomial coefficients are dropped when R4 has been produced. Received private pairs remain in the state until the member finishes after R5 or R6. States are consumed by each transition, so an error drops the state; scalars clear their storage on drop and states have redacted `Debug`. The caller's RNG state and any durable copies are outside this crate (§3.11). diff --git a/tinycloud-threshold/src/checkpoint.rs b/tinycloud-threshold/src/checkpoint.rs index 1d68e390..f4b31e48 100644 --- a/tinycloud-threshold/src/checkpoint.rs +++ b/tinycloud-threshold/src/checkpoint.rs @@ -680,3 +680,104 @@ proptest! { if let Ok(v)=IbeCiphertext::decode(&c) { prop_assert_eq!(v.encode(),c); } } } + +/// Same checkpoint name filter, driven by real signed DKG candidates. Details: TC-2220 +#[test] +fn checkpoint_2of3_dkg() { + use crate::ceremony::Decode; + use crate::dkg::{DkgFeldman, DkgOutput}; + use crate::dkg_relay::{agreed, members, run, sum, vanishing_feldman, Run}; + + let mut constants = Vec::new(); + let dkg = run(2, 3, 0x71, |out| { + if out.round() == 4 { + constants = (1..=3) + .map(|i| DkgFeldman::decode(&out.payload(i)).unwrap().commitments[0].encode()) + .collect(); + let forged = vanishing_feldman(&out.payload(3), &[1]); + out.replace(3, &forged); + } + }); + let output: DkgOutput = agreed(&dkg, &[1, 2, 3]); + assert_eq!(dkg.transcript.rounds.len(), 6); + println!( + "1. PASS: signed 2-of-3 genesis DKG closed R1-R6; members 1, 2, 3 and dkg_verify_transcript agree on QUAL, commitments, mpk and transcript hash {}", + hex::encode(output.transcript_hash.as_bytes()) + ); + assert!(dkg.reconstruct.values().all(|r| *r)); + assert_eq!(output.qual, members(&[1, 2, 3])); + assert_eq!(output.reconstructed, members(&[3])); + assert!(output.disqualified.is_empty()); + println!("2. PASS: dealer 3's R4 passed only member 1; every member waited for the common closed R5, and R6 recovered dealer 3, which stayed in QUAL"); + assert_eq!( + output.public.master_public_key().encode(), + sum(constants.iter().copied()) + ); + println!("3. PASS: mpk equals the sum of all three dealers' committed constants, including dealer 3's recovered contribution"); + + let Run { + fixture, outcomes, .. + } = dkg; + let anchor = fixture.signers.anchor(); + let body = CertificateBody::genesis( + &anchor, + &fixture.ceremony, + &output.public, + &output.transcript_hash, + ) + .unwrap(); + let cert = verify_certificate( + &anchor, + &crate::cert::tests::sign(body, &fixture.signers), + &fixture.signers, + ) + .unwrap(); + let shares: Vec = outcomes + .into_values() + .map(|outcome| KeyShare::bind(outcome.unwrap().0, &cert).unwrap()) + .collect(); + assert_eq!(shares.len(), 3); + println!("4. PASS: owner-signed genesis certificate verified under the anchor; all three DKG candidate shares bound to it"); + + let mut rng = ChaCha20Rng::from_seed([0x72; 32]); + let tsk = TransportSecretKey::generate(&mut rng); + let req = DecryptRequest::new( + &cert.key_context(), + identity(&cert.key_context(), "records/dkg"), + tsk.public_key(), + ) + .unwrap(); + let auth = authorize(req.clone()); + let issued: Vec<_> = shares + .iter() + .map(|s| issue_share(s, &auth, &mut rng).unwrap()) + .collect(); + for share in &issued { + verify_share(cert.public_key_set(), &req, share).unwrap(); + } + let data = DataKey::new([0x24; 32]); + let ct = wrap_key( + cert.public_key_set().master_public_key(), + req.identity(), + &data, + &mut rng, + ) + .unwrap(); + let mut keys = Vec::new(); + for (a, b) in [(0, 1), (0, 2), (1, 2)] { + let encrypted = combine( + cert.public_key_set(), + &req, + &[issued[a].clone(), issued[b].clone()], + ) + .unwrap(); + let key = decrypt_derived_key(&encrypted, &tsk, cert.public_key_set(), &req).unwrap(); + assert_eq!( + unwrap_key(&key, req.identity(), &ct).unwrap().as_bytes(), + data.as_bytes() + ); + keys.push(key.key.encode()); + } + assert!(keys.windows(2).all(|p| p[0] == p[1])); + println!("5. PASS: each DKG share verified; pairs {{1,2}}, {{1,3}}, {{2,3}} derived the same key and unwrapped the data key"); +} diff --git a/tinycloud-threshold/src/curve.rs b/tinycloud-threshold/src/curve.rs index 0c35e726..36c91954 100644 --- a/tinycloud-threshold/src/curve.rs +++ b/tinycloud-threshold/src/curve.rs @@ -98,7 +98,6 @@ macro_rules! scalar_binary { } impl Scalar { - #[cfg(test)] pub(crate) fn from_bytes(bytes: &[u8]) -> Result { let bytes: &[u8; 32] = bytes.try_into().map_err(|_| Error::InvalidLength)?; let mut out = Self(blst_scalar::default()); @@ -113,7 +112,6 @@ impl Scalar { } /// The returned secret bytes must be guarded by their caller. - #[cfg(test)] pub(crate) fn to_bytes(&self) -> [u8; 32] { let mut out = [0; 32]; // SAFETY: exactly 32 writable bytes; canonical initialized scalar. @@ -162,7 +160,6 @@ impl Scalar { bool::from(self.0.b.ct_eq(&[0; 32])) } - #[cfg(test)] scalar_binary!(add, blst_fr_add); scalar_binary!(sub, blst_fr_sub); scalar_binary!(mul, blst_fr_mul); @@ -379,7 +376,6 @@ pub(crate) fn hash_g1(msg: &[u8], dst: &[u8]) -> G1 { } out } -#[cfg(test)] pub(crate) fn hash_g2(msg: &[u8], dst: &[u8]) -> G2 { let mut out = G2::identity(); // SAFETY: readable pointer-length pairs, no augmentation, initialized output. diff --git a/tinycloud-threshold/src/dkg.rs b/tinycloud-threshold/src/dkg.rs new file mode 100644 index 00000000..5440fae0 --- /dev/null +++ b/tinycloud-threshold/src/dkg.rs @@ -0,0 +1,1542 @@ +//! Genesis GJKR New-DKG over owner-closed rounds. Details: TC-2220 +//! +//! Members consume owner-closed rounds in a fixed order through consume-self +//! states; `dkg_verify_transcript` applies the same public rules to the +//! authenticated round records. Liveness under the genesis budget assumes the +//! trusted owner relay delivers every conforming member's message. + +use core::fmt; +use std::collections::{btree_map::Entry, BTreeMap, BTreeSet}; + +use rand_core::CryptoRngCore; +use zeroize::Zeroizing; + +use crate::{ + ceremony::{ + ceremony_message_body, AuthorizedCeremony, CeremonyKind, ClosedRound, Decode, + DidSignatureVerifier, MemberCohort, RoundMessage, SignedStatement, + }, + cert::{CandidateShare, PublicMaterial}, + curve::{hash_g2, Scalar, G2}, + dst, + encoding::{Decoder, Encoder}, + error::Error, + ids::{MemberIndex, Threshold, TranscriptHash}, + poly, + seal::{ + open_dealing, seal_dealing, CeremonyHpkePublicKey, CeremonyHpkeSecretKey, DealingContext, + SealedDealing, + }, +}; + +/// Initial transcript link tag; not part of the frozen S0.1 domain table. Details: TC-2220 +pub(crate) const DKG_START: &[u8] = b"TC-THRESHOLD-V1-DKG-START"; +const PEDERSEN_H_MESSAGE: &[u8] = b"pedersen-h"; +const VERSION: u8 = 1; +const MAX_ENTRIES: u16 = 16; +const MESSAGE_CAP: usize = 65_536; +const TRANSCRIPT_CAP: usize = 4_194_304; +const PLAINTEXT_LEN: usize = 64; + +const DEAL: u8 = 1; +const COMPLAIN: u8 = 2; +const ANSWER: u8 = 3; +const EXTRACT: u8 = 4; +const EXTRACTION_COMPLAINTS: u8 = 5; +const RECONSTRUCT: u8 = 6; + +/// `TH(DKG_START, authorized_ceremony_digest)`, the link preceding closed R1. Details: TC-2220 +pub fn dkg_initial_transcript(c: &AuthorizedCeremony) -> TranscriptHash { + TranscriptHash::new(dst::tagged_hash(DKG_START, &c.digest())) +} + +// ------------------------------------------------------------------ payloads + +/// A scalar value and its Pedersen blinding value at one member index. Details: TC-2220 +pub(crate) struct SharePair { + pub(crate) value: Scalar, + pub(crate) blinding: Scalar, +} + +impl SharePair { + fn duplicate(&self) -> Self { + let zero = Scalar::from_u64(0); + Self { + value: self.value.add(&zero), + blinding: self.blinding.add(&zero), + } + } + + fn encode_into(&self, e: &mut Encoder) { + e.fixed(Zeroizing::new(self.value.to_bytes()).as_slice()); + e.fixed(Zeroizing::new(self.blinding.to_bytes()).as_slice()); + } + + fn decode_from(d: &mut Decoder) -> Result { + let value = Zeroizing::new(d.fixed::<32>()?); + let blinding = Zeroizing::new(d.fixed::<32>()?); + Ok(Self { + value: Scalar::from_bytes(value.as_slice())?, + blinding: Scalar::from_bytes(blinding.as_slice())?, + }) + } +} + +impl fmt::Debug for SharePair { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("SharePair([REDACTED])") + } +} + +fn version(d: &mut Decoder) -> Result<(), Error> { + if d.u8()? != VERSION { + return Err(Error::InvalidEncoding); + } + Ok(()) +} + +fn count(d: &mut Decoder) -> Result { + let count = d.u16()?; + if count > MAX_ENTRIES { + return Err(Error::LimitExceeded); + } + Ok(usize::from(count)) +} + +fn index_after(d: &mut Decoder, previous: &mut Option) -> Result { + let index = MemberIndex::new(d.u16()?)?; + if previous.is_some_and(|p| index <= p) { + return Err(Error::InvalidEncoding); + } + *previous = Some(index); + Ok(index) +} + +fn encode_points(e: &mut Encoder, points: &[G2]) { + e.u16(points.len() as u16); + for point in points { + e.fixed(&point.encode()); + } +} + +fn decode_points(d: &mut Decoder) -> Result, Error> { + let count = count(d)?; + let mut points = Vec::with_capacity(count); + for _ in 0..count { + points.push(G2::decode(&d.fixed::<96>()?, true)?); + } + Ok(points) +} + +fn encode_pairs(entries: &[(MemberIndex, SharePair)]) -> Vec { + let mut e = Encoder::new(); + e.u8(VERSION); + e.u16(entries.len() as u16); + for (index, pair) in entries { + e.u16(index.get()); + pair.encode_into(&mut e); + } + e.finish() +} + +fn decode_pairs(bytes: &[u8]) -> Result, Error> { + let mut d = Decoder::new(bytes)?; + version(&mut d)?; + let count = count(&mut d)?; + let mut entries = Vec::with_capacity(count); + let mut previous = None; + for _ in 0..count { + let index = index_after(&mut d, &mut previous)?; + entries.push((index, SharePair::decode_from(&mut d)?)); + } + d.finish()?; + Ok(entries) +} + +/// R1: Pedersen commitments and one sealed dealing per recipient. Details: TC-2220 +#[derive(Debug)] +pub struct DkgDeal { + pub(crate) commitments: Vec, + pub(crate) dealings: Vec<(MemberIndex, SealedDealing)>, +} + +impl DkgDeal { + /// `01 || t:u16 || C_k:96… || n:u16 || [(recipient:u16 || lp(sealed))]`. Details: TC-2220 + pub fn encode(&self) -> Vec { + let mut e = Encoder::new(); + e.u8(VERSION); + encode_points(&mut e, &self.commitments); + e.u16(self.dealings.len() as u16); + for (index, dealing) in &self.dealings { + e.u16(index.get()); + e.lp(&dealing.encode()); + } + e.finish() + } +} + +impl Decode for DkgDeal { + fn decode(bytes: &[u8]) -> Result { + let mut d = Decoder::new(bytes)?; + version(&mut d)?; + let commitments = decode_points(&mut d)?; + let count = count(&mut d)?; + let mut dealings = Vec::with_capacity(count); + let mut previous = None; + for _ in 0..count { + let index = index_after(&mut d, &mut previous)?; + dealings.push((index, SealedDealing::decode(d.lp(MESSAGE_CAP)?)?)); + } + d.finish()?; + Ok(Self { + commitments, + dealings, + }) + } +} + +/// R2: dealers whose dealing failed for the sender. Details: TC-2220 +#[derive(Debug)] +pub struct DkgComplaints { + pub(crate) dealers: Vec, +} + +impl DkgComplaints { + /// `01 || count:u16 || [dealer:u16]`, strictly increasing. Details: TC-2220 + pub fn encode(&self) -> Vec { + let mut e = Encoder::new(); + e.u8(VERSION); + e.u16(self.dealers.len() as u16); + for dealer in &self.dealers { + e.u16(dealer.get()); + } + e.finish() + } +} + +impl Decode for DkgComplaints { + fn decode(bytes: &[u8]) -> Result { + let mut d = Decoder::new(bytes)?; + version(&mut d)?; + let count = count(&mut d)?; + let mut dealers = Vec::with_capacity(count); + let mut previous = None; + for _ in 0..count { + dealers.push(index_after(&mut d, &mut previous)?); + } + d.finish()?; + Ok(Self { dealers }) + } +} + +/// R3: a dealer's published pairs for complaining recipients. Details: TC-2220 +#[derive(Debug)] +pub struct DkgAnswers { + pub(crate) answers: Vec<(MemberIndex, SharePair)>, +} + +impl DkgAnswers { + /// `01 || count:u16 || [(recipient:u16 || value:32 || blinding:32)]`. Details: TC-2220 + pub fn encode(&self) -> Vec { + encode_pairs(&self.answers) + } +} + +impl Decode for DkgAnswers { + fn decode(bytes: &[u8]) -> Result { + Ok(Self { + answers: decode_pairs(bytes)?, + }) + } +} + +/// R4: a qualified dealer's Feldman coefficients. Details: TC-2220 +#[derive(Debug)] +pub struct DkgFeldman { + pub(crate) commitments: Vec, +} + +impl DkgFeldman { + /// `01 || t:u16 || A_k:96…`. Details: TC-2220 + pub fn encode(&self) -> Vec { + let mut e = Encoder::new(); + e.u8(VERSION); + encode_points(&mut e, &self.commitments); + e.finish() + } +} + +impl Decode for DkgFeldman { + fn decode(bytes: &[u8]) -> Result { + let mut d = Decoder::new(bytes)?; + version(&mut d)?; + let commitments = decode_points(&mut d)?; + d.finish()?; + Ok(Self { commitments }) + } +} + +/// R5: the sender's Pedersen-valid, Feldman-invalid pairs by dealer. Details: TC-2220 +#[derive(Debug)] +pub struct DkgExtractionComplaints { + pub(crate) complaints: Vec<(MemberIndex, SharePair)>, +} + +impl DkgExtractionComplaints { + /// Same layout as R3, keyed by dealer. Details: TC-2220 + pub fn encode(&self) -> Vec { + encode_pairs(&self.complaints) + } +} + +impl Decode for DkgExtractionComplaints { + fn decode(bytes: &[u8]) -> Result { + Ok(Self { + complaints: decode_pairs(bytes)?, + }) + } +} + +/// R6: the sender's pairs for every reconstructed dealer. Details: TC-2220 +#[derive(Debug)] +pub struct DkgReconstruction { + pub(crate) values: Vec<(MemberIndex, SharePair)>, +} + +impl DkgReconstruction { + /// Same layout as R3, keyed by dealer. Details: TC-2220 + pub fn encode(&self) -> Vec { + encode_pairs(&self.values) + } +} + +impl Decode for DkgReconstruction { + fn decode(bytes: &[u8]) -> Result { + Ok(Self { + values: decode_pairs(bytes)?, + }) + } +} + +/// An unsigned member envelope for one round; the node signs `signing_preimage`. Details: TC-2220 +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DkgMessage { + round: u8, + body: Vec, +} + +impl DkgMessage { + fn new( + c: &AuthorizedCeremony, + round: u8, + me: MemberIndex, + payload: &[u8], + ) -> Result { + Ok(Self { + round, + body: ceremony_message_body(c, round, MemberCohort::Recipients, me, payload)?, + }) + } + /// Round number of this envelope. Details: TC-2220 + pub fn round(&self) -> u8 { + self.round + } + /// Exact canonical member envelope bytes. Details: TC-2220 + pub fn body(&self) -> &[u8] { + &self.body + } + /// `TH(CEREMONY_MSG, body)`, signed with the member's node key. Details: TC-2220 + pub fn signing_preimage(&self) -> Vec { + dst::tagged_hash(dst::CEREMONY_MSG, &self.body).to_vec() + } +} + +// --------------------------------------------------------------- outputs + +/// Why a dealer is outside QUAL. Details: TC-2220 +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DisqualifyReason { + /// No R1 message in closed R1. Details: TC-2220 + MissingDeal, + /// R1 failed to decode or had the wrong commitment count or recipient set. Details: TC-2220 + MalformedDeal, + /// Conflicting authenticated content in R1, R2 or R3. Details: TC-2220 + Equivocated { + /// Round of the conflicting messages. Details: TC-2220 + round: u8, + }, + /// At least t distinct recipients complained. Details: TC-2220 + Complaints, + /// A required answer is absent from closed R3. Details: TC-2220 + MissingAnswer, + /// A published answer fails the Pedersen relation. Details: TC-2220 + InvalidAnswer, +} + +/// Public outcome shared by members and the transcript verifier. Details: TC-2220 +#[derive(Clone, Debug)] +pub struct DkgOutput { + /// Summed Feldman polynomial, master key and public shares. Details: TC-2220 + pub public: PublicMaterial, + /// Qualified dealers fixed after closed R3. Details: TC-2220 + pub qual: BTreeSet, + /// Dealers outside QUAL with the first applicable reason. Details: TC-2220 + pub disqualified: BTreeMap, + /// QUAL dealers whose Feldman coefficients were recovered from R6. Details: TC-2220 + pub reconstructed: BTreeSet, + /// Transcript link after the last closed round. Details: TC-2220 + pub transcript_hash: TranscriptHash, +} + +/// One owner closure and its exact ordered member statements. Details: TC-2220 +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DkgRoundRecord { + /// Signed owner closure. Details: TC-2220 + pub closure: SignedStatement, + /// Member statements in owner order. Details: TC-2220 + pub messages: Vec, +} + +/// Raw signed rounds R1..R5, plus R6 when reconstruction is required. Details: TC-2220 +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DkgTranscript { + /// Round records in order. Details: TC-2220 + pub rounds: Vec, +} + +// ------------------------------------------------------------ public rules + +enum Sent<'a, M> { + One(&'a RoundMessage), + Conflicting, +} +impl Clone for Sent<'_, M> { + fn clone(&self) -> Self { + *self + } +} +impl Copy for Sent<'_, M> {} + +/// Group authenticated messages by sender; identical envelope bytes are one message. +fn by_sender(round: &ClosedRound) -> BTreeMap> { + let mut senders = BTreeMap::new(); + for message in round.messages() { + match senders.entry(message.member()) { + Entry::Vacant(slot) => { + slot.insert(Sent::One(message)); + } + Entry::Occupied(mut slot) => { + if let Sent::One(first) = *slot.get() { + if first.statement().bytes != message.statement().bytes { + slot.insert(Sent::Conflicting); + } + } + } + } + } + senders +} + +fn decoded<'a, M>(sent: Option>) -> Option<&'a M> { + match sent { + Some(Sent::One(message)) => message.payload().as_ref().ok(), + _ => None, + } +} + +fn evaluate_points(points: &[G2], x: MemberIndex) -> G2 { + let x = Scalar::from_u64(u64::from(x.get())); + points + .iter() + .rev() + .fold(G2::identity(), |acc, point| acc.mul(&x).add(point)) +} + +fn pedersen_h() -> G2 { + hash_g2(PEDERSEN_H_MESSAGE, dst::PEDERSEN_H) +} + +fn commitment_hash(points: &[G2]) -> [u8; 32] { + let mut e = Encoder::new(); + encode_points(&mut e, points); + dst::tagged_hash(dst::COMMITMENTS, &e.finish()) +} + +fn times_linear(polynomial: &[Scalar], root: &Scalar) -> Vec { + let zero = Scalar::from_u64(0); + let below = core::iter::once(&zero).chain(polynomial.iter()); + let at = polynomial.iter().map(Some).chain(core::iter::once(None)); + below + .zip(at) + .map(|(below, at)| match at { + Some(at) => below.sub(&at.mul(root)), + None => below.add(&zero), + }) + .collect() +} + +/// Coefficients of the unique polynomial through the supplied distinct points. +fn interpolate(points: &[(Scalar, &Scalar)]) -> Result, Error> { + let mut coefficients: Vec = points.iter().map(|_| Scalar::from_u64(0)).collect(); + for (m, (xm, ym)) in points.iter().enumerate() { + let mut basis = vec![Scalar::from_u64(1)]; + let mut denominator = Scalar::from_u64(1); + for (l, (xl, _)) in points.iter().enumerate() { + if l != m { + basis = times_linear(&basis, xl); + denominator = denominator.mul(&xm.sub(xl)); + } + } + let scale = ym.mul(&denominator.inverse()?); + for (coefficient, b) in coefficients.iter_mut().zip(&basis) { + *coefficient = coefficient.add(&b.mul(&scale)); + } + } + Ok(coefficients) +} + +/// Public ceremony state; members and the verifier apply identical rules. +struct Public { + ceremony: AuthorizedCeremony, + h: G2, + previous: TranscriptHash, + /// Valid R1 commitments of dealers not yet disqualified; QUAL after R3. + commitments: BTreeMap>, + disqualified: BTreeMap, + complaints: BTreeMap>, + equivocated: BTreeSet, + feldman: BTreeMap>, + faulted: BTreeSet, +} + +impl Public { + fn new(c: &AuthorizedCeremony) -> Result { + if c.kind() != CeremonyKind::Genesis { + return Err(Error::InvalidCeremony); + } + c.threshold().check_genesis_budget()?; + Ok(Self { + ceremony: c.clone(), + h: pedersen_h(), + previous: dkg_initial_transcript(c), + commitments: BTreeMap::new(), + disqualified: BTreeMap::new(), + complaints: BTreeMap::new(), + equivocated: BTreeSet::new(), + feldman: BTreeMap::new(), + faulted: BTreeSet::new(), + }) + } + + fn threshold(&self) -> Threshold { + self.ceremony.threshold() + } + + fn t(&self) -> usize { + usize::from(self.threshold().t()) + } + + fn members(&self) -> impl Iterator + '_ { + self.ceremony.recipients().iter().map(|r| r.index) + } + + fn in_cohort(&self, index: MemberIndex) -> bool { + index.get() <= self.threshold().n() + } + + /// The closed round must belong to this ceremony, phase and exact previous link. + fn bind(&mut self, round: &ClosedRound, expected: u8) -> Result<(), Error> { + if round.ceremony_digest() != self.ceremony.digest() + || round.round() != expected + || round.previous_hash() != self.previous + { + return Err(Error::InvalidRound); + } + self.previous = round.transcript_hash(); + Ok(()) + } + + fn pedersen(&self, pair: &SharePair, commitments: &[G2], x: MemberIndex) -> bool { + G2::generator() + .mul(&pair.value) + .add(&self.h.mul(&pair.blinding)) + == evaluate_points(commitments, x) + } + + fn deals<'r>( + &mut self, + r1: &'r ClosedRound, + ) -> Result, Error> { + self.bind(r1, DEAL)?; + let senders = by_sender(r1); + let t = self.t(); + let n = self.threshold().n(); + let mut valid = BTreeMap::new(); + for i in self.members().collect::>() { + let reason = match senders.get(&i).copied() { + None => Some(DisqualifyReason::MissingDeal), + Some(Sent::Conflicting) => Some(DisqualifyReason::Equivocated { round: DEAL }), + Some(Sent::One(message)) => match message.payload() { + Ok(deal) + if deal.commitments.len() == t + && deal.dealings.iter().map(|(j, _)| j.get()).eq(1..=n) => + { + self.commitments.insert(i, deal.commitments.clone()); + valid.insert(i, deal); + None + } + _ => Some(DisqualifyReason::MalformedDeal), + }, + }; + if let Some(reason) = reason { + self.disqualified.insert(i, reason); + } + } + Ok(valid) + } + + fn complaints(&mut self, r2: &ClosedRound) -> Result<(), Error> { + self.bind(r2, COMPLAIN)?; + for (j, sent) in by_sender(r2) { + match sent { + Sent::Conflicting => { + self.equivocated.insert(j); + } + Sent::One(message) => { + let Ok(list) = message.payload() else { + continue; + }; + if !list.dealers.iter().all(|i| self.in_cohort(*i)) { + continue; + } + for i in list.dealers.iter().filter(|i| **i != j) { + self.complaints.entry(*i).or_default().insert(j); + } + } + } + } + Ok(()) + } + + /// Fix QUAL; return valid required answers addressed to `recipient`. + fn answers( + &mut self, + r3: &ClosedRound, + recipient: Option, + ) -> Result, Error> { + self.bind(r3, ANSWER)?; + let senders = by_sender(r3); + let empty = BTreeSet::new(); + let mut replacements = BTreeMap::new(); + let mut removed = BTreeMap::new(); + for (i, commitments) in &self.commitments { + let required = self.complaints.get(i).unwrap_or(&empty); + let answers = decoded(senders.get(i).copied()) + .filter(|a| a.answers.iter().all(|(j, _)| self.in_cohort(*j))); + let reason = if self.equivocated.contains(i) { + Some(DisqualifyReason::Equivocated { round: COMPLAIN }) + } else if matches!(senders.get(i), Some(Sent::Conflicting)) { + Some(DisqualifyReason::Equivocated { round: ANSWER }) + } else if required.len() >= self.t() { + Some(DisqualifyReason::Complaints) + } else if answers.is_some_and(|a| { + a.answers + .iter() + .any(|(j, pair)| !self.pedersen(pair, commitments, *j)) + }) { + Some(DisqualifyReason::InvalidAnswer) + } else if !required + .iter() + .all(|j| answers.is_some_and(|a| a.answers.iter().any(|(k, _)| k == j))) + { + Some(DisqualifyReason::MissingAnswer) + } else { + None + }; + match reason { + Some(reason) => { + removed.insert(*i, reason); + } + None => { + if let (Some(me), Some(answers)) = (recipient, answers) { + if required.contains(&me) { + if let Some((_, pair)) = answers.answers.iter().find(|(j, _)| *j == me) + { + replacements.insert(*i, pair.duplicate()); + } + } + } + } + } + } + for (i, reason) in removed { + self.commitments.remove(&i); + self.disqualified.insert(i, reason); + } + if self.commitments.len() < self.t() { + return Err(Error::InsufficientQualified); + } + Ok(replacements) + } + + fn qual(&self) -> BTreeSet { + self.commitments.keys().copied().collect() + } + + fn extract(&mut self, r4: &ClosedRound) -> Result<(), Error> { + self.bind(r4, EXTRACT)?; + let senders = by_sender(r4); + let t = self.t(); + for i in self.qual() { + match decoded(senders.get(&i).copied()) { + Some(feldman) if feldman.commitments.len() == t => { + self.feldman.insert(i, feldman.commitments.clone()); + } + _ => { + self.faulted.insert(i); + } + } + } + Ok(()) + } + + /// Apply closed R5; return whether R6 is required. + fn barrier(&mut self, r5: &ClosedRound) -> Result { + self.bind(r5, EXTRACTION_COMPLAINTS)?; + let senders = by_sender(r5); + let mut upheld = BTreeSet::new(); + for (j, sent) in &senders { + let Some(list) = decoded(Some(*sent)) else { + continue; + }; + for (i, pair) in &list.complaints { + if i == j || self.faulted.contains(i) { + continue; + } + let (Some(commitments), Some(feldman)) = + (self.commitments.get(i), self.feldman.get(i)) + else { + continue; + }; + if self.pedersen(pair, commitments, *j) + && G2::generator().mul(&pair.value) != evaluate_points(feldman, *j) + { + upheld.insert(*i); + } + } + } + self.faulted.extend(upheld); + Ok(!self.faulted.is_empty()) + } + + fn reconstruct(&mut self, r6: &ClosedRound) -> Result<(), Error> { + self.bind(r6, RECONSTRUCT)?; + let senders = by_sender(r6); + let t = self.t(); + for i in self.faulted.clone() { + let commitments = self.commitments.get(&i).ok_or(Error::InvalidRound)?; + let points: Vec<(Scalar, &Scalar)> = senders + .iter() + .filter_map(|(m, sent)| { + let list = decoded(Some(*sent))?; + let (_, pair) = list.values.iter().find(|(k, _)| *k == i)?; + self.pedersen(pair, commitments, *m) + .then(|| (Scalar::from_u64(u64::from(m.get())), &pair.value)) + }) + .take(t) + .collect(); + if points.len() < t { + return Err(Error::InsufficientRecovery); + } + let coefficients = interpolate(&points)?; + let recovered = coefficients + .iter() + .map(|a| G2::generator().mul(a)) + .collect(); + self.feldman.insert(i, recovered); + } + Ok(()) + } + + fn output(&self) -> Result { + let mut sums: Vec = (0..self.t()).map(|_| G2::identity()).collect(); + for i in self.commitments.keys() { + let feldman = self.feldman.get(i).ok_or(Error::InvalidRound)?; + for (sum, coefficient) in sums.iter_mut().zip(feldman) { + *sum = sum.add(coefficient); + } + } + Ok(DkgOutput { + public: PublicMaterial::from_commitments(self.threshold(), sums)?, + qual: self.qual(), + disqualified: self.disqualified.clone(), + reconstructed: self.faulted.clone(), + transcript_hash: self.previous, + }) + } +} + +// ------------------------------------------------------------ member states + +struct Polynomial { + values: Vec, + blinding: Vec, +} + +impl Polynomial { + fn at(&self, x: MemberIndex) -> SharePair { + let x = Scalar::from_u64(u64::from(x.get())); + SharePair { + value: poly::evaluate(&self.values, &x), + blinding: poly::evaluate(&self.blinding, &x), + } + } +} + +macro_rules! redacted { + ($($name:ident),*) => {$( + impl fmt::Debug for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(concat!(stringify!($name), "([REDACTED])")) + } + } + )*}; +} + +/// Holds this member's polynomials and recipient key until closed R1. Details: TC-2220 +pub struct DkgDealt { + public: Public, + me: MemberIndex, + hpke: CeremonyHpkeSecretKey, + polynomial: Polynomial, +} + +/// Holds the polynomials and private pairs until closed R2. Details: TC-2220 +pub struct DkgComplained { + public: Public, + me: MemberIndex, + polynomial: Polynomial, + pairs: BTreeMap, +} + +/// Holds the polynomials and private pairs until closed R3 fixes QUAL. Details: TC-2220 +pub struct DkgAnswered { + public: Public, + me: MemberIndex, + polynomial: Polynomial, + pairs: BTreeMap, +} + +/// Holds one private pair per QUAL dealer until closed R4. Details: TC-2220 +pub struct DkgExtracting { + public: Public, + me: MemberIndex, + pairs: BTreeMap, +} + +/// Holds the private pairs until the common closed R5. Details: TC-2220 +pub struct DkgAwaitingBarrier { + public: Public, + me: MemberIndex, + pairs: BTreeMap, +} + +/// Holds the private pairs until closed R6. Details: TC-2220 +pub struct DkgReconstructing { + public: Public, + me: MemberIndex, + pairs: BTreeMap, +} + +redacted!( + DkgDealt, + DkgComplained, + DkgAnswered, + DkgExtracting, + DkgAwaitingBarrier, + DkgReconstructing +); + +/// The outcome of consuming the common closed R5. Details: TC-2220 +#[derive(Debug)] +#[allow( + clippy::large_enum_variant, + reason = "Returned once per ceremony and moved immediately; variants mirror the R5 outcome." +)] +pub enum AfterBarrier { + /// No extraction fault or upheld R5 complaint. Details: TC-2220 + Finished(CandidateShare, DkgOutput), + /// R6 is required; send the message and consume closed R6. Details: TC-2220 + Reconstruct(DkgReconstructing, DkgMessage), +} + +/// Validate the genesis context and local recipient key, then deal R1. Details: TC-2220 +pub fn dkg_start( + c: &AuthorizedCeremony, + me: MemberIndex, + hpke_sk: CeremonyHpkeSecretKey, + rng: &mut impl CryptoRngCore, +) -> Result<(DkgDealt, DkgMessage), Error> { + let public = Public::new(c)?; + let own = c + .recipients() + .iter() + .find(|r| r.index == me) + .ok_or(Error::InvalidIndex)?; + if hpke_sk.public_key()?.encode() != own.hpke_public_key { + return Err(Error::ContextMismatch); + } + let t = public.t(); + let polynomial = Polynomial { + values: (0..t).map(|_| Scalar::random(&mut *rng, true)).collect(), + blinding: (0..t).map(|_| Scalar::random(&mut *rng, true)).collect(), + }; + let commitments: Vec = polynomial + .values + .iter() + .zip(&polynomial.blinding) + .map(|(a, b)| G2::generator().mul(a).add(&public.h.mul(b))) + .collect(); + let hash = commitment_hash(&commitments); + let mut dealings = Vec::with_capacity(c.recipients().len()); + for recipient in c.recipients() { + let pair = polynomial.at(recipient.index); + let value = Zeroizing::new(pair.value.to_bytes()); + let blinding = Zeroizing::new(pair.blinding.to_bytes()); + let mut plaintext = Zeroizing::new([0u8; PLAINTEXT_LEN]); + for (out, byte) in plaintext + .iter_mut() + .zip(value.iter().chain(blinding.iter())) + { + *out = *byte; + } + let context = DealingContext { + ceremony_digest: c.digest(), + round: DEAL, + sender: me, + recipient: recipient.index, + commitment_hash: hash, + }; + let key = CeremonyHpkePublicKey::decode(&recipient.hpke_public_key)?; + dealings.push(( + recipient.index, + seal_dealing(&key, &context, plaintext.as_slice(), &mut *rng)?, + )); + } + let deal = DkgDeal { + commitments, + dealings, + }; + let message = DkgMessage::new(c, DEAL, me, &deal.encode())?; + Ok(( + DkgDealt { + public, + me, + hpke: hpke_sk, + polynomial, + }, + message, + )) +} + +impl DkgDealt { + fn open(&self, dealer: MemberIndex, deal: &DkgDeal) -> Result { + let (_, sealed) = deal + .dealings + .iter() + .find(|(j, _)| *j == self.me) + .ok_or(Error::InvalidSealedDealing)?; + let context = DealingContext { + ceremony_digest: self.public.ceremony.digest(), + round: DEAL, + sender: dealer, + recipient: self.me, + commitment_hash: commitment_hash(&deal.commitments), + }; + let plaintext = open_dealing(&self.hpke, &context, sealed)?; + if plaintext.len() != PLAINTEXT_LEN { + return Err(Error::InvalidSealedDealing); + } + let (value, blinding) = plaintext.split_at(32); + Ok(SharePair { + value: Scalar::from_bytes(value)?, + blinding: Scalar::from_bytes(blinding)?, + }) + } + + /// Open and check every valid dealing; complain about failures (R2). Details: TC-2220 + pub fn complain( + mut self, + r1: &ClosedRound, + ) -> Result<(DkgComplained, DkgMessage), Error> { + let deals = self.public.deals(r1)?; + let mut pairs = BTreeMap::new(); + let mut dealers = Vec::new(); + for (i, deal) in deals { + match self.open(i, deal) { + Ok(pair) if self.public.pedersen(&pair, &deal.commitments, self.me) => { + pairs.insert(i, pair); + } + _ if i != self.me => dealers.push(i), + _ => {} + } + } + let message = DkgMessage::new( + &self.public.ceremony, + COMPLAIN, + self.me, + &DkgComplaints { dealers }.encode(), + )?; + let Self { + public, + me, + hpke, + polynomial, + } = self; + drop(hpke); + Ok(( + DkgComplained { + public, + me, + polynomial, + pairs, + }, + message, + )) + } +} + +impl DkgComplained { + /// Publish this dealer's pairs for every counted complaint against it (R3). Details: TC-2220 + pub fn answer( + mut self, + r2: &ClosedRound, + ) -> Result<(DkgAnswered, DkgMessage), Error> { + self.public.complaints(r2)?; + let answers = match ( + self.public.commitments.contains_key(&self.me), + self.public.complaints.get(&self.me), + ) { + (true, Some(required)) => required + .iter() + .map(|j| (*j, self.polynomial.at(*j))) + .collect(), + _ => Vec::new(), + }; + let message = DkgMessage::new( + &self.public.ceremony, + ANSWER, + self.me, + &DkgAnswers { answers }.encode(), + )?; + Ok(( + DkgAnswered { + public: self.public, + me: self.me, + polynomial: self.polynomial, + pairs: self.pairs, + }, + message, + )) + } +} + +impl DkgAnswered { + /// Fix QUAL, apply valid answers and publish Feldman coefficients (R4). Details: TC-2220 + pub fn extract( + mut self, + r3: &ClosedRound, + ) -> Result<(DkgExtracting, DkgMessage), Error> { + let replacements = self.public.answers(r3, Some(self.me))?; + self.pairs.extend(replacements); + let qual = self.public.qual(); + self.pairs.retain(|i, _| qual.contains(i)); + if self.pairs.len() != qual.len() { + return Err(Error::LocalShareUnavailable); + } + let feldman = DkgFeldman { + commitments: self + .polynomial + .values + .iter() + .map(|a| G2::generator().mul(a)) + .collect(), + }; + let message = DkgMessage::new(&self.public.ceremony, EXTRACT, self.me, &feldman.encode())?; + Ok(( + DkgExtracting { + public: self.public, + me: self.me, + pairs: self.pairs, + }, + message, + )) + } +} + +impl DkgExtracting { + /// Report Feldman-invalid pairs; R5 is always sent, possibly empty. Details: TC-2220 + pub fn extraction_complaints( + mut self, + r4: &ClosedRound, + ) -> Result<(DkgAwaitingBarrier, DkgMessage), Error> { + self.public.extract(r4)?; + let complaints = self + .public + .feldman + .iter() + .filter(|(i, _)| **i != self.me) + .filter_map(|(i, feldman)| { + let pair = self.pairs.get(i)?; + (G2::generator().mul(&pair.value) != evaluate_points(feldman, self.me)) + .then(|| (*i, pair.duplicate())) + }) + .collect(); + let message = DkgMessage::new( + &self.public.ceremony, + EXTRACTION_COMPLAINTS, + self.me, + &DkgExtractionComplaints { complaints }.encode(), + )?; + Ok(( + DkgAwaitingBarrier { + public: self.public, + me: self.me, + pairs: self.pairs, + }, + message, + )) + } +} + +fn candidate( + public: &Public, + me: MemberIndex, + pairs: &BTreeMap, +) -> Result<(CandidateShare, DkgOutput), Error> { + let output = public.output()?; + let mut scalar = Scalar::from_u64(0); + for i in &output.qual { + let pair = pairs.get(i).ok_or(Error::LocalShareUnavailable)?; + scalar = scalar.add(&pair.value); + } + Ok(( + CandidateShare { + transcript: output.transcript_hash, + index: me, + scalar, + }, + output, + )) +} + +impl DkgAwaitingBarrier { + /// Consume the common closed R5; finish or publish R6 values. Details: TC-2220 + pub fn after_barrier( + mut self, + r5: &ClosedRound, + ) -> Result { + if !self.public.barrier(r5)? { + let (share, output) = candidate(&self.public, self.me, &self.pairs)?; + return Ok(AfterBarrier::Finished(share, output)); + } + let values = self + .public + .faulted + .iter() + .filter_map(|i| Some((*i, self.pairs.get(i)?.duplicate()))) + .collect(); + let message = DkgMessage::new( + &self.public.ceremony, + RECONSTRUCT, + self.me, + &DkgReconstruction { values }.encode(), + )?; + Ok(AfterBarrier::Reconstruct( + DkgReconstructing { + public: self.public, + me: self.me, + pairs: self.pairs, + }, + message, + )) + } +} + +impl DkgReconstructing { + /// Recover every faulted QUAL dealer from closed R6 and finish. Details: TC-2220 + pub fn finish( + mut self, + r6: &ClosedRound, + ) -> Result<(CandidateShare, DkgOutput), Error> { + self.public.reconstruct(r6)?; + candidate(&self.public, self.me, &self.pairs) + } +} + +/// Recompute the outcome from raw signed rounds; no expected output is trusted. Details: TC-2220 +pub fn dkg_verify_transcript( + c: &AuthorizedCeremony, + t: &DkgTranscript, + v: &impl DidSignatureVerifier, +) -> Result { + let mut size = 0usize; + for record in &t.rounds { + let statements = core::iter::once(&record.closure).chain(record.messages.iter()); + for statement in statements { + size = size + .checked_add(statement.bytes.len()) + .and_then(|s| s.checked_add(statement.signature.len())) + .ok_or(Error::LimitExceeded)?; + if size > TRANSCRIPT_CAP { + return Err(Error::LimitExceeded); + } + } + } + let mut public = Public::new(c)?; + let mut records = t.rounds.iter(); + let r1 = verified_round::(c, records.next(), DEAL, public.previous, v)?; + public.deals(&r1)?; + let r2 = verified_round::(c, records.next(), COMPLAIN, public.previous, v)?; + public.complaints(&r2)?; + let r3 = verified_round::(c, records.next(), ANSWER, public.previous, v)?; + public.answers(&r3, None)?; + let r4 = verified_round::(c, records.next(), EXTRACT, public.previous, v)?; + public.extract(&r4)?; + let r5 = verified_round::( + c, + records.next(), + EXTRACTION_COMPLAINTS, + public.previous, + v, + )?; + if public.barrier(&r5)? { + let r6 = verified_round::( + c, + records.next(), + RECONSTRUCT, + public.previous, + v, + )?; + public.reconstruct(&r6)?; + } + if records.next().is_some() { + return Err(Error::InvalidRound); + } + public.output() +} + +fn verified_round( + c: &AuthorizedCeremony, + record: Option<&DkgRoundRecord>, + round: u8, + previous: TranscriptHash, + v: &impl DidSignatureVerifier, +) -> Result, Error> { + let record = record.ok_or(Error::InvalidRound)?; + ClosedRound::verify(c, round, &previous, &record.closure, &record.messages, v) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + ceremony::{tests::Signers, AuthorizedCeremony, CeremonyKind, Decode}, + curve::{Scalar, G2}, + dst, + error::Error, + ids::*, + seal::{seal_dealing, CeremonyHpkeSecretKey, DealingContext}, + }; + use proptest::prelude::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + use sha2::{Digest, Sha256}; + + fn idx(i: u16) -> MemberIndex { + MemberIndex::new(i).unwrap() + } + + fn pair(value: u64, blinding: u64) -> SharePair { + SharePair { + value: Scalar::from_u64(value), + blinding: Scalar::from_u64(blinding), + } + } + + const R: [u8; 32] = [ + 0x73, 0xed, 0xa7, 0x53, 0x29, 0x9d, 0x7d, 0x48, 0x33, 0x39, 0xd8, 0x08, 0x09, 0xa1, 0xd8, + 0x05, 0x53, 0xbd, 0xa4, 0x02, 0xff, 0xfe, 0x5b, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x01, + ]; + + fn sealed(recipient: u16, seed: u8) -> crate::seal::SealedDealing { + let mut rng = ChaCha20Rng::from_seed([seed; 32]); + let (_, public) = CeremonyHpkeSecretKey::generate(&mut rng); + let context = DealingContext { + ceremony_digest: [seed; 32], + round: 1, + sender: idx(1), + recipient: idx(recipient), + commitment_hash: [7; 32], + }; + seal_dealing(&public, &context, &[0; 64], &mut rng).unwrap() + } + + fn deal() -> DkgDeal { + let g = G2::generator(); + DkgDeal { + commitments: vec![g.clone(), g.mul(&Scalar::from_u64(2))], + dealings: vec![(idx(1), sealed(1, 3)), (idx(2), sealed(2, 4))], + } + } + + fn rejects_every_truncation_and_trailing_byte(bytes: &[u8]) { + for cut in 0..bytes.len() { + assert!(M::decode(&bytes[..cut]).is_err(), "truncated at {cut}"); + } + let mut trailing = bytes.to_vec(); + trailing.push(0); + assert!(M::decode(&trailing).is_err()); + let mut version = bytes.to_vec(); + version[0] = 2; + assert!(matches!(M::decode(&version), Err(Error::InvalidEncoding))); + } + + #[test] + fn dkg_start_label_is_distinct_from_and_not_a_prefix_of_every_frozen_tag() { + assert_eq!(DKG_START, b"TC-THRESHOLD-V1-DKG-START"); + assert_eq!(dst::DOMAINS.len(), 19); + for tag in dst::DOMAINS { + assert_ne!(*tag, DKG_START); + assert!(!tag.starts_with(DKG_START)); + assert!(!DKG_START.starts_with(tag)); + } + } + + #[test] + fn initial_transcript_hashes_the_exact_ceremony_digest() { + let signers = Signers::new(); + let ceremony = signers.genesis(); + let mut hash = Sha256::new(); + hash.update(25u32.to_be_bytes()); + hash.update(b"TC-THRESHOLD-V1-DKG-START"); + hash.update(ceremony.digest()); + let expected: [u8; 32] = hash.finalize().into(); + assert_eq!(dkg_initial_transcript(&ceremony).as_bytes(), &expected); + assert_ne!(expected, [0; 32]); + assert_ne!(expected, ceremony.digest()); + + let mut body = signers.body(CeremonyKind::Genesis, ceremony.threshold()); + body.id = CeremonyId::new([42; 32]); + let other = AuthorizedCeremony::verify_genesis( + &signers.anchor(), + &signers.statement(&body), + &signers, + ) + .unwrap(); + assert_ne!( + dkg_initial_transcript(&other), + dkg_initial_transcript(&ceremony) + ); + } + + #[test] + fn deal_payload_has_the_exact_layout_and_rejects_noncanonical_bytes() { + let deal = deal(); + let bytes = deal.encode(); + let mut expected = vec![1, 0, 2]; + for c in &deal.commitments { + expected.extend_from_slice(&c.encode()); + } + expected.extend_from_slice(&[0, 2]); + for (index, dealing) in &deal.dealings { + let envelope = dealing.encode(); + expected.extend_from_slice(&index.get().to_be_bytes()); + expected.extend_from_slice(&(envelope.len() as u32).to_be_bytes()); + expected.extend_from_slice(&envelope); + } + assert_eq!(bytes, expected); + assert_eq!(DkgDeal::decode(&bytes).unwrap().encode(), bytes); + rejects_every_truncation_and_trailing_byte::(&bytes); + + let swapped = DkgDeal { + commitments: deal.commitments.clone(), + dealings: vec![(idx(2), sealed(2, 4)), (idx(1), sealed(1, 3))], + }; + assert!(DkgDeal::decode(&swapped.encode()).is_err()); + let duplicate = DkgDeal { + commitments: deal.commitments.clone(), + dealings: vec![(idx(1), sealed(1, 3)), (idx(1), sealed(1, 4))], + }; + assert!(DkgDeal::decode(&duplicate.encode()).is_err()); + + let mut bad_point = bytes.clone(); + // Setting the infinity flag on a nonzero compressed x is invalid. + bad_point[3] ^= 0x40; + assert!(DkgDeal::decode(&bad_point).is_err()); + let mut bad_envelope = bytes.clone(); + let envelope_version = 3 + 2 * 96 + 2 + 2 + 4; + bad_envelope[envelope_version] = 2; + assert!(DkgDeal::decode(&bad_envelope).is_err()); + let mut index_zero = bytes.clone(); + index_zero[3 + 2 * 96 + 2..3 + 2 * 96 + 4].copy_from_slice(&[0, 0]); + assert!(DkgDeal::decode(&index_zero).is_err()); + + assert!(matches!( + DkgDeal::decode(&[1, 0, 17]), + Err(Error::LimitExceeded) + )); + let mut many_dealings = vec![1, 0, 0]; + many_dealings.extend_from_slice(&[0, 17]); + assert!(matches!( + DkgDeal::decode(&many_dealings), + Err(Error::LimitExceeded) + )); + } + + #[test] + fn complaint_payload_is_a_sorted_unique_index_list() { + let complaints = DkgComplaints { + dealers: vec![idx(2), idx(5)], + }; + let bytes = complaints.encode(); + assert_eq!(bytes, vec![1, 0, 2, 0, 2, 0, 5]); + assert_eq!(DkgComplaints::decode(&bytes).unwrap().encode(), bytes); + assert_eq!( + DkgComplaints::decode(&[1, 0, 0]).unwrap().dealers, + Vec::::new() + ); + rejects_every_truncation_and_trailing_byte::(&bytes); + for invalid in [ + vec![1, 0, 2, 0, 5, 0, 2], + vec![1, 0, 2, 0, 2, 0, 2], + vec![1, 0, 1, 0, 0], + vec![1, 0, 1, 0, 17], + ] { + assert!(DkgComplaints::decode(&invalid).is_err()); + } + assert!(matches!( + DkgComplaints::decode(&[1, 0, 17]), + Err(Error::LimitExceeded) + )); + } + + #[test] + fn published_pair_payloads_share_one_canonical_layout() { + let entries = || vec![(idx(1), pair(7, 8)), (idx(3), pair(9, 10))]; + let answers = DkgAnswers { answers: entries() }.encode(); + let extraction = DkgExtractionComplaints { + complaints: entries(), + } + .encode(); + let reconstruction = DkgReconstruction { values: entries() }.encode(); + let mut expected = vec![1, 0, 2]; + for (index, value, blinding) in [(1u16, 7u8, 8u8), (3, 9, 10)] { + expected.extend_from_slice(&index.to_be_bytes()); + let mut scalar = [0; 32]; + scalar[31] = value; + expected.extend_from_slice(&scalar); + scalar[31] = blinding; + expected.extend_from_slice(&scalar); + } + assert_eq!(answers, expected); + assert_eq!(extraction, expected); + assert_eq!(reconstruction, expected); + assert_eq!(DkgAnswers::decode(&answers).unwrap().encode(), answers); + assert_eq!( + DkgExtractionComplaints::decode(&extraction) + .unwrap() + .encode(), + extraction + ); + assert_eq!( + DkgReconstruction::decode(&reconstruction).unwrap().encode(), + reconstruction + ); + rejects_every_truncation_and_trailing_byte::(&answers); + rejects_every_truncation_and_trailing_byte::(&extraction); + rejects_every_truncation_and_trailing_byte::(&reconstruction); + + let mut noncanonical = expected.clone(); + noncanonical[5..37].copy_from_slice(&R); + assert!(matches!( + DkgAnswers::decode(&noncanonical), + Err(Error::InvalidScalar) + )); + let mut blinding = expected.clone(); + blinding[37..69].copy_from_slice(&R); + assert!(matches!( + DkgReconstruction::decode(&blinding), + Err(Error::InvalidScalar) + )); + let mut unsorted = expected.clone(); + unsorted[3..5].copy_from_slice(&[0, 3]); + unsorted[69..71].copy_from_slice(&[0, 1]); + assert!(DkgExtractionComplaints::decode(&unsorted).is_err()); + let mut duplicate = expected; + duplicate[69..71].copy_from_slice(&[0, 1]); + assert!(DkgAnswers::decode(&duplicate).is_err()); + assert!(matches!( + DkgAnswers::decode(&[1, 0, 17]), + Err(Error::LimitExceeded) + )); + } + + #[test] + fn feldman_payload_allows_individual_identity_coefficients() { + let g = G2::generator(); + let feldman = DkgFeldman { + commitments: vec![g.clone(), G2::identity(), g.mul(&Scalar::from_u64(3))], + }; + let bytes = feldman.encode(); + assert_eq!(bytes.len(), 3 + 3 * 96); + assert_eq!(&bytes[..3], &[1, 0, 3]); + assert_eq!(&bytes[3 + 96..3 + 192], &G2::identity().encode()); + assert_eq!(DkgFeldman::decode(&bytes).unwrap().encode(), bytes); + rejects_every_truncation_and_trailing_byte::(&bytes); + let mut off_curve = bytes.clone(); + off_curve[3 + 95] ^= 1; + assert!(DkgFeldman::decode(&off_curve).is_err()); + assert!(matches!( + DkgFeldman::decode(&[1, 0, 17]), + Err(Error::LimitExceeded) + )); + } + + proptest! { + #[test] + fn random_payload_bytes_reject_or_reencode_canonically( + bytes in proptest::collection::vec(any::(), 0..1400) + ) { + if let Ok(v) = DkgDeal::decode(&bytes) { prop_assert_eq!(v.encode(), bytes.clone()); } + if let Ok(v) = DkgComplaints::decode(&bytes) { prop_assert_eq!(v.encode(), bytes.clone()); } + if let Ok(v) = DkgAnswers::decode(&bytes) { prop_assert_eq!(v.encode(), bytes.clone()); } + if let Ok(v) = DkgFeldman::decode(&bytes) { prop_assert_eq!(v.encode(), bytes.clone()); } + if let Ok(v) = DkgExtractionComplaints::decode(&bytes) { prop_assert_eq!(v.encode(), bytes.clone()); } + if let Ok(v) = DkgReconstruction::decode(&bytes) { prop_assert_eq!(v.encode(), bytes.clone()); } + } + + #[test] + fn structured_payload_mutations_reject_or_reencode_canonically( + offset in 0usize..2048, + mask in 1u8..=255 + ) { + let valid = [ + deal().encode(), + DkgComplaints { dealers: vec![idx(1), idx(4)] }.encode(), + DkgAnswers { answers: vec![(idx(2), pair(3, 4))] }.encode(), + DkgFeldman { commitments: vec![G2::generator(), G2::identity()] }.encode(), + ]; + for bytes in valid { + let mut mutated = bytes.clone(); + let n = mutated.len(); + mutated[offset % n] ^= mask; + if let Ok(v) = DkgDeal::decode(&mutated) { prop_assert_eq!(v.encode(), mutated.clone()); } + if let Ok(v) = DkgComplaints::decode(&mutated) { prop_assert_eq!(v.encode(), mutated.clone()); } + if let Ok(v) = DkgAnswers::decode(&mutated) { prop_assert_eq!(v.encode(), mutated.clone()); } + if let Ok(v) = DkgFeldman::decode(&mutated) { prop_assert_eq!(v.encode(), mutated.clone()); } + } + } + } +} diff --git a/tinycloud-threshold/src/dkg_relay.rs b/tinycloud-threshold/src/dkg_relay.rs new file mode 100644 index 00000000..9dfd0368 --- /dev/null +++ b/tinycloud-threshold/src/dkg_relay.rs @@ -0,0 +1,2016 @@ +//! Test-only owner relay driving real member states over signed closed rounds. Details: TC-2220 +//! +//! Liveness scenarios deliver every honest message. Omissions and substitutions +//! below are explicit failure cases chosen by each test, not a broadcast model. + +use std::collections::{BTreeMap, BTreeSet}; + +use rand_chacha::ChaCha20Rng; +use rand_core::{CryptoRng, RngCore, SeedableRng}; +use serde_json::{json, Value}; + +use crate::{ + ceremony::{ + ceremony_message_body, round_closure_body, round_closure_preimage, tests::Signers, + AuthorizedCeremony, CeremonyKind, CeremonyRecipient, CeremonyStatement, ClosedRound, + Decode, DidSignatureVerifier, MemberCohort, SignedStatement, + }, + cert::{verify_certificate, CandidateShare, CertificateBody}, + curve::{hash_g2, Scalar, G2}, + dkg::*, + dst, + error::{Error, SignatureError}, + ids::*, + seal::{ + open_dealing, seal_dealing, CeremonyHpkePublicKey, CeremonyHpkeSecretKey, DealingContext, + }, + KeyShare, +}; + +pub(crate) fn idx(i: u16) -> MemberIndex { + MemberIndex::new(i).unwrap() +} + +pub(crate) fn pair(value: u64, blinding: u64) -> SharePair { + SharePair { + value: Scalar::from_u64(value), + blinding: Scalar::from_u64(blinding), + } +} + +/// Owner, member DIDs, Ed25519 dev keys and a genesis ceremony with real recipient keys. +pub(crate) struct Fixture { + pub(crate) signers: Signers, + pub(crate) ceremony: AuthorizedCeremony, + pub(crate) statement: SignedStatement, + pub(crate) seed: u8, +} + +pub(crate) fn statement(t: u16, n: u16, seed: u8) -> (Signers, SignedStatement) { + let signers = Signers::new(); + let mut rng = ChaCha20Rng::from_seed([seed; 32]); + let recipients = (1..=n) + .map(|i| CeremonyRecipient { + index: idx(i), + node: Signers::did(i as u8), + hpke_public_key: CeremonyHpkeSecretKey::generate(&mut rng).1.encode(), + }) + .collect(); + let body = CeremonyStatement { + kind: CeremonyKind::Genesis, + id: CeremonyId::new([seed; 32]), + network: signers.anchor().network, + owner: Signers::did(0), + current: None, + threshold: Threshold::new(t, n).unwrap(), + rekey: RekeyCounter::new(0), + epoch: Epoch::new(0), + recipients, + }; + let signed = signers.statement(&body); + (signers, signed) +} + +pub(crate) fn fixture(t: u16, n: u16, seed: u8) -> Fixture { + let (signers, statement) = statement(t, n, seed); + let ceremony = + AuthorizedCeremony::verify_genesis(&signers.anchor(), &statement, &signers).unwrap(); + Fixture { + signers, + ceremony, + statement, + seed, + } +} + +impl Fixture { + /// Regenerate member i's ceremony recipient key from the fixture seed. + pub(crate) fn secret(&self, i: u16) -> CeremonyHpkeSecretKey { + let mut rng = ChaCha20Rng::from_seed([self.seed; 32]); + let mut key = None; + for _ in 1..=i { + key = Some(CeremonyHpkeSecretKey::generate(&mut rng).0); + } + key.unwrap() + } + + pub(crate) fn member_rng(&self, i: u16) -> ChaCha20Rng { + let mut seed = [self.seed; 32]; + seed[0] = i as u8; + seed[1] = 0xd1; + ChaCha20Rng::from_seed(seed) + } + + pub(crate) fn sign(&self, member: u16, message: &DkgMessage) -> SignedStatement { + assert_eq!( + message.signing_preimage(), + crate::ceremony::ceremony_message_preimage(message.body()).unwrap() + ); + SignedStatement { + bytes: message.body().to_vec(), + signature: self + .signers + .sign(&Signers::did(member as u8), &message.signing_preimage()), + } + } + + pub(crate) fn n(&self) -> u16 { + self.ceremony.threshold().n() + } + + pub(crate) fn t(&self) -> u16 { + self.ceremony.threshold().t() + } +} + +fn sender(statement: &SignedStatement) -> u16 { + u16::from_be_bytes([statement.bytes[35], statement.bytes[36]]) +} + +/// Messages the owner is about to close for one round. +pub(crate) struct Outbox<'a> { + round: u8, + fixture: &'a Fixture, + rng: &'a mut ChaCha20Rng, + pub(crate) messages: Vec, +} + +impl Outbox<'_> { + pub(crate) fn round(&self) -> u8 { + self.round + } + pub(crate) fn fixture(&self) -> &Fixture { + self.fixture + } + pub(crate) fn ceremony(&self) -> &AuthorizedCeremony { + &self.fixture.ceremony + } + pub(crate) fn rng(&mut self) -> &mut ChaCha20Rng { + &mut *self.rng + } + /// A member-signed envelope carrying arbitrary payload bytes for this round. + pub(crate) fn signed(&self, member: u16, payload: &[u8]) -> SignedStatement { + let bytes = ceremony_message_body( + &self.fixture.ceremony, + self.round, + MemberCohort::Recipients, + idx(member), + payload, + ) + .unwrap(); + let preimage = crate::ceremony::ceremony_message_preimage(&bytes).unwrap(); + SignedStatement { + signature: self + .fixture + .signers + .sign(&Signers::did(member as u8), &preimage), + bytes, + } + } + pub(crate) fn has(&self, member: u16) -> bool { + self.messages.iter().any(|m| sender(m) == member) + } + pub(crate) fn payload(&self, member: u16) -> Vec { + let message = self + .messages + .iter() + .find(|m| sender(m) == member) + .expect("member message in this round"); + message.bytes[41..].to_vec() + } + pub(crate) fn remove(&mut self, member: u16) { + self.messages.retain(|m| sender(m) != member); + } + pub(crate) fn replace(&mut self, member: u16, payload: &[u8]) { + let signed = self.signed(member, payload); + let position = self.messages.iter().position(|m| sender(m) == member); + self.remove(member); + match position { + Some(p) => self.messages.insert(p, signed), + None => self.messages.push(signed), + } + } + pub(crate) fn push(&mut self, member: u16, payload: &[u8]) { + let signed = self.signed(member, payload); + self.messages.push(signed); + } + pub(crate) fn duplicate(&mut self, member: u16) { + let copy = self + .messages + .iter() + .find(|m| sender(m) == member) + .cloned() + .expect("member message in this round"); + self.messages.push(copy); + } +} + +pub(crate) type Outcome = Result<(CandidateShare, DkgOutput), Error>; + +pub(crate) struct Run { + pub(crate) fixture: Fixture, + pub(crate) outcomes: BTreeMap, + /// Members that asked for R6 after the common closed R5. + pub(crate) reconstruct: BTreeMap, + pub(crate) transcript: DkgTranscript, +} + +impl Run { + pub(crate) fn verify(&self) -> Result { + dkg_verify_transcript( + &self.fixture.ceremony, + &self.transcript, + &self.fixture.signers, + ) + } +} + +struct Relay<'a, V> { + fixture: &'a Fixture, + verifier: &'a V, + previous: TranscriptHash, + transcript: DkgTranscript, + rng: ChaCha20Rng, +} + +impl Relay<'_, V> { + fn close( + &mut self, + round: u8, + messages: Vec, + tamper: &mut impl FnMut(&mut Outbox), + ) -> ClosedRound { + let mut outbox = Outbox { + round, + fixture: self.fixture, + rng: &mut self.rng, + messages, + }; + tamper(&mut outbox); + let messages = outbox.messages; + let c = &self.fixture.ceremony; + let bytes = round_closure_body(c, round, &self.previous, &messages).unwrap(); + let closure = SignedStatement { + signature: self.fixture.signers.sign( + &self.fixture.signers.anchor().expected_owner, + &round_closure_preimage(&bytes).unwrap(), + ), + bytes, + }; + let closed = + ClosedRound::::verify(c, round, &self.previous, &closure, &messages, self.verifier) + .unwrap(); + assert_eq!(closed.previous_hash(), self.previous); + self.previous = closed.transcript_hash(); + self.transcript + .rounds + .push(DkgRoundRecord { closure, messages }); + closed + } +} + +type States = BTreeMap>; + +fn advance( + fixture: &Fixture, + states: States, + closed: &ClosedRound, + round: u8, + step: impl Fn(S, &ClosedRound) -> Result<(N, DkgMessage), Error>, +) -> (States, Vec) { + let mut next = BTreeMap::new(); + let mut out = Vec::new(); + for (i, state) in states { + match state.and_then(|s| step(s, closed)) { + Ok((s, message)) => { + assert_eq!(message.round(), round); + out.push(fixture.sign(i, &message)); + next.insert(i, Ok(s)); + } + Err(e) => { + next.insert(i, Err(e)); + } + } + } + (next, out) +} + +fn stopped(states: &States) -> bool { + states.values().all(Result::is_err) +} + +fn errors(states: States) -> BTreeMap { + states + .into_iter() + .map(|(i, s)| (i, Err(s.err().expect("stopped member")))) + .collect() +} + +/// Run every member's real state machine through a signing owner relay. +pub(crate) fn drive( + fixture: Fixture, + verifier: &V, + mut tamper: impl FnMut(&mut Outbox), +) -> Run { + let n = fixture.n(); + let mut relay = Relay { + fixture: &fixture, + verifier, + previous: dkg_initial_transcript(&fixture.ceremony), + transcript: DkgTranscript { rounds: Vec::new() }, + rng: ChaCha20Rng::from_seed([fixture.seed ^ 0xaa; 32]), + }; + let mut dealt = BTreeMap::new(); + let mut r1 = Vec::new(); + for i in 1..=n { + let mut rng = fixture.member_rng(i); + let (state, message) = + dkg_start(&fixture.ceremony, idx(i), fixture.secret(i), &mut rng).unwrap(); + assert_eq!(message.round(), 1); + r1.push(fixture.sign(i, &message)); + dealt.insert(i, Ok(state)); + } + let closed1 = relay.close::(1, r1, &mut tamper); + let (complained, r2) = advance(&fixture, dealt, &closed1, 2, DkgDealt::complain); + let closed2 = relay.close::(2, r2, &mut tamper); + let (answered, r3) = advance(&fixture, complained, &closed2, 3, DkgComplained::answer); + let closed3 = relay.close::(3, r3, &mut tamper); + let (extracting, r4) = advance(&fixture, answered, &closed3, 4, DkgAnswered::extract); + let mut outcomes = BTreeMap::new(); + let mut reconstruct = BTreeMap::new(); + if stopped(&extracting) { + outcomes = errors(extracting); + } else { + let closed4 = relay.close::(4, r4, &mut tamper); + let (waiting, r5) = advance( + &fixture, + extracting, + &closed4, + 5, + DkgExtracting::extraction_complaints, + ); + let closed5 = relay.close::(5, r5, &mut tamper); + let mut reconstructing = BTreeMap::new(); + let mut r6 = Vec::new(); + for (i, state) in waiting { + match state.and_then(|s| s.after_barrier(&closed5)) { + Ok(AfterBarrier::Finished(candidate, output)) => { + reconstruct.insert(i, false); + outcomes.insert(i, Ok((candidate, output))); + } + Ok(AfterBarrier::Reconstruct(state, message)) => { + assert_eq!(message.round(), 6); + reconstruct.insert(i, true); + r6.push(fixture.sign(i, &message)); + reconstructing.insert(i, state); + } + Err(e) => { + outcomes.insert(i, Err(e)); + } + } + } + if !reconstructing.is_empty() { + let closed6 = relay.close::(6, r6, &mut tamper); + for (i, state) in reconstructing { + outcomes.insert(i, state.finish(&closed6)); + } + } + } + let transcript = relay.transcript; + Run { + fixture, + outcomes, + reconstruct, + transcript, + } +} + +pub(crate) fn run(t: u16, n: u16, seed: u8, tamper: impl FnMut(&mut Outbox)) -> Run { + let fixture = fixture(t, n, seed); + let signers = Signers::new(); + drive(fixture, &signers, tamper) +} + +pub(crate) fn honest(t: u16, n: u16, seed: u8) -> Run { + run(t, n, seed, |_| {}) +} + +type View = ( + Vec<[u8; 96]>, + [u8; 96], + Vec<[u8; 96]>, + BTreeSet, + BTreeMap, + BTreeSet, + TranscriptHash, +); + +pub(crate) fn view(output: &DkgOutput) -> View { + let n = output.public.threshold().n(); + ( + output.public.commitments().collect(), + output.public.master_public_key().encode(), + (1..=n) + .map(|i| output.public.public_share(idx(i)).unwrap()) + .collect(), + output.qual.clone(), + output.disqualified.clone(), + output.reconstructed.clone(), + output.transcript_hash, + ) +} + +/// Every listed member and the public transcript verifier agree; each candidate binds. +pub(crate) fn agreed(run: &Run, members: &[u16]) -> DkgOutput { + let verified = run.verify().unwrap(); + assert_eq!(verified.transcript_hash, last_hash(run)); + for i in members { + let (candidate, output) = run.outcomes[i].as_ref().unwrap(); + assert_eq!(view(output), view(&verified), "member {i}"); + assert_eq!(candidate.index(), idx(*i)); + assert_eq!(candidate.transcript_hash(), verified.transcript_hash); + assert_eq!( + G2::generator().mul(&candidate.scalar).encode(), + verified.public.public_share(idx(*i)).unwrap(), + "candidate {i} matches its public share" + ); + } + verified +} + +fn last_hash(run: &Run) -> TranscriptHash { + let mut previous = dkg_initial_transcript(&run.fixture.ceremony); + for record in &run.transcript.rounds { + let mut body = previous.as_bytes().to_vec(); + body.extend_from_slice(&(record.closure.bytes.len() as u32).to_be_bytes()); + body.extend_from_slice(&record.closure.bytes); + body.extend_from_slice(&(record.closure.signature.len() as u32).to_be_bytes()); + body.extend_from_slice(&record.closure.signature); + previous = TranscriptHash::new(dst::tagged_hash(dst::TRANSCRIPT, &body)); + } + previous +} + +pub(crate) fn members(set: &[u16]) -> BTreeSet { + set.iter().map(|i| idx(*i)).collect() +} + +pub(crate) fn sum(points: impl IntoIterator) -> [u8; 96] { + points + .into_iter() + .fold(G2::identity(), |acc, p| { + acc.add(&G2::decode(&p, true).unwrap()) + }) + .encode() +} + +pub(crate) fn commitment_hash(commitments: &[G2]) -> [u8; 32] { + let mut body = (commitments.len() as u16).to_be_bytes().to_vec(); + for c in commitments { + body.extend_from_slice(&c.encode()); + } + dst::tagged_hash(dst::COMMITMENTS, &body) +} + +fn pedersen_h() -> G2 { + hash_g2(b"pedersen-h", dst::PEDERSEN_H) +} + +/// Re-seal one recipient's entry of a dealer's R1 with canonical but wrong scalars. +pub(crate) fn bad_dealing(out: &mut Outbox, dealer: u16, recipient: u16) -> Vec { + let mut deal = DkgDeal::decode(&out.payload(dealer)).unwrap(); + let public = CeremonyHpkePublicKey::decode( + &out.ceremony().recipients()[usize::from(recipient - 1)].hpke_public_key, + ) + .unwrap(); + let context = DealingContext { + ceremony_digest: out.ceremony().digest(), + round: 1, + sender: idx(dealer), + recipient: idx(recipient), + commitment_hash: commitment_hash(&deal.commitments), + }; + let mut plaintext = [0u8; 64]; + plaintext[31] = 5; + plaintext[63] = 6; + let sealed = seal_dealing(&public, &context, &plaintext, out.rng()).unwrap(); + deal.dealings + .iter_mut() + .find(|(j, _)| j.get() == recipient) + .unwrap() + .1 = sealed; + deal.encode() +} + +/// Open a dealer's R1 entry with the recipient's regenerated key. +pub(crate) fn opened(fixture: &Fixture, deal: &[u8], dealer: u16, recipient: u16) -> SharePair { + let deal = DkgDeal::decode(deal).unwrap(); + let context = DealingContext { + ceremony_digest: fixture.ceremony.digest(), + round: 1, + sender: idx(dealer), + recipient: idx(recipient), + commitment_hash: commitment_hash(&deal.commitments), + }; + let sealed = &deal + .dealings + .iter() + .find(|(j, _)| j.get() == recipient) + .unwrap() + .1; + let plaintext = open_dealing(&fixture.secret(recipient), &context, sealed).unwrap(); + SharePair { + value: Scalar::from_bytes(&plaintext[..32]).unwrap(), + blinding: Scalar::from_bytes(&plaintext[32..]).unwrap(), + } +} + +/// Add δ(x)=Π(x−root)·D to a Feldman vector: the check passes exactly at the roots. +pub(crate) fn vanishing_feldman(payload: &[u8], roots: &[u16]) -> Vec { + let mut feldman = DkgFeldman::decode(payload).unwrap(); + let d = G2::generator().mul(&Scalar::from_u64(0x5eed)); + let mut poly = vec![Scalar::from_u64(1)]; + for root in roots { + let mut next: Vec = (0..=poly.len()).map(|_| Scalar::from_u64(0)).collect(); + for (k, c) in poly.iter().enumerate() { + next[k + 1] = next[k + 1].add(c); + next[k] = next[k].sub(&c.mul(&Scalar::from_u64(u64::from(*root)))); + } + poly = next; + } + assert!(poly.len() <= feldman.commitments.len()); + for (k, c) in poly.iter().enumerate() { + feldman.commitments[k] = feldman.commitments[k].add(&d.mul(c)); + } + feldman.encode() +} + +fn constants(out: &Outbox, into: &mut BTreeMap) { + for i in 1..=out.fixture().n() { + if out.has(i) { + let feldman = DkgFeldman::decode(&out.payload(i)).unwrap(); + into.insert(i, feldman.commitments[0].encode()); + } + } +} + +fn genuine_mpk(constants: &BTreeMap, qual: &BTreeSet) -> [u8; 96] { + sum(qual.iter().map(|i| constants[&i.get()])) +} + +/// A test DID verifier that also accepts `signature || 00`, modelling a +/// signature scheme with more than one valid encoding of one statement. +struct Malleable(Signers); +impl DidSignatureVerifier for Malleable { + fn verify(&self, signer: &Did, message: &[u8], signature: &[u8]) -> Result<(), SignatureError> { + match signature { + [inner @ .., 0] if inner.len() == 64 => self.0.verify(signer, message, inner), + _ => self.0.verify(signer, message, signature), + } + } +} + +struct CountingRng { + inner: ChaCha20Rng, + draws: usize, +} +impl RngCore for CountingRng { + fn next_u32(&mut self) -> u32 { + self.draws += 1; + self.inner.next_u32() + } + fn next_u64(&mut self) -> u64 { + self.draws += 1; + self.inner.next_u64() + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.draws += 1; + self.inner.fill_bytes(dest) + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.draws += 1; + self.inner.try_fill_bytes(dest) + } +} +impl CryptoRng for CountingRng {} + +fn counting() -> CountingRng { + CountingRng { + inner: ChaCha20Rng::from_seed([0x77; 32]), + draws: 0, + } +} + +// ---------------------------------------------------------------- completion + +#[test] +fn genuine_2of3_completes_with_every_member_and_the_verifier_agreeing() { + let run = honest(2, 3, 0x31); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); + assert!(output.disqualified.is_empty()); + assert!(output.reconstructed.is_empty()); + assert_eq!(run.transcript.rounds.len(), 5); + assert!(run.reconstruct.values().all(|r| !r)); + assert_eq!(output.public.threshold(), Threshold::new(2, 3).unwrap()); + let mut sorted = output.public.commitments().collect::>(); + assert_eq!(sorted.len(), 2); + sorted.dedup(); + assert_eq!(sorted.len(), 2); +} + +#[test] +fn genesis_3of4_is_refused_before_any_dealing() { + let (signers, statement) = statement(3, 4, 0x32); + assert!(matches!( + AuthorizedCeremony::verify_genesis(&signers.anchor(), &statement, &signers), + Err(Error::UnsupportedFaultBudget) + )); + assert_eq!( + Threshold::new(3, 4).unwrap().check_genesis_budget(), + Err(Error::UnsupportedFaultBudget) + ); + assert_eq!(Threshold::new(3, 5).unwrap().check_genesis_budget(), Ok(())); +} + +#[test] +fn three_of_five_with_two_faulty_members_recovers_the_committed_master_key() { + // Members 4 and 5 are faulty. Dealer 5 publishes a Feldman vector that + // passes only at indices 4 and 5; member 4 files a false R5 complaint + // against honest dealer 1; both withhold R6. + let mut constants_seen = BTreeMap::new(); + let run = run(3, 5, 0x33, |out| match out.round() { + 4 => { + constants(out, &mut constants_seen); + let forged = vanishing_feldman(&out.payload(5), &[4, 5]); + out.replace(5, &forged); + } + 5 => { + let complaint = DkgExtractionComplaints { + complaints: vec![(idx(1), pair(11, 12))], + }; + out.replace(4, &complaint.encode()); + } + 6 => { + out.remove(4); + out.remove(5); + } + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3, 4, 5])); + assert!(output.disqualified.is_empty()); + assert_eq!(output.reconstructed, members(&[5])); + assert_eq!( + output.public.master_public_key().encode(), + genuine_mpk(&constants_seen, &output.qual) + ); + for i in [1, 2, 3] { + assert!(run.reconstruct[&i]); + } +} + +#[test] +fn one_recipient_bad_private_value_is_replaced_by_a_valid_answer() { + let mut answer = None; + let run = run(2, 3, 0x34, |out| match out.round() { + 1 => { + let forged = bad_dealing(out, 1, 2); + out.replace(1, &forged); + } + 2 => assert_eq!( + DkgComplaints::decode(&out.payload(2)).unwrap().dealers, + vec![idx(1)] + ), + 3 => answer = Some(DkgAnswers::decode(&out.payload(1)).unwrap()), + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); + assert!(output.disqualified.is_empty()); + let answer = answer.unwrap(); + assert_eq!(answer.answers.len(), 1); + assert_eq!(answer.answers[0].0, idx(2)); +} + +// ------------------------------------------------------------ R2/R3 rulings + +#[test] +fn false_complaint_is_refuted_by_a_valid_answer() { + let run = run(2, 3, 0x35, |out| { + if out.round() == 2 { + out.replace( + 3, + &DkgComplaints { + dealers: vec![idx(1)], + } + .encode(), + ); + } + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); +} + +#[test] +fn t_distinct_complainers_disqualify_even_with_valid_answers() { + let run = run(2, 3, 0x36, |out| { + if out.round() == 1 { + let forged = bad_dealing(out, 3, 1); + out.replace(3, &forged); + let forged = bad_dealing(out, 3, 2); + out.replace(3, &forged); + } + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2])); + assert_eq!( + output.disqualified, + BTreeMap::from([(idx(3), DisqualifyReason::Complaints)]) + ); +} + +#[test] +fn duplicate_complaints_count_once() { + // t = 3: two distinct complainers, one retransmitting, stay below t. + let run = run(3, 5, 0x37, |out| { + if out.round() == 2 { + let complaint = DkgComplaints { + dealers: vec![idx(1)], + } + .encode(); + out.replace(4, &complaint); + out.replace(5, &complaint); + out.duplicate(4); + out.duplicate(4); + } + }); + let output = agreed(&run, &[1, 2, 3, 4, 5]); + assert_eq!(output.qual, members(&[1, 2, 3, 4, 5])); +} + +#[test] +fn self_complaints_are_ignored() { + // Dealer 3 sends member 1 a bad value and complains against itself. With + // the self-complaint counted, two complainers would reach t = 2. + let run = run(2, 3, 0x38, |out| match out.round() { + 1 => { + let forged = bad_dealing(out, 3, 1); + out.replace(3, &forged); + } + 2 => out.replace( + 3, + &DkgComplaints { + dealers: vec![idx(3)], + } + .encode(), + ), + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); +} + +#[test] +fn malformed_r2_supplies_no_complaints() { + // A well-formed complaint with a trailing byte is malformed: it creates no + // required answer, so dealer 1's missing R3 does not disqualify it. + let run = run(2, 3, 0x39, |out| match out.round() { + 2 => { + let mut malformed = DkgComplaints { + dealers: vec![idx(1)], + } + .encode(); + malformed.push(0); + out.replace(3, &malformed); + } + 3 => out.remove(1), + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); +} + +#[test] +fn conflicting_r2_content_is_equivocation_even_when_malformed() { + let run = run(3, 5, 0x3a, |out| { + if out.round() == 2 { + out.replace(5, &[1, 0]); + out.push(5, &[1, 0, 1]); + out.replace(4, &[1, 0, 1, 0]); + out.duplicate(4); + } + }); + let output = agreed(&run, &[1, 2, 3, 4]); + assert_eq!( + output.disqualified, + BTreeMap::from([(idx(5), DisqualifyReason::Equivocated { round: 2 })]) + ); + assert_eq!(output.qual, members(&[1, 2, 3, 4])); +} + +/// Retransmit a member's first message with an alternate valid signature encoding. +fn alternate_signature(out: &mut Outbox, member: u16) { + let mut copy = out + .messages + .iter() + .find(|m| sender(m) == member) + .cloned() + .unwrap(); + copy.signature.push(0); + out.messages.push(copy); +} + +#[test] +fn different_signature_encoding_is_not_equivocation() { + let verifier = Malleable(Signers::new()); + let run = drive(fixture(2, 3, 0x3b), &verifier, |out| { + if out.round() <= 3 { + alternate_signature(out, 3); + } + }); + assert_eq!(run.transcript.rounds[0].messages.len(), 4); + let verified = + dkg_verify_transcript(&run.fixture.ceremony, &run.transcript, &verifier).unwrap(); + assert_eq!(verified.qual, members(&[1, 2, 3])); + assert!(verified.disqualified.is_empty()); + for i in [1, 2, 3] { + assert_eq!(view(&run.outcomes[&i].as_ref().unwrap().1), view(&verified)); + } + + let changed = drive(fixture(2, 3, 0x3b), &verifier, |out| { + if out.round() == 1 { + let forged = bad_dealing(out, 3, 2); + out.push(3, &forged); + } + }); + let verified = + dkg_verify_transcript(&changed.fixture.ceremony, &changed.transcript, &verifier).unwrap(); + assert_eq!( + verified.disqualified, + BTreeMap::from([(idx(3), DisqualifyReason::Equivocated { round: 1 })]) + ); +} + +#[test] +fn missing_r3_disqualifies_only_with_required_answers() { + let run = run(2, 3, 0x3c, |out| match out.round() { + 2 => out.replace( + 3, + &DkgComplaints { + dealers: vec![idx(1)], + } + .encode(), + ), + 3 => { + out.remove(1); + out.remove(2); + } + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!( + output.disqualified, + BTreeMap::from([(idx(1), DisqualifyReason::MissingAnswer)]) + ); + assert_eq!(output.qual, members(&[2, 3])); +} + +#[test] +fn malformed_r3_disqualifies_only_with_required_answers() { + let run = run(2, 3, 0x3d, |out| match out.round() { + 2 => out.replace( + 3, + &DkgComplaints { + dealers: vec![idx(1)], + } + .encode(), + ), + 3 => { + for dealer in [1, 2] { + let mut malformed = out.payload(dealer); + malformed.push(0); + out.replace(dealer, &malformed); + } + } + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!( + output.disqualified, + BTreeMap::from([(idx(1), DisqualifyReason::MissingAnswer)]) + ); + assert_eq!(output.qual, members(&[2, 3])); +} + +#[test] +fn invalid_unsolicited_answer_disqualifies_its_dealer() { + let run = run(2, 3, 0x3e, |out| { + if out.round() == 3 { + let answer = DkgAnswers { + answers: vec![(idx(3), pair(5, 6))], + }; + out.replace(2, &answer.encode()); + } + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!( + output.disqualified, + BTreeMap::from([(idx(2), DisqualifyReason::InvalidAnswer)]) + ); + assert_eq!(output.qual, members(&[1, 3])); +} + +#[test] +fn valid_unsolicited_answer_is_ignored() { + let mut deal = Vec::new(); + let run = run(2, 3, 0x3f, |out| match out.round() { + 1 => deal = out.payload(2), + 3 => { + let answer = DkgAnswers { + answers: vec![(idx(3), opened(out.fixture(), &deal, 2, 3))], + }; + out.replace(2, &answer.encode()); + } + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); + assert!(output.disqualified.is_empty()); +} + +#[test] +fn r2_naming_a_dealer_outside_the_cohort_supplies_no_complaints() { + // Member 3's R2 names dealer 1 and index 4 > n. The list is out of cohort, + // so it creates no required answer and dealer 1's missing R3 is harmless. + let run = run(2, 3, 0x5c, |out| match out.round() { + 2 => out.replace( + 3, + &DkgComplaints { + dealers: vec![idx(1), idx(4)], + } + .encode(), + ), + 3 => out.remove(1), + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); + assert!(output.disqualified.is_empty()); +} + +#[test] +fn r3_naming_a_recipient_outside_the_cohort_is_malformed() { + // Dealer 2 publishes an unrequested R3 entry for index 4 > n: malformed, + // and without required answers it does not disqualify. Dealer 1 answers + // member 3's complaint validly but adds an index-4 entry: malformed, so the + // required answer counts as absent. + let run = run(2, 3, 0x5d, |out| match out.round() { + 2 => out.replace( + 3, + &DkgComplaints { + dealers: vec![idx(1)], + } + .encode(), + ), + 3 => { + let mut answers = DkgAnswers::decode(&out.payload(1)).unwrap(); + assert_eq!(answers.answers.len(), 1); + answers.answers.push((idx(4), pair(5, 6))); + out.replace(1, &answers.encode()); + let unrequested = DkgAnswers { + answers: vec![(idx(4), pair(5, 6))], + }; + out.replace(2, &unrequested.encode()); + } + _ => {} + }); + // Assert the verifier's outcome directly, so a counted out-of-cohort entry + // fails here as a disqualification mismatch rather than inside a helper. + assert_eq!( + run.verify().map(|o| o.disqualified), + Ok(BTreeMap::from([(idx(1), DisqualifyReason::MissingAnswer)])) + ); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!( + output.disqualified, + BTreeMap::from([(idx(1), DisqualifyReason::MissingAnswer)]) + ); + assert_eq!(output.qual, members(&[2, 3])); +} + +#[test] +fn relay_omitted_complaint_fails_only_the_affected_member() { + // Failure scenario outside the trusted-relay liveness model. + let run = run(2, 3, 0x40, |out| match out.round() { + 1 => { + let forged = bad_dealing(out, 1, 2); + out.replace(1, &forged); + } + 2 => out.remove(2), + _ => {} + }); + assert!(matches!( + run.outcomes[&2], + Err(Error::LocalShareUnavailable) + )); + let output = agreed(&run, &[1, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); +} + +// ------------------------------------------------- R4, barrier and R6 rules + +#[test] +fn selective_r4_reaches_the_common_barrier_and_is_reconstructed() { + let mut constants_seen = BTreeMap::new(); + let mut r5 = BTreeMap::new(); + let run = run(2, 3, 0x41, |out| match out.round() { + 4 => { + constants(out, &mut constants_seen); + let forged = vanishing_feldman(&out.payload(3), &[1]); + out.replace(3, &forged); + } + 5 => { + for i in 1..=3 { + r5.insert( + i, + DkgExtractionComplaints::decode(&out.payload(i)) + .unwrap() + .complaints + .len(), + ); + } + } + _ => {} + }); + // Member 1's own check passed; member 2 complained. Both waited for the + // same closed R5 and both entered R6. + assert_eq!(r5, BTreeMap::from([(1, 0), (2, 1), (3, 0)])); + assert_eq!( + run.reconstruct, + BTreeMap::from([(1, true), (2, true), (3, true)]) + ); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3])); + assert_eq!(output.reconstructed, members(&[3])); + assert_eq!( + output.public.master_public_key().encode(), + genuine_mpk(&constants_seen, &output.qual) + ); + assert_eq!(run.transcript.rounds.len(), 6); +} + +type Mutation = fn(&mut Outbox); + +#[test] +fn bad_missing_and_malformed_r4_are_extraction_faults() { + let cases: [(&str, Mutation); 5] = [ + ("bad", |out| { + let forged = vanishing_feldman(&out.payload(3), &[]); + out.replace(3, &forged); + }), + ("missing", |out| out.remove(3)), + ("trailing byte", |out| { + let mut p = out.payload(3); + p.push(0); + out.replace(3, &p); + }), + ("wrong length", |out| { + let mut f = DkgFeldman::decode(&out.payload(3)).unwrap(); + f.commitments.pop(); + out.replace(3, &f.encode()); + }), + ("invalid point", |out| { + let mut p = out.payload(3); + p[3 + 95] ^= 1; + out.replace(3, &p); + }), + ]; + for (name, mutate) in cases { + let mut constants_seen = BTreeMap::new(); + let run = run(2, 3, 0x42, |out| { + if out.round() == 4 { + constants(out, &mut constants_seen); + mutate(out); + } + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.qual, members(&[1, 2, 3]), "{name}"); + assert_eq!(output.reconstructed, members(&[3]), "{name}"); + assert_eq!( + output.public.master_public_key().encode(), + genuine_mpk(&constants_seen, &output.qual), + "{name}" + ); + } +} + +#[test] +fn r1_equivocation_disqualifies_but_r4_equivocation_reconstructs() { + let mut constants_seen = BTreeMap::new(); + let run = run(3, 5, 0x43, |out| match out.round() { + 1 => { + let second = bad_dealing(out, 5, 1); + out.push(5, &second); + } + 4 => { + constants(out, &mut constants_seen); + let second = vanishing_feldman(&out.payload(4), &[]); + out.push(4, &second); + } + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!( + output.disqualified, + BTreeMap::from([(idx(5), DisqualifyReason::Equivocated { round: 1 })]) + ); + assert_eq!(output.qual, members(&[1, 2, 3, 4])); + assert_eq!(output.reconstructed, members(&[4])); + assert_eq!( + output.public.master_public_key().encode(), + genuine_mpk(&constants_seen, &output.qual) + ); +} + +#[test] +fn every_member_sends_r5_and_an_empty_barrier_finishes() { + let mut r5 = Vec::new(); + let run = run(2, 3, 0x44, |out| { + if out.round() == 5 { + for i in 1..=3 { + r5.push(DkgExtractionComplaints::decode(&out.payload(i)).unwrap()); + } + assert_eq!(out.messages.len(), 3); + } + }); + assert!(r5.iter().all(|r| r.complaints.is_empty())); + let output = agreed(&run, &[1, 2, 3]); + assert!(output.reconstructed.is_empty()); + assert_eq!(run.transcript.rounds.len(), 5); +} + +#[test] +fn missing_or_malformed_r5_counts_as_an_empty_list() { + let reference = agreed(&honest(2, 3, 0x45), &[1, 2, 3]); + for malformed in [false, true] { + let run = run(2, 3, 0x45, |out| { + if out.round() == 5 { + if malformed { + out.replace(2, &[1, 0]); + } else { + out.remove(2); + } + } + }); + let output = agreed(&run, &[1, 2, 3]); + assert!(output.reconstructed.is_empty()); + assert_eq!(output.qual, reference.qual); + assert_eq!( + output.public.master_public_key().encode(), + reference.public.master_public_key().encode() + ); + } +} + +#[test] +fn false_extraction_complaints_are_refuted() { + // Member 3 complains against dealer 1 with (a) a Pedersen-invalid pair + // and (b) its genuine, Feldman-valid pair; neither triggers R6. + for genuine in [false, true] { + let mut deal = Vec::new(); + let run = run(2, 3, 0x46, |out| match out.round() { + 1 => deal = out.payload(1), + 5 => { + let value = if genuine { + opened(out.fixture(), &deal, 1, 3) + } else { + pair(5, 6) + }; + let complaint = DkgExtractionComplaints { + complaints: vec![(idx(1), value)], + }; + out.replace(3, &complaint.encode()); + } + _ => {} + }); + let output = agreed(&run, &[1, 2, 3]); + assert!(output.reconstructed.is_empty()); + assert!(run.reconstruct.values().all(|r| !r)); + } +} + +#[test] +fn corrupt_dealer_withholding_its_r6_is_recovered_and_kept() { + let mut constants_seen = BTreeMap::new(); + let run = run(2, 3, 0x47, |out| match out.round() { + 4 => { + constants(out, &mut constants_seen); + let forged = vanishing_feldman(&out.payload(3), &[]); + out.replace(3, &forged); + } + 6 => out.remove(3), + _ => {} + }); + let output = agreed(&run, &[1, 2]); + assert_eq!(output.qual, members(&[1, 2, 3])); + assert_eq!(output.reconstructed, members(&[3])); + assert_eq!( + output.public.master_public_key().encode(), + genuine_mpk(&constants_seen, &output.qual) + ); +} + +#[test] +fn invalid_or_equivocated_r6_values_do_not_count() { + // Dealer 3 is faulted; member 2 equivocates in R6 and member 3 sends an + // invalid value. Only member 1's value is valid, below t = 2. + let run = run(2, 3, 0x48, |out| match out.round() { + 4 => { + let forged = vanishing_feldman(&out.payload(3), &[]); + out.replace(3, &forged); + } + 6 => { + let changed = DkgReconstruction { + values: vec![(idx(3), pair(1, 2))], + }; + out.push(2, &changed.encode()); + out.replace(3, &changed.encode()); + } + _ => {} + }); + for i in [1, 2, 3] { + assert!(matches!(run.outcomes[&i], Err(Error::InsufficientRecovery))); + } + assert!(matches!(run.verify(), Err(Error::InsufficientRecovery))); +} + +#[test] +fn insufficient_valid_r6_values_abort_with_a_typed_error() { + let run = run(2, 3, 0x49, |out| match out.round() { + 4 => { + let forged = vanishing_feldman(&out.payload(3), &[]); + out.replace(3, &forged); + } + 6 => { + out.remove(2); + out.remove(3); + } + _ => {} + }); + for i in [1, 2, 3] { + assert!(matches!(run.outcomes[&i], Err(Error::InsufficientRecovery))); + } + assert!(matches!(run.verify(), Err(Error::InsufficientRecovery))); +} + +#[test] +fn fewer_than_t_qualified_dealers_abort_with_a_typed_error() { + let run = run(2, 3, 0x4a, |out| { + if out.round() == 1 { + out.remove(2); + out.remove(3); + } + }); + for i in [1, 2, 3] { + assert!(matches!( + run.outcomes[&i], + Err(Error::InsufficientQualified) + )); + } + assert_eq!(run.transcript.rounds.len(), 3); + assert!(matches!(run.verify(), Err(Error::InsufficientQualified))); +} + +#[test] +fn missing_and_malformed_r1_disqualify() { + let missing = run(3, 5, 0x4b, |out| { + if out.round() == 1 { + out.remove(4); + let mut truncated = out.payload(5); + truncated.pop(); + out.replace(5, &truncated); + } + }); + let output = agreed(&missing, &[1, 2, 3]); + assert_eq!( + output.disqualified, + BTreeMap::from([ + (idx(4), DisqualifyReason::MissingDeal), + (idx(5), DisqualifyReason::MalformedDeal) + ]) + ); + let malformed = run(3, 5, 0x4c, |out| { + if out.round() == 1 { + let mut deal = DkgDeal::decode(&out.payload(5)).unwrap(); + deal.commitments.pop(); + out.replace(5, &deal.encode()); + let mut deal = DkgDeal::decode(&out.payload(4)).unwrap(); + deal.dealings.pop(); + out.replace(4, &deal.encode()); + } + }); + let output = agreed(&malformed, &[1, 2, 3]); + assert_eq!( + output.disqualified, + BTreeMap::from([ + (idx(4), DisqualifyReason::MalformedDeal), + (idx(5), DisqualifyReason::MalformedDeal) + ]) + ); +} + +// --------------------------------------------- authorization and state binding + +#[test] +fn local_recipient_key_and_context_are_checked_before_any_randomness() { + let f = fixture(2, 3, 0x50); + let mut rng = counting(); + assert!(matches!( + dkg_start(&f.ceremony, idx(1), f.secret(2), &mut rng), + Err(Error::ContextMismatch) + )); + assert!(matches!( + dkg_start(&f.ceremony, idx(4), f.secret(1), &mut rng), + Err(Error::InvalidIndex) + )); + let signers = Signers::new(); + let (current, _) = crate::cert::tests::genesis(&signers); + let (_, _, reshare) = crate::cert::tests::successor( + &signers, + ¤t, + CeremonyKind::Reshare, + Threshold::new(2, 3).unwrap(), + // Seed 11 matches the genesis fixture, so the reshare keeps its mpk. + 11, + ); + assert!(matches!( + dkg_start(&reshare, idx(1), f.secret(1), &mut rng), + Err(Error::InvalidCeremony) + )); + assert_eq!(rng.draws, 0); + let (state, message) = dkg_start(&f.ceremony, idx(1), f.secret(1), &mut rng).unwrap(); + assert!(rng.draws > 0); + assert_eq!(message.round(), 1); + assert_eq!(format!("{state:?}"), "DkgDealt([REDACTED])"); +} + +fn close_for( + f: &Fixture, + ceremony: &AuthorizedCeremony, + round: u8, + previous: &TranscriptHash, + messages: &[SignedStatement], +) -> Result, Error> { + let bytes = round_closure_body(ceremony, round, previous, messages)?; + let closure = SignedStatement { + signature: f.signers.sign( + &f.signers.anchor().expected_owner, + &round_closure_preimage(&bytes)?, + ), + bytes, + }; + ClosedRound::::verify(ceremony, round, previous, &closure, messages, &f.signers) +} + +#[test] +fn transitions_refuse_other_ceremonies_rounds_and_histories() { + let f = fixture(2, 3, 0x51); + let other = fixture(2, 3, 0x52); + let h0 = dkg_initial_transcript(&f.ceremony); + let start = |i: u16| { + let mut rng = f.member_rng(i); + dkg_start(&f.ceremony, idx(i), f.secret(i), &mut rng).unwrap() + }; + let r1: Vec<_> = (1..=3).map(|i| f.sign(i, &start(i).1)).collect(); + let closed1 = close_for::(&f, &f.ceremony, 1, &h0, &r1).unwrap(); + + // Another authorized ceremony, its own correct history. + let other_r1: Vec<_> = (1..=3) + .map(|i| { + let mut rng = other.member_rng(i); + let (_, m) = dkg_start(&other.ceremony, idx(i), other.secret(i), &mut rng).unwrap(); + other.sign(i, &m) + }) + .collect(); + let foreign = close_for::( + &other, + &other.ceremony, + 1, + &dkg_initial_transcript(&other.ceremony), + &other_r1, + ) + .unwrap(); + assert!(matches!( + start(1).0.complain(&foreign), + Err(Error::InvalidRound) + )); + // The same messages are not accepted under this ceremony's closure. + assert!(close_for::(&f, &f.ceremony, 1, &h0, &other_r1).is_err()); + + // Correct ceremony, wrong previous link. + let rebased = + close_for::(&f, &f.ceremony, 1, &TranscriptHash::new([9; 32]), &r1).unwrap(); + assert!(matches!( + start(1).0.complain(&rebased), + Err(Error::InvalidRound) + )); + + // A round-2 closure presented to the round-1 transition. + let empty = DkgComplaints { dealers: vec![] }.encode(); + let r2_as_deal: Vec<_> = (1..=3) + .map(|i| { + let bytes = + ceremony_message_body(&f.ceremony, 2, MemberCohort::Recipients, idx(i), &empty) + .unwrap(); + let preimage = crate::ceremony::ceremony_message_preimage(&bytes).unwrap(); + SignedStatement { + signature: f.signers.sign(&Signers::did(i as u8), &preimage), + bytes, + } + }) + .collect(); + let wrong_round = close_for::(&f, &f.ceremony, 2, &h0, &r2_as_deal).unwrap(); + assert!(matches!( + start(1).0.complain(&wrong_round), + Err(Error::InvalidRound) + )); + + // Later phase: R3 chained to R1 instead of R2. + let mut states = BTreeMap::new(); + for i in 1..=3u16 { + states.insert(i, start(i).0); + } + let mut complained = BTreeMap::new(); + let mut r2 = Vec::new(); + for (i, s) in states { + let (s, m) = s.complain(&closed1).unwrap(); + r2.push(f.sign(i, &m)); + complained.insert(i, s); + } + let closed2 = + close_for::(&f, &f.ceremony, 2, &closed1.transcript_hash(), &r2).unwrap(); + let mut r3 = Vec::new(); + let mut answered = BTreeMap::new(); + for (i, s) in complained { + let (s, m) = s.answer(&closed2).unwrap(); + r3.push(f.sign(i, &m)); + answered.insert(i, s); + } + let skipped = + close_for::(&f, &f.ceremony, 3, &closed1.transcript_hash(), &r3).unwrap(); + let state = answered.remove(&1).unwrap(); + assert!(matches!(state.extract(&skipped), Err(Error::InvalidRound))); + let chained = + close_for::(&f, &f.ceremony, 3, &closed2.transcript_hash(), &r3).unwrap(); + assert!(answered.remove(&2).unwrap().extract(&chained).is_ok()); +} + +#[test] +fn transitions_refuse_a_foreign_ceremony_closure_on_this_history() { + // Same owner, round and previous link (this ceremony's H0); only the + // authorized-ceremony digest differs. + let f = fixture(2, 3, 0x5a); + let other = fixture(2, 3, 0x5b); + let h0 = dkg_initial_transcript(&f.ceremony); + let other_r1: Vec<_> = (1..=3) + .map(|i| { + let mut rng = other.member_rng(i); + let (_, m) = dkg_start(&other.ceremony, idx(i), other.secret(i), &mut rng).unwrap(); + other.sign(i, &m) + }) + .collect(); + let foreign = close_for::(&other, &other.ceremony, 1, &h0, &other_r1).unwrap(); + assert_eq!(foreign.previous_hash(), h0); + assert_eq!(foreign.round(), 1); + assert_ne!(foreign.ceremony_digest(), f.ceremony.digest()); + let mut rng = f.member_rng(1); + let (state, _) = dkg_start(&f.ceremony, idx(1), f.secret(1), &mut rng).unwrap(); + assert!(matches!(state.complain(&foreign), Err(Error::InvalidRound))); +} + +#[test] +fn closed_rounds_reject_wrong_signers_cohorts_and_signatures() { + let f = fixture(2, 3, 0x53); + let h0 = dkg_initial_transcript(&f.ceremony); + let mut rng = f.member_rng(1); + let (_, message) = dkg_start(&f.ceremony, idx(1), f.secret(1), &mut rng).unwrap(); + let genuine = f.sign(1, &message); + assert!(close_for::(&f, &f.ceremony, 1, &h0, std::slice::from_ref(&genuine)).is_ok()); + + let mut wrong_signer = genuine.clone(); + wrong_signer.signature = f + .signers + .sign(&Signers::did(2), &message.signing_preimage()); + assert!(matches!( + close_for::(&f, &f.ceremony, 1, &h0, &[wrong_signer]), + Err(Error::InvalidSignature) + )); + let mut flipped = genuine.clone(); + flipped.signature[0] ^= 1; + assert!(matches!( + close_for::(&f, &f.ceremony, 1, &h0, &[flipped]), + Err(Error::InvalidSignature) + )); + let mut current_cohort = genuine.clone(); + current_cohort.bytes[34] = MemberCohort::Current as u8; + current_cohort.signature = f.signers.sign( + &Signers::did(1), + &crate::ceremony::ceremony_message_preimage(¤t_cohort.bytes).unwrap(), + ); + assert!(close_for::(&f, &f.ceremony, 1, &h0, &[current_cohort]).is_err()); +} + +// -------------------------------------------------------- public verifier + +#[test] +fn verifier_recomputes_from_authenticated_records_only() { + let run = run(2, 3, 0x54, |out| { + if out.round() == 4 { + let forged = vanishing_feldman(&out.payload(2), &[]); + out.replace(2, &forged); + } + }); + let output = agreed(&run, &[1, 2, 3]); + assert_eq!(output.reconstructed, members(&[2])); + + let mut tampered = DkgTranscript { + rounds: run.transcript.rounds.clone(), + }; + tampered.rounds[1].messages[0].signature[0] ^= 1; + assert!(dkg_verify_transcript(&run.fixture.ceremony, &tampered, &run.fixture.signers).is_err()); + + let mut swapped = DkgTranscript { + rounds: run.transcript.rounds.clone(), + }; + swapped.rounds.swap(2, 3); + assert!(dkg_verify_transcript(&run.fixture.ceremony, &swapped, &run.fixture.signers).is_err()); + + let other = fixture(2, 3, 0x55); + assert!(dkg_verify_transcript(&other.ceremony, &run.transcript, &run.fixture.signers).is_err()); + + let mut missing_r6 = DkgTranscript { + rounds: run.transcript.rounds.clone(), + }; + missing_r6.rounds.pop(); + assert!(matches!( + dkg_verify_transcript(&run.fixture.ceremony, &missing_r6, &run.fixture.signers), + Err(Error::InvalidRound) + )); + + let plain = honest(2, 3, 0x56); + let mut extra = DkgTranscript { + rounds: plain.transcript.rounds.clone(), + }; + extra.rounds.push(run.transcript.rounds[5].clone()); + assert!(matches!( + dkg_verify_transcript(&plain.fixture.ceremony, &extra, &plain.fixture.signers), + Err(Error::InvalidRound) + )); +} + +struct Refusing; +impl DidSignatureVerifier for Refusing { + fn verify(&self, _: &Did, _: &[u8], _: &[u8]) -> Result<(), SignatureError> { + panic!("size bound must precede signature verification") + } +} + +#[test] +fn transcript_bound_of_4_mib_precedes_verification() { + let f = fixture(2, 3, 0x57); + let record = |copies: usize| DkgRoundRecord { + closure: SignedStatement { + bytes: vec![0; 98], + signature: vec![0; 64], + }, + messages: vec![ + SignedStatement { + bytes: vec![0; 65_536], + signature: vec![0; 64], + }; + copies + ], + }; + // 64 × (65 536 + 64) + 98 + 64 bytes exceeds 4 194 304. + let over = DkgTranscript { + rounds: vec![record(64)], + }; + assert!(matches!( + dkg_verify_transcript(&f.ceremony, &over, &Refusing), + Err(Error::LimitExceeded) + )); + let split = DkgTranscript { + rounds: vec![record(32), record(32)], + }; + assert!(matches!( + dkg_verify_transcript(&f.ceremony, &split, &Refusing), + Err(Error::LimitExceeded) + )); + let under = DkgTranscript { + rounds: vec![record(63)], + }; + let result = dkg_verify_transcript(&f.ceremony, &under, &f.signers); + assert!(result.is_err()); + assert!(!matches!(result, Err(Error::LimitExceeded))); +} + +// ----------------------------------------------------- within-budget property + +fn misbehave(out: &mut Outbox, m: u16, behaviour: u8, target: u16) { + let present = out.has(m); + match (out.round(), behaviour) { + (1, 1) => out.remove(m), + (1, 2) if present => { + let mut p = out.payload(m); + p.pop(); + out.replace(m, &p); + } + (1, 3) if present => { + let second = bad_dealing(out, m, target); + out.push(m, &second); + } + (1, 4) | (1, 5) if present => { + let forged = bad_dealing(out, m, target); + out.replace(m, &forged); + } + (2, 6) => out.replace( + m, + &DkgComplaints { + dealers: vec![idx(target)], + } + .encode(), + ), + (2, 7) => out.push(m, &[1, 0, 1, 0, target as u8]), + (3, 5) => out.remove(m), + (3, 8) => out.replace( + m, + &DkgAnswers { + answers: vec![(idx(target), pair(5, 6))], + } + .encode(), + ), + (4, 9) if present => { + let forged = vanishing_feldman(&out.payload(m), &[m]); + out.replace(m, &forged); + } + (4, 10) => out.remove(m), + (4, 11) if present => { + let mut p = out.payload(m); + p.push(0); + out.replace(m, &p); + } + (4, 12) if present => { + let second = vanishing_feldman(&out.payload(m), &[]); + out.push(m, &second); + } + (5, 13) => out.replace( + m, + &DkgExtractionComplaints { + complaints: vec![(idx(target), pair(5, 6))], + } + .encode(), + ), + (6, 13) | (5, 14) | (6, 14) => out.remove(m), + _ => {} + } +} + +/// The `misbehave` model has 15 behaviours per faulty member. +const BEHAVIOURS: u8 = 15; +/// Fixed fixture seeds; each shape is enumerated under both. +const ENUMERATION_SEEDS: [u8; 2] = [0x01, 0x9c]; + +// Coverage: the enumeration below runs every behaviour assignment of the +// in-tree `misbehave` model to the last t−1 members, for 2-of-3 and 3-of-5, +// under two fixed seeds: 2 × (15 + 15 × 15) = 480 ceremonies. It covers this +// model only, with honest messages always delivered by the relay; it is not a +// claim about arbitrary faulty behaviour, other seeds or other parameters. + +/// One within-budget run: faulty members are the last t−1 indices; every honest +/// member and the verifier agree, honest dealers stay in QUAL, and mpk is the sum +/// of the QUAL dealers' committed constants. +fn within_budget_agreement(t: u16, n: u16, seed: u8, behaviours: &[u8]) { + let honest_members: Vec = (1..=n - (t - 1)).collect(); + let faulty: Vec = (n - (t - 1) + 1..=n).collect(); + assert_eq!(faulty.len(), behaviours.len()); + let mut constants_seen = BTreeMap::new(); + let run = run(t, n, seed, |out| { + if out.round() == 4 { + constants(out, &mut constants_seen); + } + for (m, behaviour) in faulty.iter().zip(behaviours) { + misbehave(out, *m, *behaviour, 1); + } + }); + let output = agreed(&run, &honest_members); + let faulty_set = members(&faulty); + assert!( + members(&honest_members).is_subset(&output.qual), + "{behaviours:?}" + ); + assert!( + output.disqualified.keys().all(|i| faulty_set.contains(i)), + "{behaviours:?}" + ); + assert!( + output.reconstructed.is_subset(&faulty_set), + "{behaviours:?}" + ); + assert_eq!( + output.public.master_public_key().encode(), + genuine_mpk(&constants_seen, &output.qual), + "{behaviours:?}" + ); +} + +#[test] +fn every_within_budget_fault_pattern_finishes_with_identical_outcomes_2of3() { + for seed in ENUMERATION_SEEDS { + for behaviour in 0..BEHAVIOURS { + within_budget_agreement(2, 3, seed, &[behaviour]); + } + } +} + +#[test] +fn every_within_budget_fault_pattern_finishes_with_identical_outcomes_3of5() { + for seed in ENUMERATION_SEEDS { + for first in 0..BEHAVIOURS { + for second in 0..BEHAVIOURS { + within_budget_agreement(3, 5, seed, &[first, second]); + } + } + } +} + +// ------------------------------------------------------------------ vectors + +fn hex_statement(s: &SignedStatement) -> Value { + json!({"body": hex::encode(&s.bytes), "signature": hex::encode(&s.signature)}) +} + +fn reason(r: &DisqualifyReason) -> Value { + match r { + DisqualifyReason::MissingDeal => json!({"reason": "MissingDeal"}), + DisqualifyReason::MalformedDeal => json!({"reason": "MalformedDeal"}), + DisqualifyReason::Equivocated { round } => { + json!({"reason": "Equivocated", "round": round}) + } + DisqualifyReason::Complaints => json!({"reason": "Complaints"}), + DisqualifyReason::MissingAnswer => json!({"reason": "MissingAnswer"}), + DisqualifyReason::InvalidAnswer => json!({"reason": "InvalidAnswer"}), + } +} + +fn output_json(o: &DkgOutput) -> Value { + let n = o.public.threshold().n(); + json!({ + "qual": o.qual.iter().map(|i| i.get()).collect::>(), + "disqualified": o.disqualified.iter().map(|(i, r)| { + let mut entry = reason(r); + entry["member"] = json!(i.get()); + entry + }).collect::>(), + "reconstructed": o.reconstructed.iter().map(|i| i.get()).collect::>(), + "mpk": hex::encode(o.public.master_public_key().encode()), + "commitments": o.public.commitments().map(hex::encode).collect::>(), + "public_shares": (1..=n).map(|i| json!({ + "member": i, + "point": hex::encode(o.public.public_share(idx(i)).unwrap()), + })).collect::>(), + "transcript_hash": hex::encode(o.transcript_hash.as_bytes()), + }) +} + +fn case(name: &str, description: &str, run: &Run) -> Value { + let inputs = json!({ + "threshold": run.fixture.t(), + "members": run.fixture.n(), + "ceremony": hex_statement(&run.fixture.statement), + "ceremony_digest": hex::encode(run.fixture.ceremony.digest()), + "initial_transcript": hex::encode(dkg_initial_transcript(&run.fixture.ceremony).as_bytes()), + "rounds": run.transcript.rounds.iter().map(|r| json!({ + "closure": hex_statement(&r.closure), + "messages": r.messages.iter().map(hex_statement).collect::>(), + })).collect::>(), + }); + match run.verify() { + Ok(output) => { + json!({"name": name, "description": description, "operation": "dkg_verify_transcript", "inputs": inputs, "outputs": output_json(&output)}) + } + Err(error) => { + json!({"name": name, "description": description, "operation": "dkg_verify_transcript", "inputs": inputs, "error": format!("{error:?}")}) + } + } +} + +fn vectors() -> Value { + let honest_run = honest(2, 3, 0x61); + let selective = run(2, 3, 0x62, |out| { + if out.round() == 4 { + let forged = vanishing_feldman(&out.payload(3), &[1]); + out.replace(3, &forged); + } + }); + let two_faulty = run(3, 5, 0x63, |out| match out.round() { + 4 => { + let forged = vanishing_feldman(&out.payload(5), &[4, 5]); + out.replace(5, &forged); + } + 5 => out.replace( + 4, + &DkgExtractionComplaints { + complaints: vec![(idx(1), pair(11, 12))], + } + .encode(), + ), + 6 => { + out.remove(4); + out.remove(5); + } + _ => {} + }); + let answers = run(3, 5, 0x64, |out| match out.round() { + 1 => { + let forged = bad_dealing(out, 1, 2); + out.replace(1, &forged); + let second = bad_dealing(out, 4, 3); + out.push(4, &second); + let forged = bad_dealing(out, 5, 1); + out.replace(5, &forged); + } + 3 => out.remove(5), + _ => {} + }); + let insufficient = run(2, 3, 0x65, |out| match out.round() { + 4 => { + let forged = vanishing_feldman(&out.payload(3), &[]); + out.replace(3, &forged); + } + 6 => { + out.remove(2); + out.remove(3); + } + _ => {} + }); + let unqualified = run(2, 3, 0x66, |out| { + if out.round() == 1 { + out.remove(2); + out.remove(3); + } + }); + let cases = vec![ + case("genesis_2of3", "All members follow the protocol; R5 is empty and no R6 round exists.", &honest_run), + case("genesis_2of3_selective_extraction", "Dealer 3's R4 vector passes only member 1's check; member 2's R5 complaint leads to R6 reconstruction of dealer 3.", &selective), + case("genesis_3of5_two_faulty", "Dealer 5's R4 passes only at members 4 and 5; member 4 files an invalid R5 complaint; members 4 and 5 send no R6.", &two_faulty), + case("genesis_3of5_answers_and_disqualification", "Dealer 1's bad value to member 2 is answered; dealer 4 sends two R1 messages; dealer 5 does not answer member 1's complaint.", &answers), + case("insufficient_reconstruction_values", "Only member 1 sends R6 for faulted dealer 3.", &insufficient), + case("insufficient_qualified_dealers", "Only dealer 1 publishes R1.", &unqualified), + ]; + let signers: Vec<_> = (0..=5u8) + .map(|i| { + json!({ + "did": Signers::did(i).as_str(), + "ed25519_public_key": hex::encode(ed25519_dalek::SigningKey::from_bytes(&[i; 32]).verifying_key().to_bytes()), + }) + }) + .collect(); + let anchor = Signers::new().anchor(); + json!({ + "format_version": 1, + "suite": "BLS12-381 G2 Pedersen/Feldman GJKR New-DKG; RFC 9180 base mode DHKEM(X25519, HKDF-SHA256)/HKDF-SHA256/ChaCha20-Poly1305 dealings; Ed25519 test signatures", + "spec_sections": ["3.2", "3.3", "3.7", "3.10", "S0.3 DKG appendix"], + "encoding": "hex; statements are exact signed bytes; scalars 32-byte big-endian; G2 96-byte compressed", + "seeds": {"ceremonies": [0x61, 0x62, 0x63, 0x64, 0x65, 0x66]}, + "dst": { + "dkg_start": String::from_utf8_lossy(DKG_START), + "pedersen_h": String::from_utf8_lossy(dst::PEDERSEN_H), + "pedersen_h_message": "pedersen-h", + "commitments": String::from_utf8_lossy(dst::COMMITMENTS), + "dealing": String::from_utf8_lossy(dst::DEALING), + "ceremony_auth": String::from_utf8_lossy(dst::CEREMONY_AUTH), + "ceremony_msg": String::from_utf8_lossy(dst::CEREMONY_MSG), + "round_close": String::from_utf8_lossy(dst::ROUND_CLOSE), + "transcript": String::from_utf8_lossy(dst::TRANSCRIPT), + }, + "anchor": {"owner": anchor.expected_owner.as_str(), "network": anchor.network.as_str()}, + "signers": signers, + "cases": cases, + }) +} + +#[test] +fn dkg_vectors_regenerate_byte_for_byte_and_replay() { + let encoded = format!("{}\n", serde_json::to_string_pretty(&vectors()).unwrap()); + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("vectors/v1/dkg-genesis.json"); + if std::env::var("TC_THRESHOLD_REGEN_VECTORS").as_deref() == Ok("1") { + std::fs::write(&path, &encoded).unwrap(); + } + let published = std::fs::read_to_string(&path).unwrap(); + assert_eq!(published, encoded); + + let file: Value = serde_json::from_str(&published).unwrap(); + let signers = Signers::new(); + let bytes = |v: &Value| hex::decode(v.as_str().unwrap()).unwrap(); + let statement = |v: &Value| SignedStatement { + bytes: bytes(&v["body"]), + signature: bytes(&v["signature"]), + }; + for case in file["cases"].as_array().unwrap() { + let inputs = &case["inputs"]; + let ceremony = AuthorizedCeremony::verify_genesis( + &signers.anchor(), + &statement(&inputs["ceremony"]), + &signers, + ) + .unwrap(); + assert_eq!( + dkg_initial_transcript(&ceremony).as_bytes().to_vec(), + bytes(&inputs["initial_transcript"]) + ); + let transcript = DkgTranscript { + rounds: inputs["rounds"] + .as_array() + .unwrap() + .iter() + .map(|r| DkgRoundRecord { + closure: statement(&r["closure"]), + messages: r["messages"] + .as_array() + .unwrap() + .iter() + .map(statement) + .collect(), + }) + .collect(), + }; + match dkg_verify_transcript(&ceremony, &transcript, &signers) { + Ok(output) => assert_eq!(output_json(&output), case["outputs"], "{}", case["name"]), + Err(error) => assert_eq!(format!("{error:?}"), case["error"].as_str().unwrap()), + } + } +} + +#[test] +fn pedersen_generator_is_the_frozen_hash_to_g2_output() { + let fixture: Value = + serde_json::from_str(include_str!("../vectors/v1/primitives.json")).unwrap(); + let case = fixture["cases"] + .as_array() + .unwrap() + .iter() + .find(|c| c["name"] == "pedersen_h") + .unwrap(); + assert_eq!( + hex::encode(pedersen_h().encode()), + case["outputs"]["point"].as_str().unwrap() + ); + // The relay's commitment check: every member's Pedersen relation holds. + let run = honest(2, 3, 0x58); + let deal = DkgDeal::decode(&run.transcript.rounds[0].messages[0].bytes[41..]).unwrap(); + let opened_pair = opened( + &run.fixture, + &run.transcript.rounds[0].messages[0].bytes[41..], + 1, + 2, + ); + let x = Scalar::from_u64(2); + let expected = deal + .commitments + .iter() + .rev() + .fold(G2::identity(), |acc, c| acc.mul(&x).add(c)); + assert_eq!( + G2::generator() + .mul(&opened_pair.value) + .add(&pedersen_h().mul(&opened_pair.blinding)), + expected + ); +} + +#[test] +fn certificate_binding_uses_the_dkg_output() { + let run = honest(2, 3, 0x59); + let output = agreed(&run, &[1, 2, 3]); + let f = &run.fixture; + let body = CertificateBody::genesis( + &f.signers.anchor(), + &f.ceremony, + &output.public, + &output.transcript_hash, + ) + .unwrap(); + let cert = verify_certificate( + &f.signers.anchor(), + &crate::cert::tests::sign(body, &f.signers), + &f.signers, + ) + .unwrap(); + let Run { outcomes, .. } = run; + for (i, outcome) in outcomes { + let (candidate, _) = outcome.unwrap(); + let share = KeyShare::bind(candidate, &cert).unwrap(); + assert_eq!(share.index(), idx(i)); + } +} diff --git a/tinycloud-threshold/src/error.rs b/tinycloud-threshold/src/error.rs index 3deea2a8..5e137be6 100644 --- a/tinycloud-threshold/src/error.rs +++ b/tinycloud-threshold/src/error.rs @@ -52,6 +52,12 @@ pub enum Error { NotEnoughReady, #[error("candidate does not match certificate")] CandidateMismatch, + #[error("fewer than t qualified dealers")] + InsufficientQualified, + #[error("fewer than t valid reconstruction values")] + InsufficientRecovery, + #[error("no valid private value from a qualified dealer")] + LocalShareUnavailable, } /// Verification distinguishes context selection from a member's share equation. diff --git a/tinycloud-threshold/src/lib.rs b/tinycloud-threshold/src/lib.rs index a864b3bb..b08bc3ab 100644 --- a/tinycloud-threshold/src/lib.rs +++ b/tinycloud-threshold/src/lib.rs @@ -21,6 +21,7 @@ pub mod context; #[allow(unsafe_code)] pub mod curve; pub mod derive; +pub mod dkg; pub mod dst; mod encoding; pub mod error; @@ -29,13 +30,6 @@ pub mod ibe; pub mod identity; pub mod ids; mod poly; -#[cfg_attr( - not(test), - allow( - dead_code, - reason = "Crate-private dealing helpers await DKG/reshare callers." - ) -)] pub mod seal; pub use activation::{ @@ -50,6 +44,12 @@ pub use cert::{ }; pub use context::{AuthorizedDerivation, KeyContext}; pub use derive::*; +pub use dkg::{ + dkg_initial_transcript, dkg_start, dkg_verify_transcript, AfterBarrier, DisqualifyReason, + DkgAnswered, DkgAnswers, DkgAwaitingBarrier, DkgComplained, DkgComplaints, DkgDeal, DkgDealt, + DkgExtracting, DkgExtractionComplaints, DkgFeldman, DkgMessage, DkgOutput, DkgReconstructing, + DkgReconstruction, DkgRoundRecord, DkgTranscript, +}; pub use error::{CertError, CombineError, Error, ShareError, SignatureError}; pub use fault::{verify_fault_proof, FaultProof, FaultVerdict, NotAFaultReason}; pub use ibe::*; @@ -60,4 +60,6 @@ pub use seal::{CeremonyHpkePublicKey, CeremonyHpkeSecretKey, SealedDealing}; #[cfg(test)] mod checkpoint; #[cfg(test)] +mod dkg_relay; +#[cfg(test)] mod vectors; diff --git a/tinycloud-threshold/src/poly.rs b/tinycloud-threshold/src/poly.rs index 643deed7..561bd99b 100644 --- a/tinycloud-threshold/src/poly.rs +++ b/tinycloud-threshold/src/poly.rs @@ -16,7 +16,6 @@ pub(crate) fn lagrange( Ok(numerator.mul(&denominator.inverse()?)) } -#[cfg(test)] pub(crate) fn evaluate(coefficients: &[Scalar], x: &Scalar) -> Scalar { coefficients .iter() diff --git a/tinycloud-threshold/src/seal.rs b/tinycloud-threshold/src/seal.rs index f9d883fe..86900cd8 100644 --- a/tinycloud-threshold/src/seal.rs +++ b/tinycloud-threshold/src/seal.rs @@ -47,6 +47,16 @@ impl CeremonyHpkeSecretKey { public.write_exact(&mut public_bytes); (owned, CeremonyHpkePublicKey(public_bytes)) } + + /// Derive this ceremony recipient's public key. Details: TC-2220 + pub(crate) fn public_key(&self) -> Result { + let secret = ::PrivateKey::from_bytes(&self.bytes) + .map_err(|_| Error::InvalidHpkeKey)?; + let public = HpkeKem::sk_to_pk(&secret); + let mut public_bytes = [0; 32]; + public.write_exact(&mut public_bytes); + Ok(CeremonyHpkePublicKey(public_bytes)) + } } impl fmt::Debug for CeremonyHpkeSecretKey { @@ -626,4 +636,11 @@ mod tests { proptest::prop_assert_eq!(decoded.encode(), wire); } } + + #[test] + fn secret_public_key_matches_generated_recipient() { + let mut rng = ChaCha20Rng::from_seed([0x39; 32]); + let (secret, public) = CeremonyHpkeSecretKey::generate(&mut rng); + assert_eq!(secret.public_key(), Ok(public)); + } } diff --git a/tinycloud-threshold/vectors/v1/dkg-genesis.json b/tinycloud-threshold/vectors/v1/dkg-genesis.json new file mode 100644 index 00000000..b010575e --- /dev/null +++ b/tinycloud-threshold/vectors/v1/dkg-genesis.json @@ -0,0 +1,1003 @@ +{ + "anchor": { + "network": "tinycloud:test:certificates", + "owner": "did:key:tc2222-fixture-0" + }, + "cases": [ + { + "description": "All members follow the protocol; R5 is empty and no R6 round exists.", + "inputs": { + "ceremony": { + "body": "010161616161616161616161616161616161616161616161616161616161616161610000001b74696e79636c6f75643a746573743a636572746966696361746573000000186469643a6b65793a7463323232322d666978747572652d3000000200030000000000000000000000000000000000030001000000186469643a6b65793a7463323232322d666978747572652d31156f3db7c1a81bbf5e560ab3220408fe1e022a93e19b781609f7bafd10f779060002000000186469643a6b65793a7463323232322d666978747572652d322c012b5c4cd586196623b6f1cb9a47d81b31d15b42308774658961e26881d40b0003000000186469643a6b65793a7463323232322d666978747572652d33292d8377c411746c3c15e9a87d4e7ba9feaccebd703c9574b6c123defc74103c", + "signature": "06b4ec78f6b16462b777b83e661ebd1d06f1352011b45bb22f714ce08d9ffbacc7d4fdbe96fbba331659431d72cb4f85f4609f97e6c6652ee6f1b8c8923be20b" + }, + "ceremony_digest": "ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc576", + "initial_transcript": "13a43bac02ea3dd76a0a7e44f2b6fa49b68a23ff96ef71ee82344986710da711", + "members": 3, + "rounds": [ + { + "closure": { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc576010aebf81afb3c2fd60c5be289741b662e7f42225606b0b8bec23b396e28e51f3e13a43bac02ea3dd76a0a7e44f2b6fa49b68a23ff96ef71ee82344986710da711", + "signature": "8529daebcc8d107c1ca60e6fa27ae4816c9a7111de53fcb94a65ba09ed968cbc27c60c349a93e6333966a38bef3f4ccaf0ec7003ef1d11a6f4cf42cbc4cd9f00" + }, + "messages": [ + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760101000100000236010002b8cb9b05797274c12e70b8f63171d7d746d7d42971e6cce339a74eb54da283bf9ecc37a7e67c0cdbb17e6e4aafa6ef7411c74438257204a7e8f5472561ead2cbcd1f6830989b63663b3cfc0a0a83aa5a4e724e8a6e50ccd088c18ed281856eec823fa43511e29ba3187072a6c7eaa981bab2a929e9de3bd7cea9d5db9487a9cc8fae77fb931b9421a8a6fc643cf8c91c0624323e77afdb6b635496a0d78fe3a32fa02bc0d5508b414f8afda8bfec9cbbfcab78d3a76f34c6cd9a8f936a477f49000300010000007501c6e17f43a91de702f29a207a659fe500c19197c8af5723fb9315daaea317fc5d000000503495a04adb2deea3fbf73379ac99bfac168f24265b567d3bee7583c35cfe8b8b928caa7862c84026b8bcffb420f3fe4c537a6ed6a2b19df0394b1f3c6109e4390495b7284e5e348ee8fc90affdf0d97800020000007501b57b9821675b6c10277f51fe0ae007acc44774f55077d574f393bb99dac6135500000050952e744063b56ce9470d522e1075bf833353cadb38e5fca362c6c7413c88d0940985f6002be3fe8be23c8ee3e83a6189c5748f369a5c7e2d98101256d73abd0ba9940e00eea579578eb4906f87e1af6a000300000075018463dfc3ca9da6f954c05b0e58dc93f82b8a6f2f51e9e704576c5a94d47f0c2e0000005096abb821a4f401aa69635ad7edc78127fe7453c535b79758c8a9c5e159f6e59dff79c1c6674cf1db0b90db7ceb65e16c9ce57ab32b46dd575b21e6f1a4e2ae17f265edc4a01189ad7881df16d256c625", + "signature": "0e005122b3ae2b8dc479ac3c87a7013525e07bcc18239842816a28d07affc773ffb26d0ae6f180657413b9e717c2f237f72598c0f48c35bef85490090a8a2d05" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760101000200000236010002adc93f789714a0c2a761924f5c8b9282e77ca2271f0fd42a7490013f303e429b7ea7ce7e831a4c4d519849f5490650c2127c3dfbff34f4ff671e1f6e4d74d3dff500d4828fb519275d6d7345d0e55ce8e85c970f3b7f69997b1bfdc944709b839773b2018d711a1baa9b2243c89dab6f8dc7970b38731620c6999e41276ceb747f7eb7b88498a894ea37d998c6ee55f217a563e5e770da09fd8baa3c6bc01cca89ee7f2d0bc4e28964586eaf24dd24dae84d2b111e59e51f50fbec6e5dbdda5b0003000100000075015e0127cd2084859b2b95b8fc1ad88b1e2ccd76420bea31b5ce6d2a783fed990a00000050efa290a524e08dc0762d931f6573c83a727cc436fd2f64a1540cf7ecb617ea61b121eee92a2934ed20726987d66c9393a97f01d52eea233417423c40fe922871a01483baeda910eace20ed904681f7f3000200000075011e7d0d107fe482980fa32f2b362cb44032df727558bb76051c3f8a1ca056276a00000050bc749c6f49fb8af769af1da04778bbcf6ca38d4c6ebbf22c121124147f89e8842d59cef953a8a4974ff01b2bdc71f9dd73438fa4102f1dea02c422295d7b0e17d37be7c5577823e8350024e5f4cd5abf0003000000750155253cb203bf6cc5cc955141d62c4fdaf32b79f1d9dc4cc055a1ea6408d2ed6f000000505db8b47f231cd8e1830d90e449a2eba091b3937696930ee7cf9a02ec5b33883ab94e05022afb497fd90671ed3cab7736bcaa1e7735b2de63717095e30c723007073547ceac2d8861bafbe8d400900361", + "signature": "f25f590ccebcfc270974084f64a8bc3ccf9fb1b124be7f8d4cd44dd237214906f0803eabe001a16dc544f4e2d76dded1affa61c02c910edd8718a528cdbe7809" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760101000300000236010002944cc7dc866fb71180b8a3b06ba37169dbbc6fa4ac58cb629e618923d17b5b212e25ee65dc3625fecb716d4a2486cc7706ff423f0eb4b3cb77240b3d6e16f93b46d18e407f56d869841914c2dcb5c956dcb64b8b42d78cfcf946dcccdcc9795db20c4dc0dae9df1acfdc343682ce38a8d6943aa729b6c193acb33faec1af3e75cc9a87c6e61f605c3315391220bb52cd115bac2a0ec7b42a9d8db24e8d106fafe2eb1548a5050ce2b2cd0d895c7e26189312e947db2d8a11abb4d6aae1df8b730003000100000075014a34455acfa0b0e82e31d5dc6a30525278384f6a1677131c8a3a174d2744e2380000005071b3a41c15f74c8ae834124e4b1ed2c167371c8def6cfdcb0833c93f0a8c66d1d03030290b7fe12f6f302efde8be7944e51c46ce8e056c7dd3cba24dc2858a50a1c53969175e663ec2fecf8f800f7d490002000000750162c6ad6adba12d7bfd2adec882539849e67d9a4ea21d1b373b8fcc10aefafc67000000505ae370c1bca9e9af3a4226ca02037e9e1f392394660dc6fba75f61a621b85b2e97d7f7e07f365a3640e3458c068ec9579985ea9c61e59a993ace6c57621eb54967c01f1a7d3a9d73ce38738ec0d7c6ce00030000007501c614eb2a3234dfb44775ff69d8d9a189e15f2d4869397740db4579b5fb905a67000000503b32013b9d28b13d6a42acdf95d226f7706d00c27d8d53478bf25a4cf08d78e1b4c74c9121e052428e67fe76f7b13298b3eb9fe6e1f53625e02abc5b58425929f78c22d6c615079d2709f07c159e963c", + "signature": "1c07a7fe6cd10f1f911946b162948ba55f90842d08d37336588982856da68f53685db5187b6e7267b32f6490dbbe1702333f56b132c623ce872755ff4dd1cd02" + } + ] + }, + { + "closure": { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc576028b26f0948ed227976565776504f719c5debd598849617cd74a31158f6785a856d080233f299edea73b80a76dbab233e12214c1955e55b3c45961066d2dc91739", + "signature": "ff9922bccf415cd810fa87efc3eef15983a4338888cf01680e37958929c8ae0d03909c6942f4303f3c388528c14c643dcab074cd8a0f26ec2addc7d5686e9a08" + }, + "messages": [ + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760201000100000003010000", + "signature": "8f786d6d0b8b97bc2bfe295dd38f665ddeced09b7317f8d3ee037cd21c63b269af375b8fd48ceb73d6baeaa578d0a02b568d9d5ae8a06efc1a2be0c2e4befc09" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760201000200000003010000", + "signature": "810d90b56da243830327d0cb63a8b47736e9268f62384f63c686e052e83492812d02f962fe8f2435eea89b2d33fbb3833e9a6c1bbfa34d69ebe1c54de3826608" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760201000300000003010000", + "signature": "e186f4ecb49dfa3cf0e6d3281ed1526f0537dbfb1aa34b3195809c604a519b417c690f3a998d9242dd70e6ce07ab1f5c25c458f60d7f350ac6ceec5d21ee1706" + } + ] + }, + { + "closure": { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc57603e53cad9c41fc1ba68ce6dd8e05715d148b7ff100713c8a82cc43f8d5c7c11eec6d3c0b00e74df50c07808e1188ac65d14d73436b94ce83185f43399eef4736ba", + "signature": "265468f84d0a8ef43694a8143166ed030a5eab9f7b6d51d70ac3cfcefa67ed1415dee7361ad8ce8bd98d6fe70ca3d27763c102b3c2efa337433cc397d57d380e" + }, + "messages": [ + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760301000100000003010000", + "signature": "e26b56975b3fd57319b1a39473813de045e486681131d2151bcdefc18033b37e2d72f27c08c7ef5697d42355ade156553a9fe931baf67b520d3a0d115b20ce07" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760301000200000003010000", + "signature": "b345d1df29ae3e97bcfa169a9a4794615f78d62094dc9ec27b2531fdf92f8756fe72a5384e8a143cbd88e4936d606d26a237819f13c429c243880c0d759b2006" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760301000300000003010000", + "signature": "cf5bc0cd2648e0d4caacfb7fbaa6874028ffe0d5b994cc38922981fdabc70d4b9d5f6e03c9f66dca5ae19fec5a5eb65159c581bd7f7bff9cd45d475dd925040b" + } + ] + }, + { + "closure": { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc57604627ff7d0d52cdabe4432b6dd693993d9206fe1a41aca2e03f34f5e1eb86ca3ba1a6e1dc705568eaf71cdcddc1d5485820ce18d869a4fa223b7a302b853e0ddff", + "signature": "840ca0ca91eb71eec4652f3219dda3df785780c11388ee18a68c1a242e76abb5a345cf17e63ae764d01321cb055b549ee90505527c831bf2f7d09747c11ed102" + }, + "messages": [ + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc57604010001000000c3010002b992ff6fd5367bda9628627be657995c8dfb7260385a19a651dc59501a656d68f8e4918d1cb5562d75480c94906193cd16cbbb64750562ed0c98c62c78c57444e8e1a15b6ca2ec45563ed22a7220944a79e5f834e9cf2b4b664be128fc892a53b9e3cd2e5f84900704c995d56fbe76765d3732404704bd433e95abd356cfc094f62284ee2a7f93e04436c774fb003fdc0fca160d42ebabdb087da7ce1725c1a2332f691576b046037363a3f947dacbd0a5f1339ce14a05ad5da6bb2defaf4ca8", + "signature": "f44506ce0f858bb4b69f6cf0cce66439367c5420b841a6e11622d4c7351775406bfde504bb6f534890d118a1f021ac50f8e52e3286c43a591d08c0f56eca6900" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc57604010002000000c30100028f6a6c707cd945d7a8d5e0acd611027da1271ea730991ff95b1dbf55be655b34b1f5375bc802f061cc441b9b453156fc185e6365add2c2b291825491be92d06f251278a6252b9daa8021fb2528f92da055a3012521d19045719049c508831c1bade0f2c7f5a45b435198a8a9700a0120a05d445e3d301db9f62b0742c1f384eb9489591b8fda937cdd24801df71f151d101f7c5c1b779ffc92f3c67d46b2de1a526e86efb9c648057027a4d03572a9410a4acdbf55b386f672f7d0b565183b8d", + "signature": "f17e7458b133f2b73973c47149e2ab610e09c6e41e93dccf9c303e715f4b3a089d1edda270b2feaf6d88e7202d33bb2394195717ec276e897cbff99c2b1b5108" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc57604010003000000c3010002a805a08cc86b83c5d28e6f5c26112a4aed7290669a6105b90dab0c1fe7236cd7570522d5a88e1e1864eb1ec82c6568ec0b9c5e882e06da5bc7daad93b1001b78ccdd900d53688bfdb269166366a093ff9d8506182311b75c47e2b1d8db2ebbe1a6d8cbad099b0ad4671a3cf70fdbb48faf9aa42389dbcc6198afebced971ef0c595031c28727da27c343bef6d21c023114e3f7bf614a3ce524c971faffb093102e0dedaf43118774602c61b48c42e5aa5d8fa662c93d4fa367ecf6984823a82c", + "signature": "5440de821960437b66786335a033574c71e0cbf0706db60449fbb42713ca48ee074100c2c62b56efc1a782221d188250e6359b1542532d2054f0d5db3a1c5b0c" + } + ] + }, + { + "closure": { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc57605db2b6e5e9cdfa6f076b7b94fcb3bba670236e27656ac3939546935c072fce2a4972f8d63f27b895091f94cf92f86ec26ca6c42ecf7843686a2dd47c4f2a6ca83", + "signature": "d80d681eafdd6a5c47e73aa136c2e1e1d517628961c388e4d2e6ca507fa4f79bd88364900978ed8e86df34f56a4228134f86076de86e19d9e54cdeab030ada05" + }, + "messages": [ + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760501000100000003010000", + "signature": "25139cd9ab68fa245b6798c0d1713d5dda756756e20072d403cbddaef48198e740598a3794994bb8834d30bec2c8b3f3f440283aebb0ef9775bd792d8c10f40d" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760501000200000003010000", + "signature": "91d79791ad1f1d9f687a6e9408ed1365cb8a30e1cfb4e0fd060acc7365b04d96c2953972e02bd28855ffde6277e93968031a41b93649ff76cc1aa28e2072b505" + }, + { + "body": "01ec56614eda44fba6e64b1a7b0c58eef4213175fb6050c8d2c7c99c14d7ffc5760501000300000003010000", + "signature": "498c7ecc7a11d42535eaecf93817260672a12ee35a11776f9689b7b45646319d0459f826ed5464e398c3a08f275611b76930c627d944f049aed264f6a024d20e" + } + ] + } + ], + "threshold": 2 + }, + "name": "genesis_2of3", + "operation": "dkg_verify_transcript", + "outputs": { + "commitments": [ + "896cabab7453b2a12897f7664a0bd231ee54e1391820d3399cbaeb24dde62ad0170171cf64f2ca9cff582b56c54974ed119818836ffeb2e52ddaf2aa109a89ec4abad722533a668d5c9462563c3d0076163b19dd2a759e8094f677a21219764a", + "92a9b338245074d1b88282d7a4f7830caf860c84fc37c746cd236e1240cf5e96e14bc3925b7790e3a1d8fc3a6fba3b2f19d3996f1798788cedbb346a6009c62d99251fdb5ac81a5036e29ce1b5749152a83f9f82d94d38e0574a6d3cc0f8f248" + ], + "disqualified": [], + "mpk": "896cabab7453b2a12897f7664a0bd231ee54e1391820d3399cbaeb24dde62ad0170171cf64f2ca9cff582b56c54974ed119818836ffeb2e52ddaf2aa109a89ec4abad722533a668d5c9462563c3d0076163b19dd2a759e8094f677a21219764a", + "public_shares": [ + { + "member": 1, + "point": "b7da057b5b2cf058fe8a2cee59045adf09e4a4051ebfb982b5807ea29f338c6429029a4764ce22ca13b62ba1bc61a24313850bd80369707b56c5fec45ebf5a31b060b6c75ffcce34c1abccc6d7ff9c5d3cd4434859a78b108ed66c3d4a1a9887" + }, + { + "member": 2, + "point": "aed1a44dc8ef85a7c2ec8b63e03d2bf64cd709431c24b01d8712ee84167e2b742655607d872c6afb774b6c226d1c7d110665987dd280cc2471e3d22edfeff2a5aeeb7bb0fc802803e3f8012be80422b6a307af6e9c5f72568b5bca45fe9e5e33" + }, + { + "member": 3, + "point": "91964e1f115af8fb7f71751fea53d7b3783b8398b3cb56d2cbac73afb6dfe6ddf8aab3e8c977848a91fec27654776ed50addd59305895e675565fb10e8f769626b8c1b6a1830292cbf3276efd58497536bd8480247eb842c71b5793a701a6e90" + } + ], + "qual": [ + 1, + 2, + 3 + ], + "reconstructed": [], + "transcript_hash": "3e9150b05c5610427d76e12f46ab3b6347c465a5695aede6f6e9efce9947d7b3" + } + }, + { + "description": "Dealer 3's R4 vector passes only member 1's check; member 2's R5 complaint leads to R6 reconstruction of dealer 3.", + "inputs": { + "ceremony": { + "body": "010162626262626262626262626262626262626262626262626262626262626262620000001b74696e79636c6f75643a746573743a636572746966696361746573000000186469643a6b65793a7463323232322d666978747572652d3000000200030000000000000000000000000000000000030001000000186469643a6b65793a7463323232322d666978747572652d31862064d76002a77cf789bf424066eae5f176540cddb45caf025413492abd2a3e0002000000186469643a6b65793a7463323232322d666978747572652d320b75d6059b87d5920be94ce04a0e050d4bd8dddc565e85b5f5a4d9ed88218f500003000000186469643a6b65793a7463323232322d666978747572652d33e732fca52c12138bbf3060d5939e086583437f32e76f6c594a9c9d624981355c", + "signature": "c192c655b8322f7429a6ee5821a5a11869da863d17be234ac3879904ce6b6432a08580bc8a9c3939c1ca7ac7885a705997ecd425d5e947bb4de41e88d3f7ac08" + }, + "ceremony_digest": "d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb772", + "initial_transcript": "d74680fdfd166773ff0fb09f25945d93c5a857a9c8a4f1bee35a50f1769d00c8", + "members": 3, + "rounds": [ + { + "closure": { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb77201c01221bdceb89c2b8701fb49e2b32703e8631429435f5217a863719b3b0118a5d74680fdfd166773ff0fb09f25945d93c5a857a9c8a4f1bee35a50f1769d00c8", + "signature": "3f10190f7d2b91d69a0d3a8f1e3ca1cc02457a4a29e8634b6cb122797711be174d5748dc13ed7e7376d8e060ae04787e3266c81a42a747c609cba348cee32509" + }, + "messages": [ + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720101000100000236010002a5f198ee764bb4aa86b10531f2f04d8dc7f6f6c749f15323fffd56e241a79808fd21655df585124bae9b76a539e7d11a1801827a8c811a8dbea7f7c4ccb635a9d6d4e6146b3d8995715da14c0d23c7d941a24f5da51e9557d601665ed16919e3b091a0c6f5d3b719b7f8faccbbbee32cf8e9cc0deb78232e5ac2aa8c57ebfb54512816c542e60d391ba72b7f7825ed440f91c7459513ac2be3eb0b6cf4027e873e9da2b220f007ff7f1032293191a9d32acb7b26d50ae1593bd1791e9f84ecc30003000100000075017b87b1b2e23f648b3d7daf0abaf54268c4fe714cab9c9bdebe9a5be99bdc230800000050afc1d6430f640137baca65b44a63735c4c20b5a0d911f233ad08297026703f30e97afcdafe5a830ef8ecd9386bdc6b6c36e9aa62887ee47e2cd2f1852d342365623c8b5ba0eb54e49018e9ce2e8f9b0c000200000075015175806c4dd79c93d8c6b016c18c456d9421278ce893762ec721059f65784a640000005003bb4704f3eb7040eec0bacf71e44d8154e4850596005cac2d29246140acaf1bd1c1bc79fb4cb7605df0b3ee44c83d73b7575af848e0391b7e0a34dd86fd44b4d91873e3dc514e2be6789137a836a158000300000075017aa5fb2564aebda580eb8258db7a0df4845edcb47af5d14dcbb3eb5a0ed15e63000000505cd2f6c5172727661abe3e76715e36b261b0dca0c2a68a429f0dccc245970a6e2b5f4c498164428a0890575d90252e8a5240ff1c0d0e25be570335056fa0e5571c2f1d171d5c4e64c13ea753e5949468", + "signature": "742b4eb971b7436b9dcd113bce651b9a59cc2b89d9b665f619f5172de6e88f89703771623660d338124c0f781036af024efbc168b04472c3b54e8e6e13ed8e0c" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb772010100020000023601000299d6ce3b115c9a85766588d8fc51dad0edfab4d19656c6b8a7dd12b7013ec5f1db7f89390c683834746db9220912e6771933e09ea22cc9150566e8250f7b0ff4f0d623e13a1f53647f9c9af703c07de2b17acc6e1272d18b55203a1db7d8feb287b5d141712bfbc647814a42de63761ff1ebefa15b8540d62a5c5e4ffb1970d8010212acf7954885c45b1ba7fac5b9330a05d6c1168d70c2c47306a737fd43d4ede6dd0c85b702dfc0fce969b31c97788c226d146b20f91afca0a4288d0a36aa000300010000007501f3afc7027311727b94d63e968499163e1da7dc7fd75ffc5e7db61d1d0c7b865000000050d4fa70c415f79185eff18e192c7331567bb69b3b8f7420d2d9b44b634298ac4987733e287f68cd03373ba4bdcfac5a50959236f6ed4ab9a6975d6eddb49efb0d70a6ec7e9efec05ffef28f2a82a79fea00020000007501a19175e221e1805e0db24da3225cbbf9aea0253c03ab1bab163f1a7e2eeeae5c0000005095eb475bcf552036990920a494000f700fad1ccb7ea720a583f365631bb5724e9c3f931b788fb36b258f3a6e83953afd439e44a8452ac5c54577a5cdc10212cf47bb20fe5ddeabaa6d15a14d0c51079200030000007501d10ce4e8dfdb979d5f429cc9e9152708662a9c3099ef6f7ae8cbd4261a26bf3000000050fe910d7692e5d6258463b9f9f4a519c9264e9fadbf535d0595c771a6aaa4e52d8c2c8a73d336f4f262aeaa7f8790802810bb74a9bbdca6bbdf17998a1a508aa5e5fa418948e49108531ce31196ea609b", + "signature": "7105fbd5e876934bef72cfb5308d0e05eaada7177a5bd8d326f965bcf0235f99781ae6a4d18b2595117b017cc5753f163a6f1c257aea55ce15095d16678e5d0d" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb77201010003000002360100028aa7eb637c8d51ee07bb1800a29fe22126e6947301ed918a0aeb1aeca29eea531b31e4b8184dc7667c02290c9b6b06ef0332309fabb66cc3b19b04f37d8d551129bd1679806c6cd76028090b0e4e8be7e37ab4d99f1f8f4c4310cb2627cf2c358e34dd61a91bf90c16f27b3696f230f25aff11c822c3d01ac329b0d99e031d2921d99de0b053fe213f88a55de160f03a0740753d5105a67d805e6157445b27abfa6e9e03f225935d282c884dafda49f2097dcb313bfd563490355e22cb0d8f4600030001000000750126c4cef59d951b90e1af048d46ee74ab8f555c33f5c51114212e5b2209b38901000000501f29fad672014c1a5d8d82fa68c64d085b5e8a1cd06b1b25a7ae030737a7d424ac472fb396b7ea0c22a8fe3086a0e48135446eb4c980b381697bb12cdf88c7a0c24a0a63d9542c82f40db581fb94433800020000007501cbb859175fc326b3573a2383b54019013c419a724aee1a65622a2f252bd9697f00000050a42059c71fc99373f3debf03e22945e74470ed80a85f4dbb5d35a2db0df6ee98436c785fbc97a163621455a8893f6d749394d9c12c7bcf557502ad4df8fd98428adb500db3b2c1e6c8e450a2be63fc5900030000007501d1ef71aea01df7b1146ea12a58b679c5982bd728fd6c7075a55b47bc659fd52400000050ee9f62f53bfc304088ede2f66143c470d4d61d3b595850b45f6873f0bf50e12aed3686b7b035a1a468eea642944d666a64c6c93705823db8e91929fc4748cd3dc9ab14fbc48cde6829e1b9efe8d6963e", + "signature": "e077a208e758c10fddf16c33fd3ecdeac9f2d44df7406550f37a1bbb744f1b1064bd6a10e509de06c78f1a4a865e535e26657baf2fc298139b5450ad62a7b502" + } + ] + }, + { + "closure": { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb772021a6469d7fb78cdbe39dc164a11ac1a49ab3bf998ab08ed9ca43fb7f1b860c69713baa02bac396702a29487ef45eccf6c6886567436f033f521eb1d7b620ffcaf", + "signature": "fa74fe1076a3a2f0becb1c7767192f2396f5e51d19fa60396ee27d2f5b89d5f50a7b11a067062b3e74bb71b13388f1a2d5b0c14ea48ebea7e4a496843ee6880d" + }, + "messages": [ + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720201000100000003010000", + "signature": "0216d6ed0e091dc76fd0f404b69fa59f914ee32a51ac707a4eb76137c1747876c14423b43889460e2eeab4d842c3923e4917e1568c3383b3c48cc0f97afe7e0e" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720201000200000003010000", + "signature": "bedad677d8e081c24190360d3dd113070d3a6fa6ab4c994167898efd5a8863c201e5cd2a46f01489760e4743aff8b9a50b93059d177169bb52e44f937ca21d02" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720201000300000003010000", + "signature": "97a5447d6b66dc319d4f17b2e8f8669ce4e60fea833794d222b0993fec9d6c58949bad709319d9ebabf384e6f6984e2517c0d4180783aa6eed45ddd176f01d0d" + } + ] + }, + { + "closure": { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb772031b8eca6e0ee405f62f0d9d0b09e62cd2a58d3fcdd254fc275bdb22a7d06ea06cb5fd99ccdcb809eadf4e89373b2e497bde5418d2c36b923e52e3268dc7a6ecf4", + "signature": "b12a396a0c6aacfc827bc471c5702bf5b952b6b9d8769d5c3d72c93e6da71ea1ba698ebd75994b7b473a09bd3321ab20d2caed3965a27f52875b6ec65c49f60f" + }, + "messages": [ + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720301000100000003010000", + "signature": "afe3d546cad8713ed872c7ae49d4c5b892ce6f16cb1b9e98f873fa70987ef408a64a0b195d9fabd0a61be1735099e5dc0cf55d403e511221bfe5257d4a67e50f" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720301000200000003010000", + "signature": "ceaf0d61d7e9a08156af32457a84c6549bee3903b167bb02739bd659176397a70358fefbb5ffa2fbfab9fcfdb8977a3fe8da25faca6ca9ef7528153c58bf1805" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720301000300000003010000", + "signature": "3e10784a4eb2ff7fca72bf5d39747bfa2065f5ad64ff48662f5e5c8753ddb00e05141e64468a9ec67c8c476435bcc2491968644dcbf763b4cbc3e19585ab4d06" + } + ] + }, + { + "closure": { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb77204e9922ff6dba7b7a3da9e6ffc1a6575d1c3cab8ff542bb6280d9f9b3327c3d48c6b446bd0aee583e78c8df977cf49206a4612689251192118e2fe3291b4b7d35a", + "signature": "bf8f1e993ab3697949949e1cacb19793feb50bc9a4a315440433ad87bd20b31271df458ea6460e93c2d70b3c0be650b484a3347a51f8de0281b10ee5e0b12d01" + }, + "messages": [ + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb77204010001000000c3010002805516e9528d922bda3502cdc68b023f4fb1eb7ba208ec7921bb54e152fa4984c57f25b2113011c8a8ab99a0a4da00ed08e15ea796dde8ba1d61c31be4128c2f59c5197c2d9465c7865c8d9f37a2cf32ebdf4d6135ce2957f7421c68a833e6de99ca64116982443df98ceb1b42ca24a610fe3dc172d5c7dfe3d5ed72eb12e0dd4ef5b9db7c4de20827a76cc46b6d846f012104e274b4165eeb1404e5e6a7f229072d6bd937d17b28bcb50b27f4c9f844b76580f03b9368c24dd5cc882c8862f6", + "signature": "31f17887572d123f2e5d86383ae8080aff880b4485a3781492bcdf7c9c2494ed0bfc6c8e7dd7e5aa66c344ce9a4cd6a6cb06b7357f71d01285b2f8c1b1cff407" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb77204010002000000c3010002828ad929b5be62212508307cdb5bf003e86e63e880640264df9aa8edb56e0546569dc1064a1ab083e4ad5d97320d59e30e45eab6f1bbb1a9267eddcc23f081c311ae24a68f3505b824aa40325ac2ea6f23db61a6b0e5793b660bb49ba287ac1c922b02ae939a381ce9c0ede2167263f5870de83c854c81928c7fd73173f215de9516e30dc2a418a687eea27059ff697512ff130f86cadadc18b4d995e99e9fc266b3743cf28b6c8706fa3c2bbf3eda490e7d9f50b3f156b4bbd75f647243e36b", + "signature": "3be10fb9be1ac9902d369189f8432eb81da0ffac862745c5b987f9a3de272ace135f1d29a3b1e4bc0cbeed80264c1c41108a7f5f504f0aa341ec373c75e8570c" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb77204010003000000c3010002aefdebb8271056760540b28d8f80fd93fe927f9e8b447b8d787a4ba753c0fd7fc0cac5fee195679db1fa993377bda65d04af32fb441b021128c046a9b1538bb3725618a5fd2a1741625276299c4f3880532087a30ba025b24d8fc8b67050191d83204840053307cadf653661ba024a61452d1e9182856655da238001323341355516e3549bee77ca19300009737fd33806c7605e3fa103e7cd5b86185c2e82d4cba45e2ba63822fbf281b3d91d2633064248a2be3a8972d3c76fd019635449d3", + "signature": "45230f6ef87bb09c56abbb1394114c8e48e321adec90244ef032d4bb2a354b32737bbf88ffbbfd5ba91d6a33f6e5a561d762a224dc6b27a1bc523e817b21e30d" + } + ] + }, + { + "closure": { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb77205e7ab34a7e8e4d2e195960163d6a2e48903c471f64e2e1acef3fcaafb67c6eb68ae0b317b30a729444fe3cd8c585eb49f723dd768f47ea8d4b9e2b4ce9de2a724", + "signature": "1803d4706a1ed84560ac3f4b0abbd5bfc0327b46666dadf98f79ece3186aef86a70b27a1803f024569d87442b308dcbc300466a5a81de29514623c2a109b270f" + }, + "messages": [ + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720501000100000003010000", + "signature": "c5b749b20d4703403ebe2c1fe548ede97d24a5032e67ea6f7d06054707e1b7b2309118397850e12e0d69e9c4406f44df691a6b7e2272fe815c85ae4f7ba21f0f" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720501000200000045010001000351e7702fc3b5d1c23ec88db31827a5d7538dc706f03cd4231df946c24654f86440c1aacaff36ad9349d9dd20c2203bf58b33cf372c2ec81bfa4a028bb924745d", + "signature": "fb189e9fa80e9c8bf9c6e62977a27dfc1a39c140fc991bb4f2f9704f7a6f1089f07a6dbbbbd90492f026c872db5fef483ae2922a63e8e7e72470c73339c5dd02" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720501000300000003010000", + "signature": "977889930b126bc1abfa0e5759e9e5597a4c024503da1d200c998de6d2e4adb22acd890d99f2b2a9710dbb34e526a1e13ea314ca1c8c3264b98069a1e52ae405" + } + ] + }, + { + "closure": { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720690a2b2cc6a55221da9f60d27fd0edc07affa05f50b2cce1f6941cee0864c29cc90a284bbd15dea930232812bd30866e80a6b13af7a5a7e8cc2379febd3454521", + "signature": "5e05def3ba3597f6b601f24961749e72a3862bb2d0959e1f732e54ba4a19f006ed0b50e73ec29167e899183e5cdbbe261b14d3fa98eed2d3cab2126d259dc20e" + }, + "messages": [ + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb772060100010000004501000100032a20f656907c40e23ddad44693cccf608f7d0c37d45602bdd5dc69ef190ff33d5d73388d15631ff8ba887b4a31980a70c132cf604fc63a768307e0b44964f705", + "signature": "747eb3973a3a3d507428066c73a89360d74476eee77d5d9dedab65d92ffc32e42f6a0e3658b66c4b083a4a0c9e3bfc254788a735ff82002ce693d1543312e40d" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720601000200000045010001000351e7702fc3b5d1c23ec88db31827a5d7538dc706f03cd4231df946c24654f86440c1aacaff36ad9349d9dd20c2203bf58b33cf372c2ec81bfa4a028bb924745d", + "signature": "2ec3bb54dd14151760ef6bbd6efa1c84cc3c51c3409fb43e2aabf918f6c2e88e20de0c51314c9ec38cfa590cc492cba860ef600bf8ccb9965f1d1be2a6835606" + }, + { + "body": "01d6918020ea2fe4a8780e3b86b856d7f2deac804b8a9e3c2cedc7ca974c8eb7720601000300000045010001000305c042b5cd51e55a0c7c6f1792e0a448c3e0ddd30c254989661623967399fd8a24101d08e90a3b2dd92b3ef752a86d7a5534cf0e089755c1718c246328e3f1b5", + "signature": "11abaafa08bc83ec6d6ea615589fd75822588931c716b8f5dd6861fb6552996fcdd1b07aee0caad170860360ef601366fe214e07ac55b1a2ac4737db123bea0d" + } + ] + } + ], + "threshold": 2 + }, + "name": "genesis_2of3_selective_extraction", + "operation": "dkg_verify_transcript", + "outputs": { + "commitments": [ + "b63de12e3ff4ae606c874662385636db965833c2110fac9c7cb22cf3587e163477bb2938bf496c03a8cc69cd2197b2c2062d657ec0c743985f92d9b500b44a33a5c5a91706dea5ccf91afa0f3531d45ccd5d5505e6604f7d662d126e5e5774e7", + "99273f8a1338b93e08c4779ca3a6981198d966c67b4dd270712342b5bce0f06bc3061acbf32fdea67dc7c9d5c94b0c1504a458a3279f2d29b5e04e2af3cc64148153fbdfb1415d5278308a4eab8c23c69c851a52855141def3a3c774fb853096" + ], + "disqualified": [], + "mpk": "b63de12e3ff4ae606c874662385636db965833c2110fac9c7cb22cf3587e163477bb2938bf496c03a8cc69cd2197b2c2062d657ec0c743985f92d9b500b44a33a5c5a91706dea5ccf91afa0f3531d45ccd5d5505e6604f7d662d126e5e5774e7", + "public_shares": [ + { + "member": 1, + "point": "92312af844dcfaf1ec8b425e072321597ca144e88c88593ef8ab20081ab13cfeb0cdc3ec9fb95370ef86d5d0254a99f219cf18e26e3f89e0a272139a3a4f5953d4a38379cdb08ea81cca20fe75340cead1c606d0747d00e3084210d4aa54c580" + }, + { + "member": 2, + "point": "8ae9633ea7431859b491233f0c14b7505d254d87e191e7a6dd7cf08aa33d5b2ee0fa09de4d941edee5ebe6b90a5a59e91130a4880c1273c9635402d9fe3ffda578048ae8570695d12487cdd92d44457bf9ab66c5ba6571c370c3ea3578131fa5" + }, + { + "member": 3, + "point": "95f5429bdf077cb7802978f351cd79b1011088eab5a6a9bddd74bd7db8f824ca76acc3a056f068fadc881d5062ad11310dc8c755cce263b75d56afca5d5cb01dfd5528fed869bc2f45f81114d33723f4d348a2aab8406731d3b11a218fbfd3ba" + } + ], + "qual": [ + 1, + 2, + 3 + ], + "reconstructed": [ + 3 + ], + "transcript_hash": "19b6b99de67c25d936c50362dd0ad08efb19ecf482d29e3d05982ea33d47988e" + } + }, + { + "description": "Dealer 5's R4 passes only at members 4 and 5; member 4 files an invalid R5 complaint; members 4 and 5 send no R6.", + "inputs": { + "ceremony": { + "body": "010163636363636363636363636363636363636363636363636363636363636363630000001b74696e79636c6f75643a746573743a636572746966696361746573000000186469643a6b65793a7463323232322d666978747572652d3000000300050000000000000000000000000000000000050001000000186469643a6b65793a7463323232322d666978747572652d31fc07556c1b0394033f78ac77eac751a54424e2ece81ab90c0f2f5904c66bc57f0002000000186469643a6b65793a7463323232322d666978747572652d326937cecedf41d95aa7318daf80b12c7dc0ef2d728e19c321704f82e8f3500e670003000000186469643a6b65793a7463323232322d666978747572652d334c870e7760fab172f0758d751f2f551dda65e32412b3eecfd3d9dc7d3380ed350004000000186469643a6b65793a7463323232322d666978747572652d343c6a894ad08b8c4d371b01e2afaf3ee04fad9d62401b437175c64daf9e2a3c090005000000186469643a6b65793a7463323232322d666978747572652d3521911f82d2c7df6803df960e638b48abc4517ee45629ff1a2a4d0b79c5f4757b", + "signature": "25677afed79c84dfc92bf88f1df7e2d18bdbd2d51afba879c38d4e02eae3562813df8fc7fb7b6a5ebe94df328213f53f4642795f1e6912eb02a8d9cd1817ce09" + }, + "ceremony_digest": "6ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1", + "initial_transcript": "015b464ebae13bb31472d78086282ad4c8920529a6980fe72afeb17144e1ac80", + "members": 5, + "rounds": [ + { + "closure": { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1012a1d5281c60fdd141836ba74a49c97b044155e33e733b711e2dae2ad1dbaff1d015b464ebae13bb31472d78086282ad4c8920529a6980fe72afeb17144e1ac80", + "signature": "acaba6cd88e73ad73b932874aedeef2727aec3405a486889cf8c6d793a9340eb37f3d86c9fd4dd9bed35b52aaff303d147ee1e2f1f976a6fbf7f6f793867820c" + }, + "messages": [ + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1010100010000038c010003914cdfc1e3f575d4f8e65bea22ea24193216cb58e41bd4df4c9c693e6e6c1d99a1fb3106a1603e0e7143ca45cae343f102a4319bf32bb9ef6e3fa0732606b65bc113f3ada4f6e3dd454ad237d1637ac51b20c78b60481455296b0b21c29ea1b4a31f1635840c6e83e2e296275156db745f84e0af2f5bd0b2aaedc6a413d06330b39000466909be2ee3e3b3b7bcf8dff4020f32efd17279f968071c46eb5004b61d6afb1c97f87170330e904c304a25dc348f59f8ab158e6f38ef8abea1d54abb8065bb0d9a8d1b02172aa291bfc5d00ddc69a8375c0bd2a3ca1fc7616b7a660452225ef4a69bfdd1b8b4a2acc7faab160dae492d2225c7f43d65587adb59472d385df9815230ad319676c8464159a56cc9800bd2cad7c15157c5f1ee25461170000500010000007501fbdf8f1d1f935cc8d000e152239e9e44fb02c5c00a31fe142e9ff2ae96b2452b000000501f493723ecfba21c62ac82cc73c30e07c98533d40f7e3520754228017cb530e4316935782bb6f6385529efcace87afbe14141908c40fdaf640f82c1ba47dd1cb967badeadae80fb251551f014a2f0e80000200000075015b0e4777da377eef7bb5766fb4743bb2ec9cca45f5b7aae91c242e8573349b03000000506e5b19abf644255b398f768b14c5b9e592344253945011b8416d0f0d6dc41073f1909635421258ddeb61cea17e271bdbda03eaee10c6deff514bdc206f160e122f2c128926998b6a4b5fa931b4674c2700030000007501e24e22024085f2d86aa3421edcb6886c69c9f266eb8efd263d8d414b8771456200000050b93bce688aa3c9908534be0a11e1d8be6b638b28a98bd0c0140aca49d3f21b112825e162daa2c6133dca29ee232f0d6f267c1937df3a7ad99ab678323824b156760b5376e8ea8669401ba3f49f62b8e10004000000750193b5b5c50d51326df7621f69b4a01016b20d172c577f2a638741ab67d73260420000005033f2c9a1c09ebf75ee5ed44251468c9a1c8b6c3f1f3d2f1b4be2bdc0548e7076517103d36d9ebd9650bb059a07f46ebdcd2c2172d4d22b205246f4a42f9869e289ab5499d1aae529335c1f15cf2d4fd800050000007501a3a68b004b253b3534c011dc0297b5f96453bd094236edc2ce46f22e6ec27159000000500febd24734b51644c9e006761acf0cbc34c33fe2fb6f2444ed57d1ff621cfa2e897d029172ff4a15cae5f477397f0591e77eac82a2e61ccd2529a924e1cadf9c4e7f30dc4c4097a72f06352d5242148c", + "signature": "4f20fd537aaebc1f27cb8a844cb2543699b0bed3f6cfaa7805ee8d0a321c123d574b70091231ee53d78130172261b2b62a7659aabfa44e57ded92bcbad549801" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1010100020000038c01000385ba3e5e4ea0a0ffdeaa8caaf5a108d6c0209cb653662970adcb4bd841e87fd62586713e88125463a9ac42889d8029b10d882e46ab413f756dbbd71eeffe2cec64b4f012811c1f0690690ef6f1fba72f897b2565f5875626d4132950ca72a7658d44b9296ab9dc39253ea2f959fef0f8c31662d03de9a14c47e8f86fe77ee9873efb5b499c19800b74f50318bb31758e02ddcf6bd6e15162c247e8348d511605b533b4cf24912de8065f5c6767084eaa353cda7fb82c433b3eb41f05c4f463c0ad8ab9fe5ef3b1917ea5b4cb794f72eaa0777120ccae178af8bdd2a138bf165444620d377e467a3d952b84394c3144600596da86ce56d0eba94717574e852638198c7932c9693fdf9784c1acf11018ed23a52aef3987f709b0d54f42cdfef908000500010000007501bb0a09eaa9e5e26432f8d70c2308dadee4fb96908c77d717a6433d2059d1da4a000000502bea904c5f3c297a677812ee34190b15f0f1326cdad617567eca6416db5215575954a80ad6de116ba3ef9dc7f5d4ba50bece996f0b807ac0c828f3437258d3ab68269c0caffeb15d2e617f1cd5d172920002000000750161b52e2de261a4186dedf5dd7ce1e254e3a3625c697ecdfce125c9a0a9a1a71a000000505601b802937cff56f28dcd8156d99cc1864e37867ee76f1e23a96d0e65038f776352e9006685877234167c39bee46666c80aa680c5d76a2c5415a51c0c1e6aa07349aa31d2b3ca859e17968996a4f86100030000007501a68fd59c01271ea4102f680d2da8d1b8497babbe75e4c1b7c2fcf27d5b0e5347000000504f62bb2aafdb1b8d90ce10e0c80570721b2598a5f9e3abcf1578e20ba65db5f1198df5f52740c6a602c032c871692079d5208c8a39f9d4edac764bf200f5e3ba14f298a0d729f3d0fe4e34a2430176c700040000007501b85c8554ac4a9c19378040755acd0d54d6e457267c9ded86247f3afe523eb95f000000509b2e8cdc500516fb65aea9129707508549867d301886a417a6c0930c02374e78e5796eff44f82b7e84cee88eec6ed58ba967add7eaa4c5d164a48558dad65a717ae2f83b28ee25e33cb2ed52a0bd1f9200050000007501563fa8b59869b916eb9344f74529f5ca45046bc876db136310d2179c861df80b0000005015f5ee2f3c066e9feb1b78d526e3884ca3504b956a986b27bdd31c2453c73cd19a74dea1b6e9fa215892aed09a5c3cd9091d84cf5fb0c8aa27e836a302636be36780d2cef11aed62f5044b5e58033e77", + "signature": "679e5120ae3577575cc872fe284f7d50180c4bd6dc9a71c683283fb5201ab3bf18431d578fb6de74102518c30c57d093222781737c2dba5c272cb666ab062a03" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1010100030000038c010003adf9bdf65c88465398659a41c5756648d104073ec0798020bcb0bf9a852260cf841d2dba52759483d77514c9c6b534b204b4edad4af0c62cc14e2a60986a176dd28ad36d346036835268b31d19e1f00003ecae3e36c399363990bbe8958cf6a881977bc2c4d914f6aa11c6e30563577695e26fccd82ba52d2b04619d10d9c37b29af907ea0932a146ab1c6a4d575fcfe0058318d7a4ed2a83467ecfc5cb0193efc6d1e158bef5916a30c974048c2b73992d96fda66a8056304acc001de7d4cc0b8351dfbb7712b73090f54f8e781f5c6ad45355b573e6ede8affb580d777cc3a752ae46f6553d22efe6267f78e56ae080c6df27d1c4556c88a46713508f05cfc097c3d2d2b3bd9993155e2a8893c64ba2d467763ae8aedc192759345fd25af11000500010000007501ec5bba5063eb72ba8834f1f8d1a48216f061389bff75daf022543dc2b9b4861900000050d9db3a39ebec2a68796ca70fbe9d5e517511866f8a24eaafd7a4bcc72affdbac8fe069a6cba3176b65fedd7b933670076c6b1b5a888241df132e16bee505a29b3fdb603420b4520179bdc836bf8bd4ba000200000075014bde83446dd22901e8d6ef3b2ec3c28ecf82ac8453d0f22d2f2ca1b99921bd7a00000050cb510056d551dda7dddf511579fb8dc8cf48ec40e32b4ba9ef8dc3831992f253c15522573b1eff1bc86a833ec32183a1b58dace7407fc5b020069862f771fa2acda33841b10d0adfa715e452370cd448000300000075018da1338d6bb12d399851208cba9ead66f160880d3f9099aa2b17fc5761def57c000000506e825f66a096b886d665d022c0538a501df7eb5e5ed8ee23711042a1f6ee5b67e42953c802bd99c802317c9fd229c395c49098353e6924dfe3e8bcc34d832d0b88ef71dbd460a91b44966ac8492e6e7700040000007501dbf02fedf7f17357cc3b9129c8077cfbcd0bbd35132c71132256deed946d5f7100000050b6eee0378475cf4b21f2b01d3375f8057cf68e72d7aee33e5d84a88902e25720309fcc43f2587f8c8376a761e7300f7aaab1fad095d5d034d3b94d844149cd10d29779f18f19dcfd86b4daa7464404a60005000000750186d70d64afb459116a06319641cd22fbecf4764f66e7f6ee851812e74f90d70f0000005052ba114c53aa8e69456e3da5cb76f6ada31ffd8c1db0ae7ee68641fe47792419d387ce2153cfe74691440f2125e62ec22dcb989439272fd0fae476d0e3e27a53acd5d370b5bd2c3fecef2b11817cb643", + "signature": "a3171451fce0f499f99994070f771c5a4aaa2d1f11a844bff875ebf8c89da09adc6004c69190b2e0642a067038929d7701912a99a97bcd2bc20c39b087b6460f" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1010100040000038c010003a73efde7ff4936987055a3167a45e213a82d34fcdbfb002cf37aed4b142a039473470409436a53d8e79ac441fe1ecaaa080c2cb4540db983dbbbd9ff47fa138b6f422036b27708b4cacbef27817c82fac00b43535803069877efe94d3aa2d5b3963268a8d7b97598b07b91c077783f9a0ed01052d4227c565e19cd4475357c27575eda1fc0ef0bbf058d0e848887c6ec0135db236342168745a217cbe3c7cc4353554a5beeee02ff8a58c3e022a6354e4b177bb2b70819b575de4cd9bafa23efb4851077065fc23f02fff80e5c7bc6dea16855e11041d639fdb26edee885c60ef38095c96b212e029cbdcf2e4c1aaea80b31408c8fc8a3433d49a0e42515846a0723793944e50b50d57115d9636d8839de1b071cd013ac958116e5ef28e7522b000500010000007501d05669c88e331e640a1f2bdad4e6b4304fca3fbac8c66321243e0307499a8966000000500b59a0ea8af14364842e1e0d8e88d8fa1e71b783a54ef8cbddc695a2e412248d5f7d34eb3f31d02bad74c050aa253779ba325131637150ab9fb64887005e7c83a2e4dc11066dff6d230c7dccc2cb8363000200000075014bb04b95db5427cf4fcdac0aafe0f080c2a13ce4a7773794f38b1709d373d62f00000050f514b376685496a9c348b45c93b89f1191619b0cd938f42f1598a9ce90c27c6440773045ca942ad115f3ce4e89ab1b17ce0be846d985cd6cfad88268771ff3b4f094f7c4a1536fd515f8e4f796ff6d0c00030000007501185d962c42d3eee9c306086d7ad96229ac63ba21efac982a6e4095e3c5bca01a0000005072582be33f6f5598a4aac563d3b941a91ee84a4bd3f64b3e207a165061e5146c4b301940f6a9c6a6a03b986233f2922c9c166d2243a6a74ddf911ef71dd27dc477eebc048229a07a9b97f271d4d0e7ab0004000000750107c12c50ae9236f68d3e64c4618fa32b283658f2344e809e33fb801c3f178130000000503b492d0f45725c70d208b461d31593dd46499598797ee3128e27726e943cb54866cb540fb805da601939059c8354bcd76a0d12f693aeac2eb06967d4c244c9b95f18f94500c7e35f36a37c2596488bc700050000007501e67051451e1d55fad535a979f9158c336e08144d45b81d6b348c2540a6e05412000000509543be4046e2a1610633096230a290cea0915296a5aa347b365bd6b44c74b74afd8ba95c7a85ac5d1f72fc3f28d725946c37e3a87d9d23ac9c5f60b9debbec18b6d9a4409253abfb696a21fb435aac74", + "signature": "c93c94d855eed53833f659e4c400d74f181a66cfc4185345f5405e06d68a4acb206099f5e03585729c54bf1ca22354ac6a4ffb310f093ee6cf2b42863cc08c00" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1010100050000038c010003a5299847600c3abd35cf0058d64e096dac636ceba3e7bc174193708614bf205027ca1e0ffd402d03d1cff02d2403e18e0fe904991b305c24b330c37621cd9c3e2c702c3b28256aff2a41db732ae23a94e654d8f7fa49a52fe579dfdd906707d8b5a0b8c193bf078be791a9ccc1ea734273a48631b2f3edbc44819f4f4bea2004fe5270c15c9c43bbaf40e420b43c232101e7891e7b03cd1245b5e06d43123157dd5b30f4082d3c9556d4005001572cd7fd5f57da8956280c07d29ba6a8952b1ca6e0471c4620b264f03ea7602d741790135d11b65884ad2f314d275b085c7ccbe8e2cb6791594d0e2041c9e75b9c665f0e681751daabe3b360b6d7bc47955ffbdc26cad1088b797aff76f491765e6a1c4b2becc47282a7e0b958450fc7ff1ace00050001000000750187458407de235cf1645df66e38fea62ec407735178a0a2fb06e0de06c2e4e00200000050b460b1acab4bd88b82ebe389f6c2b6ccb60658e041ce39d0b026643b0d7fc0fec440a657a57763e25ed4cd568c3c830e3e6d009c23ab66958b1e7b878ae60f0fc33e0d7c328fff61a904dc7a5b764f5f00020000007501f04ff885215bd72b70daca5b4fe135fbeef9ad3d55e9294c821d0d183a823800000000501a5f4b991e7c76e3a62b7a721c8d090c862b2ec95d061be6f1fa09af1624734608f16331da02f93d0c6e42a02766d532037a45282503e19da69d2921d52f58224a912f91909f72e1935466588bb3db39000300000075013338313bc604faf013c9bd38963f1740967534b40b14f93bd32ec618a196da7800000050b2135ae0265601388ae5eb695d7f7cfbda71cb61cb08ceae10f19eab1c056fea556ff1d61b9423810a48fec9095d41b18f8e225edeeb5c35813d719a6b92a627f5d5aef442f734be80eb97d24ed49f2f00040000007501dc123512d15677a00359165e3b2d167f3539565584f3439fb33fc2254f8cab400000005062298be3cf40b0cd570efcde33bd087068488ec1965e1e51542d835238383843c08d0969e876f9f9ba69a42737daf4ad02d99707ffd223c4eeaa57c393efd834cee1a5adaa634fb76f388f71834b27610005000000750113899a40a729c3fb8b46bf096e52f55366f5517218e31d7a73fb19b910e3707f0000005016aa80ff299de371d642615e7c41a006bb3fa372739d09bde1f96664619a9a09c764d57615e2d38953ccf083fe22d5e0a83f383c93975206090aca9df786c8dd1413b973ba952b411b1903d026ea5bf2", + "signature": "61e8f34abfb65a714f2deee46c1a7b5328ad61e338bbed18ff257a7d68278462a0612971b6b140a6eaa2e6434f9e9005d44d26ffcf04db99f3aff34f8777a505" + } + ] + }, + { + "closure": { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1023c10720e7890f14e6dd6ac50a09f647f7cd1e286ac50a9efb59c197b732c5c73019552ec7dcd0d0bc6fc2885505071c0c15d7d990e6b3dd7a6bd4799d8670366", + "signature": "3fe02bfb6790592cdb327e4d9ca4ec5da4729e4c854a325df82a02203d615e8a98c339b6e131ce428fe78a8a766d2965029b0f3b41f9b20580fef0308b30c506" + }, + "messages": [ + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10201000100000003010000", + "signature": "7474c7775bd5b7a6a4b1fb25862faad2fc9c7d7de13b6028643c1d708a59bbeacb66f21710aee0ad670b554aa01d83477894cbde40e58d31fef894af7f65150d" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10201000200000003010000", + "signature": "11a9918a3a835ce2acfc88a53b47165805c90fec4c2a3c3a8b729832de3780d6da22aa3bd55171799cc27831d100b0d9cf7c751013f02a546417fb2ffb401402" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10201000300000003010000", + "signature": "16096e9588dfc247fb47e63cf82497b3809ba751309ebd9127feebbfe758c2720acea749a16437fca1129279ba6298feefe231850d9caaad47b41b676c5b480f" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10201000400000003010000", + "signature": "1dd3e9bdb7bfc648c73d3a3ff511d3333e2900703bb959833b19bbe8f8f8017cc7e0f31adaaf945211f280aa0bb25acdccaffe13f0f5868bd5c5eb7df7538e08" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10201000500000003010000", + "signature": "7836d4a4ab5df83e647f338ee8a8ec26d6f679e2c5aa12fb127b0b25887c4fd5d34c0f4c371f901cf7e770adb8233ff2369c192de70ce6a6f95a89e8f5ce200b" + } + ] + }, + { + "closure": { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10309fc819a065396defca0c0df7949efee3cb195384913b0815fe3c1ab89e1f630deb5bd511927d887f6cd0777395d8e7ae096f0a5f6d5ccec5d785f1d18b5ca44", + "signature": "4ccb756b93372a02345165aade98791b2629550223352d1a289ed9adf453fcac1d32ca0c6ee350a809bd812e3ab824e2179fe6941d0ee5f87898797bcd03f101" + }, + "messages": [ + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10301000100000003010000", + "signature": "e25e45de0eb17682da2ac2e3accc4bebf9078fea3ddda19c4d2c6fb9be4b693c22d81d16aad5c232c9562366a65ea1e7badafe2d49f7958a2ec3cd2c133f2c03" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10301000200000003010000", + "signature": "ae4b5cd93f2cc9bb47bb49bd3c6c07ec68423736e21a63422c402340bcd450af8e50b4e91260641c25e47d3ee20aa48f427ccbf386f77b79b3e674de645f610c" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10301000300000003010000", + "signature": "405d1de17b6153eb6c3ce61c443b1650c1104d99f005e929aff3667339d930a6bb09e8399cd745697ddfa0bb2a3b2a46169ef258e18eaf68c246f8a4ea19f40c" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10301000400000003010000", + "signature": "45f969f6b1a139ec06b623a902e5a89ffec1ad0e0e422a1ce0f8e0bfbd4a3efa618d0ab4742a29d814a19e007e9259b3b6010fceda9c8b56ae4c0bbb462b0509" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10301000500000003010000", + "signature": "3411e7f6559de533359add64ddedce654fa8b1c433a3e4434318161764fb899de6ab6264a4617005122699f584e82d44fd60aa19c17ed9079e9348a1c16dc60a" + } + ] + }, + { + "closure": { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1044d39edb7a1c54990e61bcdc2174cc7d830a41bda93b68ae24b410d88ed4da45370dc529cad93f96aaed2aa45d8c7bbdced7100af9249c8e349cad863d993c3a8", + "signature": "7331e46435cda56c066e798bf34c6f502d453bf7a6350dd01c8ffa5b164fa387e6ff7be2af378d678b4486023a788898eb72870ad4695300094de691e6407d01" + }, + "messages": [ + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10401000100000123010003a35c5e7296b36e8cc85f32436fca002c10b4bb4f4656e7aa24b460a654e606d6a8f89a15d72e2eadf3a14adbdf61e7ae192fd1acc48a0130905ea4baec2af80fc82384aba26ca6f06437208f4d6c08d88ddc7eb08d193693171d2e6885c691b5a3d6497012adde5dfb65a77f0edad1b3e4f8663dade66e0bd5788fa5ccdb3d6fef9ff5669165de46f468dd63245f627a162ec5414dbd99d6ea4ea78b812199853bc06aefe577bdee3ea5ac434c138f5657f248f2b3e417eeac62ab118b7d50b7a1af4180182ff24e5400859d91554d27acdd7740c4143bfcfd26233b9f3b5981cb33c397f17ea73f3a958b9808b4c0ba0cc2ac94c558007ad703c80d6e5b2ca037289b7763c80d224424856fd47f5d0f3bd73c4e8602169ad1945fce51c52417", + "signature": "1523ace14f56a8252459d02647d9e8f5b4186dabde209cb6efa87db90847c4d795967858c14811181617c885cb144ab085122f1b938844a96f2c4697d8ed9b0d" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10401000200000123010003935168bde52049dcded6d62b0172ff07beeca2a32bae97273823169cdf8f15ec6dd0a6c2f20d0d9d2f7e801541ac80110785062c4fbda64f7df627ff37fe469cd8ea2334ad7b5ffae20cbbd5f2388c1e4b95d456a6f9ae13d913269cdf881bfcac9cde3533025637ad87abc75d8df905d0c41272bbfc2f1060b99bd7e98d7b3f5d7db249775f3690f8b4ac0f60b297b603dac992ff533eda2af61f54e55752932108ed751cd76e78a82e8c928c710b73ad11323acb896af1781fb4fd24bc5082945a63fcef30ef8261e18cd768ce49013244965fffc01557158ca04e97250c6760ff4251d01624331e8a37f77587249819003854f17ac6b574a32cb8110adea0a8e17bd81ba66c68306030aefd5883e7fa003e0066015e764f566869519de98a", + "signature": "3aa745a99a34938fd785f665b879343c453b14ec7cf5eb31e80ceb1e66fa803ef95f8d334c6af73a75d36095db2b1fbefb80ea26e0d8c985e3b2aea7fb8f7205" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10401000300000123010003b7003ec95300ed39dea352981a4a5c8e05d8fc44fef8ab8530e88cbe5620dacd3ea86a5f0c68feae432c689b68b8f6110f17ebae478647e86389b4291f4863b40ec6c2ef8503664e92ec036055679e68c6db806af47b134b37b6f09e8480313da599d81cdd7e32dbc4511e11ce4ce9c304998b9338c475b10f2348d06b41e0d294fab8875105999e9353575b79f4fbd00cf1966462032b48b3628690cca7f70e8e97cfdd8b347da7f7fbdec83703963aff88b0d7eed3c21c696efd7e39cfcdc4af304a233c0c52cb55bc4d7b88c598854000af468c69a9d9a976e47a27762a740a233c46f004f86da2ecd46a4dd4e7df046be15250deba41afb59e255c29a7076c4c81e1b4e995c88c935703e8ca1a5e5e519ed06fd35d6ef43c6efaf6f5a596", + "signature": "437769f3d4f9b2e2e92845e9020f2cfbc5480e22b2e734fff2d78bb73579b9347882f0f385726b18f86dea23dc5299c3f4f9368d54b5646b5abdca4170f53905" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10401000400000123010003b2f9febfc6c9c484086c2c4da3c2d03de10cf1c500da345706d29c4824d30c81129eaac13819e36fb2928215eb64f4180829c92eec4a6b54ae6d4342178beb2e7beeae2f149839b329648dd34b1b2428187feff19969f76cc220f49f0d0a2211927c051c566ea268bb3f8ac9c06fffe4999eea2a3da9d0516316c38b7dc5559fe9226b5561362ba012a5237e8a03497e197b38e007d1e2e0f9dcdbbbfe2753a54b7aa4c9d2626f378b0b5a4c460a6fa781ca461ef297d41f9d891c17d1fe8682b4ea862c0523d43d584ddc7bfacfeeeb0433ded836719fcc6e1b9124d7df888196171873577f9f3c33bdc5cc077b4365191b9d610931f072956bc2ebf1f2f57e750c5c99daab5e613dc9c23e64a463a7ed6e4570ac9391beb653393fd5ea3ae7", + "signature": "d217fe7ee590224122e9105e280e9e02ff60f573e6d79b148c32237d434aa33554f3351a1daf176a73ac36c26f14026b207e6e34fe7e45dbcddf5a55677b2e09" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10401000500000123010003a5c3eb2da85dd092912538499c6bb751ccbcf65b80bb9fc98b0f657a86f1192a0d70d1c755af9f199c1d913d97fe1dc916a23296e3e1ede7d96f36ab3555489100086eaae81a40bac34278af7fbdd3e997a7a0c2e686e71ff6e734cab448d7caa5db7e6797372b44a8e22a81ad4ba397d5753f6b086494f7bb1b08fff68c1217da5c8e87c450462d1214c57d9598d86b1621b481a4808f8b64ef125ea0a6cbce3c511a97bfdbfe78824d6667dd3c3cb6078bd6ca632185f394c3350da53f5e758fc2cf7921f8c2d758793f098f18d091f2b6ea0e9ac6244e11afed2302a3b56d94490d722869148ff3efc94c470ecbbc083fdf305344a7e0b58dbfa2af96fe925787064d23e8101ee4b4d9a04849edac18f0e39d73ba9039067c678f86beb111", + "signature": "14d81625e84e25ad4b68f0158e122c211e787e70b8f349de5c252cd10cbee40bfb28d76e6f9b7aa80697b8f29c8877760a5365d8e1bea4192270b719e9e10908" + } + ] + }, + { + "closure": { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1051f7d67efae17d07d7326ffb8c186d52b0d35c82999b6b7ad25af2f4292c09a4053931a723ae3fbb61a39bb13aba21e88ec77a8bb6722f53f8d76048bb0733979", + "signature": "5cd49f2fedfd57dcacefe4e815eda7f43292619e3c389f6f3712950aa412c7dba749e1c4c0a3b6927fd9fa3739cc52431c168e9500cd7f31c4b3572ab5c8f604" + }, + "messages": [ + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a105010001000000450100010005210c13142328a04e497807ff567d35c47df3233a876bce66c3a882c21bb796e5704173d7becfe027b1b65fdd6886a2cea5298a376bc97f44c24dda873df4103e", + "signature": "ae0e3cd7042074a5c7211ff06d5d97735cb940b30ff6766fff256b0121b6be5699c38e25db85a3e5ba5d59bacab9b43327d50fe4f8f959bb37e1ea0e69838708" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10501000200000045010001000518a8a7e98021c65fe0a308a5ee6f15cd0ab03fd82c8b89af21d0f4d0b5a0df5b2a0cdeffed99206818fc5128315e610ee8b3756b1d5cfdf16884d578c0d8cc84", + "signature": "63124d54e544f28b4f291b2a1202ed22d71f55c34200544539625fcd3815958932c914fdb9caaa8db65d786555fdf397a430c881a1e333bf2368e82638bafe0f" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1050100030000004501000100051458ba171f4dc22fbffba10674ae95891c0a75db55c7a3a53f93568c394449762f64e113a2d6e5848eedc383fc22fb3def0f465583e9144021799fdf4c0c34fa", + "signature": "f2475f51328e484d501e070a03bef94727e6ebb6ed273b0936e2de7440924451652bf2df35bdf999940b76f0fb255f5e12bd17e70fc177d05d669e4dbf81f305" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a105010004000000450100010001000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c", + "signature": "3de59c2e68081fe7064885cd97c89ff9114049ee62918d4d8c639496a138465dd9fd4e06c1006c31ea9600c4e434259d4692d994d0dab75748ed605fb82a980c" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10501000500000003010000", + "signature": "8b65e6a75cc6260b697872edc80c678cc7cf2850bd6480e73f413e3b17d37edc70dd3045cf1b39f5018c96f5d2d919c5ee5fab7e6c15a903ba05074cca2d0509" + } + ] + }, + { + "closure": { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1064af37b2cacf191656b09a2253040665d359c2f51eeb27e4664681f194d52ea4215e08dabd22c0b223ed31d310b7b0165d02dda5aa907e03ae17004a51b903d10", + "signature": "23b0ff783f224a7e5d3433d923a00d705127c7be9a05c9cc3faa2f676f2951e92a97e809fab708f7120e01abfd3232a235eec9581fbefff8718a17f45abf940c" + }, + "messages": [ + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a106010001000000450100010005210c13142328a04e497807ff567d35c47df3233a876bce66c3a882c21bb796e5704173d7becfe027b1b65fdd6886a2cea5298a376bc97f44c24dda873df4103e", + "signature": "14b0188f1d57a1306f0829410403ed96ac874cf2a8a88f5ea3758f502ca4b0c76e0f5cb7bb74b942b53036bdbd519d3b7f14ea850c92f28e7500500709302c0f" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a10601000200000045010001000518a8a7e98021c65fe0a308a5ee6f15cd0ab03fd82c8b89af21d0f4d0b5a0df5b2a0cdeffed99206818fc5128315e610ee8b3756b1d5cfdf16884d578c0d8cc84", + "signature": "1dfdebc12b258dcaf874417a6faa11fcfe7c43cd9e1d020b72c80c4dda24815627ea449ef07a60c117feddc09bf375081b37fc0f2d3b8f7a1e709d0275346f03" + }, + { + "body": "016ae418b46861033d191f2a9a499d98374ac133c5493608451f4a3cebf8f395a1060100030000004501000100051458ba171f4dc22fbffba10674ae95891c0a75db55c7a3a53f93568c394449762f64e113a2d6e5848eedc383fc22fb3def0f465583e9144021799fdf4c0c34fa", + "signature": "6b20bb2e0e6ab5dfcc816b8b1c0c5d9f371381e7964d7e9f095f7657d1815fc8384aa8b482be4c185e79f7afa1089d159dacc33d230a2471c5eb504bd1de6a0c" + } + ] + } + ], + "threshold": 3 + }, + "name": "genesis_3of5_two_faulty", + "operation": "dkg_verify_transcript", + "outputs": { + "commitments": [ + "a7bdfc4eed60f7cc3b1d0ded7a911f7fc2b12339a23208e1cc6fdaf65387f079ac6991ced78ef7bfa98cc4e9c73eb2830da5682ca9c7eae6326fc5e42699d15ff450ebeae477268e1541710817a6c1da37c5997f814482e102bbd1b033be3391", + "93a8dfa3e80b18b28fbdeaf34242b47eee9b2285175387f1020b98d2e08b28d7c7e45edf6b8f88dc683c64a60d23d336132753e89a188da4ccdc191823fc8eba60bbed6c784c3b48c41eb1bcba5b5973693f2b9badb7e44340689a1762c83c0d", + "84d353694c58b45c8286ff712017a5bb6d00dd35509d52d35a20a0c3d09d36bd1bc034fa34eaae0d15a9e76d1b574464031babbf8d25a8c18ece182e8adaacc884538d92432ca43fc4baf2b45d2480e414dab99a0e036542da73e42252d237b3" + ], + "disqualified": [], + "mpk": "a7bdfc4eed60f7cc3b1d0ded7a911f7fc2b12339a23208e1cc6fdaf65387f079ac6991ced78ef7bfa98cc4e9c73eb2830da5682ca9c7eae6326fc5e42699d15ff450ebeae477268e1541710817a6c1da37c5997f814482e102bbd1b033be3391", + "public_shares": [ + { + "member": 1, + "point": "a4cec6dcd968988bf03583727c518aa1e558bd554dcad286f848d433cae8494c60e9299ae2282b82e4668fa4868f4bce159aa98ce34060402fd310da51ad81b2a9a2c0b14375554c2296d9ef1184d207a086c4f6e52d608ce2f41ca70010dd51" + }, + { + "member": 2, + "point": "8543be2992e8e9e4925bfe57230a2ab4dd17e4a22bc965a6e93cd1f59c434ee51a077d3c30797c2df93b82b6acd4ddac04fff815db14e8be669509ae894427c57e06de80322c2b8f7c9c7dc87cc920132a61bc60c1dbfb9e3390bf37ea6fe3ff" + }, + { + "member": 3, + "point": "b32cbfdbf7da7de66c33231b5c2db65198ba39ee1046a0df364d313b970ce1852a4bef0d302491ca9bb45ba497f6e0e60526ad16c4f293f1eab5445d3b5abd38fd065bb04b28d90605fb789e9f057d3bcf969b96c3ee6cb019e066638c389096" + }, + { + "member": 4, + "point": "b9e2d193ac2a712ffce1d75da65c57ed04995c36d6a769b2632731cd7ea88f4a5747ca1db71f0c7d1ec3dad46745151a110f547715aee3c243fba6bb75ea5f8424455f2590bf6e0e6175a4e799e42be87fdb41f6d2b63afedcdb916ea6071e66" + }, + { + "member": 5, + "point": "a3519c35b4b956ee92f8e7170c005e9cb7efad87aeb66aae33a071d5dfa1505c93cbd4215a6e8ae55c61cd400d8b46a20658c0298066a2fe0e5b699f1a1de44af056a93ea7362014d50ed7365ea9447140d810220da38e20cb9de37e10a0a991" + } + ], + "qual": [ + 1, + 2, + 3, + 4, + 5 + ], + "reconstructed": [ + 5 + ], + "transcript_hash": "eacedea49628473d397d15f9103541746e5937999de9049a3725c62ba21f9998" + } + }, + { + "description": "Dealer 1's bad value to member 2 is answered; dealer 4 sends two R1 messages; dealer 5 does not answer member 1's complaint.", + "inputs": { + "ceremony": { + "body": "010164646464646464646464646464646464646464646464646464646464646464640000001b74696e79636c6f75643a746573743a636572746966696361746573000000186469643a6b65793a7463323232322d666978747572652d3000000300050000000000000000000000000000000000050001000000186469643a6b65793a7463323232322d666978747572652d31b8c91ecaf9cbe607a8ae4b7e09cddd2a5510f67dc8c5bce74988585b73d395210002000000186469643a6b65793a7463323232322d666978747572652d32e1f2072880b09c007f0ab3358b6584f0ab4db09442da8ec185cd9893998d09750003000000186469643a6b65793a7463323232322d666978747572652d338bb953c9d9eda57753ef1194359068a100ae78e13c0754017edbd52f8d4c64160004000000186469643a6b65793a7463323232322d666978747572652d34ea7c95d49be79ab0e89cd25cfa528be77c319f7a7afaccb8cc9f80bad42d7d3b0005000000186469643a6b65793a7463323232322d666978747572652d3569d8c78584d182d9ada31836b6f2f298d5e96534b5bea1381e47d02816b44734", + "signature": "43eba7360f09e41f6f46cb56201f66b6411d84848c28af5487c3318d52c43d49c4bbb1aacacddf0ba34395e3ff16364453c6378a6147f0cd92464839a2af8601" + }, + "ceremony_digest": "990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f", + "initial_transcript": "409b925ccb7b841e470e0fe673a0b66a219587e4d96672ba5136b03db620572f", + "members": 5, + "rounds": [ + { + "closure": { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f018311d8acb0e65386351f318608264b733f887ac0b4660db50759c6507272f94e409b925ccb7b841e470e0fe673a0b66a219587e4d96672ba5136b03db620572f", + "signature": "fc1e59d805c798a3cf40e2818bac639108464ea4a476a6698e642c0742094a7eff0310e4e2b49643eaea90cf48575b380027956a897b09392ce38bf06a75a40b" + }, + "messages": [ + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f010100010000038c01000388cdf455838f4371235e0b35300a49c7ba6058bff2879861434195533efa099af6b38b55deae2642892ab5dd2b2f4c411669a5a3f8b12064ea402220d92007d599026b335979465fc06ad6a6bb9f4da056a36545e5053308094a9e818cd9ced08f1b0eeff25ef896db95dc6f54b392f6d3c742552f30ad1ed556b417e693b5ea3abbccb1ac538f8b9acdd6dac455fda907ba023480283601b6221c26c2151b7c4d9d03b48e3a04ef89d638bbf610912c298623f2aecd703c17a890c358ad9d5a84cd2e241ab05e14843db24af64b148a4b85a695a1045d1dd7446b757e83784857a95026a0a64104941eda3170fb06ea0d2edbc9373ec562a8b7467bb5dc00e38306cd16efba54f3793f335ff0d4069e9c14ff2597c2ecd4415c4d7b4a5fb909000500010000007501123b0ca1e1a369376f23eb42c767dfd781a750615286cbc2d49e07ad6152005500000050760c5a84ab90eabf9d153fbc27a6888f6a18cef6d458a0158a6e4aa90320171c6c36d33349e83ee9a025dcd6889ac51475e38bd5080137f37b802590087886b007cbab181956a5a84ac86816b24de95d00020000007501e085ee426688d9a6fa94ae50b546c7f502d9d1664724f0f31d1feb35c95db63d000000504594e59787716862e65bba08cb69065774a8e4c63e0baae3abeef5b3816c0372010f5e7bd206162bbb30ac1046c1d82c6c53280ca62f6b1abfb11386251c2694f02ae9ce966651676b065a9f6ba0dee5000300000075013c1a38297d91329db8937d2874808789fc37c40cfb488c4ae37da0b9204e407800000050e878f15b8f6822811c7d05c0dbf76c7b5ffae65e95a0410a6ca04b2a23c3364ff37cae39b3c66e25f155900d219da014061fd7bdb5ad9f6b90548529ccdbeb79e9f87a8ff2e0663fa5cdf3199d6d6c06000400000075011c62d333fd85155f9169ee84bbed3697fb74948d8ab1da9d171fed335cc5540200000050403c8527afe27bcecfa7d2037b5414d4c3409b5834ced5c458c42e985edd2336d5d502b8e6cc7f5fd8c8a2d79bab6143c04e26767c15c5e89beb78d54ec2c9553a3d3daeb5f1ed2689c24739e217a7640005000000750157e5d7d2ec191c7967d3b4467eecf42682e8746231282fbea1c2d77d2e081e7f000000502227819a3b418e32223cbde7691d15697aef5d0b0004a0af0539595edd7675f36114b82760c5fdcb18406c197fad0b911fa7f42109f5742bff8544c4907f23c6767b73f9efb93cb0cb295a9a707cc363", + "signature": "b0e28c784411a410a4372ff8bf2c5b426d397ec3043e2d3c009ea0fdabe154661be760c55f4f70afa419fdd73d4a181ec0f6ca06f5bfd29dba51f3a46a1cd10d" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f010100020000038c010003aff927e243c315bee78487fc7d8d5390e55b2922b2158a3bb89d79f8788f534b0159ba0e843cc1ddae1f02976eec2a49062a3f9d1e36401a57334ffa215b2802a846f3c9de9ea206e124ef8423686eb2bd9fa0fcb9cfd3a9c1a9f7fd9b8900d9813282c5a748b530b0b186d7cb845f2307fa196cd33e4d3eb53d47798659bd28c8c4998143bd7dc7e15cc064fe7e248405b3c2ad565f3eea41ef23cb992b179bfb7db020ba24ba1fc38d7c741d955ce4b1acbd4e8faead6912c838b84d6329eeafa2fdbdc0fbcae2c44373562ccd04064a45ef1f73d0a5b7e6ae38d670f9fe520366cef4789a37b1091fab04485226cb05b8d9f944847d2ebc4fff9ad29d6f797dc0754fda12f57b6ad29297731dd37351af59b70116cc5d576a65eb2d1f2946000500010000007501cbc5ce1751bf8319335f87ea0e4da093a697e292ded058fc3ff087d152a7a448000000507c2a68448c1fd275ec6d85594e17c1de1d2b418f5776c3edb504ddd6254642d427de549cc57df30a40f84ecc70f646ab6725f0cc1cec24bdaeccac60ca38969bdece8173fffdc87cdf99aea6da9e8d3300020000007501f49e8209a73828b7e046161820bbb625a943d462339da69de6b3aa091fffd32b00000050056b6a2a421182a099e717809f5e4c7265fb44b241786145d2e57e5f1f8e36f7407daa759e364e3c815f6e5febca64dc4615e2dde1ecc7736988357461b1a190ca596d6bfaa8e87dd7d72c9e2758378300030000007501cd2fdcd6b3d66cb81845881c01051defb5dea499538133570f1b680d05fb4677000000506eaf2643e0680b7d7fb2f5104129a733e91e29a5147dd2df8b648a47a97c2d895b8ed712c31856cfc59ebd4e9c4b402b33ce4605098fa4e3e4fe388ab656073eb4d11664984071d2f9c10c0f7bbcbdfb00040000007501086fba7ffbd717bbb820be0995aa9cb706e1790f9da9e6207f20f35af10da10e0000005094255540e00e5c22d9e863355043d5b7bd6751c34916f8554ba24d5c8cbc4560383ec79fd1b2eab1ee755aa3fe1d858d27e9432e836258dc57f85d6cec96dcce3f6c396cc11ff842666f348d4e06ecc50005000000750163eecb77a7ae765ac035b7ed55ce79bcc8a17aa5cd8ab1039f5da3e1a6c3c5290000005042649a1e299939b620f622a1e3f05e3f9b8b3f7ea494aa38a3836e3ba4490a2c38f0c032506145b3185eb004e22f70e3cf23f7656136fede3740cf840248850f4d06c68b05863e3caffdd4f005aaf61f", + "signature": "edc4bcd8d73bd7ac3a095a6f56aaab455785ac157c76a4f8476915c7ed1c2d166fffd489836ccd65cc671d96549ca1fe331d9349ce703e754523937e52555f0c" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f010100030000038c010003adf725409c961f8690d2d0a9aa02041dcef0493ea913bd78874c0de8e4b2516b4984214ba77904a6c932adf9e9a0bf3901abf4d7c555b177bad273fa57d383cd38c7f34f1806f50d38042fe92bf23d8ec90405efdd3cc01c5a4b7d872f2f8989b25e7fcff21a78dae3b1d26ad3178da80325ff3d01fcfc53dd03bece6b84cf6da0fbc9f42685e4b8fff10ebc60aa732c0a89854bb79faf32cd84433bb5d8ea71fd51b135adde9a624b140ef8f87587299f8420cab99f3b6fdacabfab861d6a0a8535a8acb4c0fffb37134c865f32c4b950bdcf8648dc0e9eb83da8a25bb344712e14470a4637f5d38f1b8d5df72afa0518f2fe724c6e5b254c6ae0524fcc7323de307e27f701ce96ef2cfafe4964093d44647b349a92ac3124f4044886d009e8000500010000007501716c96eab3f9db0314926992c5c6a63c9882d32da8a4286574e9ee187ecfda6700000050df13ec76dd6947c52237e1e52a339b474532ecc587bf6b6b351b40fc31d67c02439666a862cb7d4209a2622932c3d3ed8997b584722fde4216e6188d8481dbc022546807e9087a7e98c28493f5c0f731000200000075014ffb986266457a597a0fb04679deefc57756d448af9ead5005a9da9b9ef509380000005041f769b8ac7c7f47b28d6d365bdcbc3e8291e678484636bf723aacccdefaa2ad6bf4d5346c82948f8890b97b272670d87bd96d1deb7ded0525011073f1018168cec1aee841088d7d33d8a510d7c5b2db00030000007501c3cd422e118cab9f712f9100b1a12b09892a6d5070594fc25b3626935d73e76900000050539fa749cbc6198fb8bfd55d2d1a7a05dd61239a997dc6682d9646af6643c4b19e4cfccc48e917d8676455bc7abd0061867809c71ede745e3e0d46fb477f6dde29160982d4abfa905e0d6326a684982c000400000075015af8f84690b6f5805348c0e4f25eb2cb2bc70b8d02a4392e079ea3f3d02bf22b0000005021301703dfee597ac5b30ae4c464a8591d9a4e0ca9a7ca55ba571a11e289acd359bbc011132fe0edd7867717505a57aa13955b10e00d44daf9a79adf84d097ea8c08f09316754352c9699a8b111e0a2e0005000000750136e7026278a52df83f21173c5b1d8fc2f9ce451a7b8421d524742347c1251c780000005038f8812cad8c0f01655bc954f797da53ec2191cc192a3a4d0a7d45a20c6a309ad47aa9f34cb76d76db5ba55160034c2f942c0dbdc896daffd173fabac9fbba605079e94b8e37340a24e794e2f23e1783", + "signature": "470c8f5811379323c1b265e0094a7d1d2cd367d81c8f11a4959e9e52f566d0dc2ffb7974b7a3131974a206cee3b794538f08c4e3c2cae9ab8c9e267b6246ca0b" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f010100040000038c010003a3ce985b8d89cf802f157a403f18b8d509a422a09f808bf010963be00a2562e1dd9b905e57e136f5c459fa4751e69b4f11568500ce1e55899a91d0bab065b0ec0d8d856eea351dd0f7f935dc7a82b9118c9798a311020817698246b1c13ecd39a11d80885fc3374ad55344735862f98b89d1506fc5be878d757a2a5c58bea74d80dadd0df83eaaab6fcd73a596b3a8d60c3096187a0275d830fa4ec0de5cf17e095ca2998bf73ca2252f54a60e5cfe3564168237bac470f328dfe6982e932446ab72a1353ffa80eaf10691bd4c195efe0a60d0934ac512fa4f63976728d57710577cd9e8fa99fa374f938194364910e516e64fd151c19ad0731cdd4f601ed33597ce0004097feb8c7f9781ae8294b4a492d3d9a37abf95ed7f486b92a9ee631700050001000000750114733b9dd0ab699176c63cfdd8177cd55302b82316aa400ac3a31cc9b27165000000005047e1a16588cbec0f125922665564b155dac9c5067eed2ee8b31c44bd9a7b5274d49c8af05e9e580d4cc860ca5eee16401967b02e07e842633fa7fa0d28dca7109f7f20c1c0dcab35222b78e82e827c98000200000075019ce795e79825be1eb564fa8cbca7a475346f7485c01937dbca6e520cf161ab5300000050b1aa0ecb152ae2d238956199e2c7fe73ea2e5a7614e13c213b6343e674b4a07a2af28b3efd921ee888101d311b3058d6aabb6663fa18b9b466ef4d1c2b9763c21937740e95026b4ac73af9afa534a43f00030000007501fa2a1d9ff30951c97bf9623c7440b79ebff7882f59cf961c2a096ec308adad33000000504c114a04efcd5189a61a8190bca2a9305d341874dc588b95841fc5a09e1753bdc057503db2b93da55a3ada335535823186d469061fa53ba30931b76b77afbdf9ffbcf94b54bf65eb455b88623792b07a0004000000750187d026361a2591afb90312193c8356e8b1eee2d557dc0655ff4ca1c140d7fc4c0000005022c2bc83f3d967093861649eb656b70dcf323b080e98b078098170464c7753a11550370b2ead468b75c86d30352c508b56709baae850990d7b9a39da4a8fc2d84e491b2bd9545ecae8489654f020798d000500000075010d2396399273baf4132c689761f9f98318d8240955ef15a68a9401df6ff0fd2900000050f1d4d32ca3f2ddf603905c41498b9834e87df9a749ffa6eded423c679e5e55cb38ef71eb438ee01f366eb04987a719db867169c9b25c2ade907aaa9ee174b19e5eb17c677434bd9926c412f77c293907", + "signature": "c443871f4bf12fca7c46937f883813f0aaa58e8e14fa08910ecd82fdf948c255712c2f64c1e31f1b789baa830deed23a7cefd5b85a190adf6d259f9257d3a305" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f010100050000038c0100038bd553a6f20bff7d9d0e75616661ebeec3baa711b87824441deb02d0c4515e0e36d7b4a8b2dc91cf3a597a088691001a03483ed7d52041e822940cf5f7d7d610b715e19048bf1bde5002451a85c236e31ba2327891c36d2d72ecd30b2b1cd0afa2a37d5cd881891965b7c4e36d173d9778c1da05484d7461a547ebd126ef319bc6dc47cf4175216f519eb8c70ff2b1e8106a8013e214b43f41056a61b95645344c151e715a5e35057f8e47ae74ca2883199a03b149b91c18b8e0acd2f035c078ae586dd9676220ff62854ab99cd44e830a21bf6c283cfca25beb9379610772086a2427029ec345aed052319441b52f71045a6a58abca836615e6be6ac4a3c6177015fc6be57ccc0159a7b5d8147e6e9f80c0b16abf0be706deca66a7f2a9cd0b000500010000007501c56d7f393ec168ba888d0d1d0e938f55aab8a343bca228c4d3a52c0896468105000000504e475bce0499ad2ddf867dda032d7e61459eef70c96f42e1dfe5c7939e337e4810a2a6bf10dc1d325852d18280544ebf2b8785b8448a93cb611437ec1394eb2ca26ea01c0bddb47ffb259529c0b8c9d4000200000075010858f0e4da21af5e56b36edfbbf86ae3ceaa4a0bae5b9ab014c1219f376587620000005018945e72d69b3d4e325a46bba8d3bb58077a08a41d67052bbd3ff9e6b85abc35bcec04a8d41974e516311f3354841891c6aae43f8bbcdc93d0c12edd98d4622b6ba07f5341ee1ad4530209f9c653d7f2000300000075016c2e48fac5042f19d9d15599d6edeb9350bf144f3117ed652ecb5613c3f7a4340000005060752c641a802f3ddf4604256ad45a68596cf83c31a59b8bce0e38c533f17abaec3b489b53b0f06c53d1f0d9d1109bef0f9a65704dc13f85ea2efdafd561bb28cceca36a815aff9987664df0e0bfe651000400000075011ba6e17f2522ee644f3c88fc7fbd61e36037910662ef1f9eda4a3a04eb0e9326000000507bcd31d5ea141d904d212bda6b56c89da0cf3a33078c9ef8852d0deb942565e978bbaa5b6f8995c9a59c624bfd09b9391f49117dc28fe98f0c55a56cbbf63ae062ad7a1a410d0a711d3c2ef518aa6d11000500000075011ce7fbd024ee698d39003d70decc4b2f868bb86f67bcea5dd88be6d7cc9aee2500000050a4b3889cb676f2d8c7e3ed5adc50e1da6a5c4699078371c291e2a0e12799cfea0be10ee86cd469892f66f99661db057c29579c15490603f801052daadfb5b286d9848156f549c2c854ebc12bd006783e", + "signature": "340460b7077316f3739a12172d441532ebcba9559e4cb92fd68bf872e5fe0a6f2bd3ca594e81f1918f06126935cf4b54b7dc59460145defe578bdf4ebe94bd06" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f010100040000038c010003a3ce985b8d89cf802f157a403f18b8d509a422a09f808bf010963be00a2562e1dd9b905e57e136f5c459fa4751e69b4f11568500ce1e55899a91d0bab065b0ec0d8d856eea351dd0f7f935dc7a82b9118c9798a311020817698246b1c13ecd39a11d80885fc3374ad55344735862f98b89d1506fc5be878d757a2a5c58bea74d80dadd0df83eaaab6fcd73a596b3a8d60c3096187a0275d830fa4ec0de5cf17e095ca2998bf73ca2252f54a60e5cfe3564168237bac470f328dfe6982e932446ab72a1353ffa80eaf10691bd4c195efe0a60d0934ac512fa4f63976728d57710577cd9e8fa99fa374f938194364910e516e64fd151c19ad0731cdd4f601ed33597ce0004097feb8c7f9781ae8294b4a492d3d9a37abf95ed7f486b92a9ee631700050001000000750114733b9dd0ab699176c63cfdd8177cd55302b82316aa400ac3a31cc9b27165000000005047e1a16588cbec0f125922665564b155dac9c5067eed2ee8b31c44bd9a7b5274d49c8af05e9e580d4cc860ca5eee16401967b02e07e842633fa7fa0d28dca7109f7f20c1c0dcab35222b78e82e827c98000200000075019ce795e79825be1eb564fa8cbca7a475346f7485c01937dbca6e520cf161ab5300000050b1aa0ecb152ae2d238956199e2c7fe73ea2e5a7614e13c213b6343e674b4a07a2af28b3efd921ee888101d311b3058d6aabb6663fa18b9b466ef4d1c2b9763c21937740e95026b4ac73af9afa534a43f000300000075014543fd3003e651685e66f3aae30f6b67beafd108381afd953384961a7ed6b5460000005064214d034ca6f0cb3aca756298ecdb87509da2a3c7363c44a72dbdbc4402f1cdd42b98a6bdf8a96ddc8a673477edffdbaf4f62a631241c74bb68edccd217a2b61b7abf1336cb717e4f39e848b2c7dca60004000000750187d026361a2591afb90312193c8356e8b1eee2d557dc0655ff4ca1c140d7fc4c0000005022c2bc83f3d967093861649eb656b70dcf323b080e98b078098170464c7753a11550370b2ead468b75c86d30352c508b56709baae850990d7b9a39da4a8fc2d84e491b2bd9545ecae8489654f020798d000500000075010d2396399273baf4132c689761f9f98318d8240955ef15a68a9401df6ff0fd2900000050f1d4d32ca3f2ddf603905c41498b9834e87df9a749ffa6eded423c679e5e55cb38ef71eb438ee01f366eb04987a719db867169c9b25c2ade907aaa9ee174b19e5eb17c677434bd9926c412f77c293907", + "signature": "6d5f9785192037e868856e9de04f19b651acab2888f5cb00238541e4c6d95f321d8bc31e00777cd6f52ebc26e9a60894bcaded9023b72e0960c5f8404eb35506" + } + ] + }, + { + "closure": { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f025b355322cee77b95899b5935b15164b82defbc68975aec6156f9f633d9225d23267882b2a396b546e0015423f7c62e0a1d9dd44ca69729b569d3f28d3708ae7d", + "signature": "da4768f8e65b2ac73429d936adeda5c5ba78ed8df61244e163b02e7aba77ef92cf85d33f02bdc0b50eb8bd3ad6c45d5879bd3d08c5c9b02f66dcd74f9e7a7d0d" + }, + "messages": [ + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f02010001000000050100010005", + "signature": "d516062f0c32c80d1dadce7396489d440a6643e517e313dcece6236656ee64a6f3e73b236b9d17ce510d2d31f70895144eb2308e4d30cfb889dcfcaa5924e800" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f02010002000000050100010001", + "signature": "4ca6ff6a374cc46324ae67da0f2435b72175f6934405a755cec699aa2e513d983178e0792fe73e1a888f8568a3b2434ad6c540902a9dac94a558879cdbe6e003" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0201000300000003010000", + "signature": "e3efc6f472514ed59a3a9ddfbdc13a2bdb6d43feb073f8520bfdc11a4b0512f2be14c58464737174378353fa35b130af484f7cc838334d241d4e13f3fb6bd005" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0201000400000003010000", + "signature": "c8cadd4c45cd6ecfe9cd743a1a1f23c49deacd90da6b21ca38cb0483fc7cac9469ff08575cede3adc98bdcd67e77680c75252b7894f3ae4a37157eb17ec8f105" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0201000500000003010000", + "signature": "68fba815d7629f135a507bc83a567ef6c8406e052554940217c418602a118c196d2d7a66579d96b2ea4435967e4264b10741ce0f835ef8c9e07908bd6b8acf0c" + } + ] + }, + { + "closure": { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f03c930813bfb6d40f67ce70a08f19b7976de96d935b156e66459f816bbdd35bca44d1402bfa1eb395adea06a33da96f0fa7fda5ea68ad6807ec9d23f8ae6f0b7f2", + "signature": "a3234083cbc1ba87645a56d7e257a03cd087370385c3c384b9c59286cc9e46c3806b81c395694fea635afccd33f3d39249c5650a77cbf49b24f8fa5a0f480a0e" + }, + "messages": [ + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f03010001000000450100010002233ab7d8a5e649d0cbd24bc811d2a0f8c3c4e73755bc6b9b5fb29f395679f01b3e24504df4987f4fc75e70902b5eff3354cfa56c7cd15f56410ce5dbe74b8b45", + "signature": "cd64cf620c8e0faeb368b43ed90912a370def98e6fa5fe7eb478c91b3e6fa552f68494ed66d49d9e362237d68e822c84a08bedfbf9ee7effeee9c8e2e354a800" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0301000200000003010000", + "signature": "f723c9938763f50da06b5c60066804aadb99320ebb2796e706e83520fe39670a9f5145ea057cdd52040a4111ee301e7f8650c148dd85056d6c633333b61c2e02" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0301000300000003010000", + "signature": "782ce81943895bb8d2f538c88e61c06f81180da658ba5306830bbefb70c09a43bf46dce1220785cacbb9779ef23f9410204cae67d35808d165118c2e0c721002" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0301000400000003010000", + "signature": "9882f3c171ed3aca9be71272d67b5f3ca49468e5b641678ffa34763bae3bada0e2ea6ee55424a3aff4e3892dcafc2e5bc5c4335e2fc57e98137b08f3a647450a" + } + ] + }, + { + "closure": { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f04c8988da1f3505a7cc808b39f89863979cc29c40b9c59790e52a0f04211eb965795c4737c42bbb9741c796dcffcb271a0b2f691e88b998b0f2aad7b2f5c3d58d1", + "signature": "1d23f71ac254b26c698604dc4e7da5b38ac9067df525793f6a24b827f467d318c36529519020bbc1cd8adb8ba51608621b8e764f4fedf15b65b220ce76ef1c0d" + }, + "messages": [ + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0401000100000123010003b0167276e94aa096d34876cdecb1783a3d4a9fc91b69142840f34dc0f005b8c0d6265c51c088281206621ac7d8b7743d0046f3a10f904732e5df9d1b18b2c0791f0d8686a6439bcd26f79b7c55f1f4b71eb183e449a54abc50ea12ce44c5410190eee8d35c9fc754bfb129d5cdd463f787df27ef8f8ff8c4e06b2245b2d91e5cd961137ba526ffd9948fb41ec7ce98a3174fb3e8fbf49cec998b96718020e36def493f0ffe0c62a1cb30c53e0ec621201a884e1e418f529adb808fa92dd4d8b58b718c10e1a85c4f4edb41cfe66822873c1c80f450766de451a10d2b38e314cb8bcf98eb0e9b5ece7a55888d8fc35c71185966a15d5ae9e0b1227dccf69bd7ef31af109708459ad0f5e6b6c99ce7a443526205ede5e39436c594f7cc507d748e", + "signature": "0a2220dd2321063104a38626648c4865477a472ea9ccca2871a6a813380cdc161cfb3e5dda481ae93a69302111aea7fe9dd3706d6a3763bb643fcf9d3bb31406" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0401000200000123010003a27fef503fd01ab056a98eee44d1c68903f249fe6d95cdacd6034ecd543c208d2b791efadb1277ece96c7573c6fef38e00d269c484d593a8a03a035194863ddcd7a7a2ec1574e85f4453b8749ac1781f047d6c3bb7d4fdff190c26b8ad87cdd8b2c72a0a1cf38015329d84aea7c29fbfc8d6080f34048fba84c41beb6d2cdc26f04f6aad587a502099666dac0a2cfefe0d243808b9d8862cdb987d82bca9ec80247fef17156dad53eab6019378c603593970846dd8d401d4fc492fb26bc269a68ac1aaaa2e23cc55080125ef4d33c3fe3fd63d3b85996e3f13171f47cb00e2c0f9ac347376f50f8282167bf64ebad8b91389f5316e8474f1997a4652ed72efa02d0252a56df6b929024042b725f66224f4cf1aee0c45506d6f48c1aa420bf55b", + "signature": "74b4dfe2984d04fa60f44c35921ed98d18d778e1df246661ee3931182ce591c75cfe5edb85963db492731099eb7e1c1fb45c34ef9af2cd332e8adcc0617a3f0a" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0401000300000123010003ad1acdfbb5cd2b6cc60fc639cc8f722419c7ef068e18f6bb163301cbcc8ace32d4aacdc7d21f12a75b2c0f0224e1b4c40afc3f29869874340d80c009e1b708e945a63bc3939a96c9f563a9f3f81f288b87c1a2bf7b9bea0cad212d563a5869279920b0ebd7645bf3e7dc9a16c13457d90f6b195d563016126e51ef297f52b30906b61dab2f1ff7a8ab023faa02554dac043c36bd03018ad043b0fc92b4fb6792751c31ef2694569f71c1fec8db6ec20d775f9e47cd1b01cb0d118e8c6676af7eadc30b94535a79e1b3f0f05e428c9bdc1e6e54eea52bba97ccb9d9cc9a964b766b8c34ba9d15715f0d19970812415d8413607349c2f4b37b6bb310d27c82129545bbd0cdb80359a0677447299f3c711aede745cd3ea83a1064ab542ae0307c90", + "signature": "6f50c471bb0a34bbe51b9e6356f6bcd8364bd9c065306a314995966494b670f33081279288ef74b11c09c6e5f88e9b0afca2198c6c9e92635c7a05763697b70b" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f04010004000001230100038635cbc8ffc41cc7c20bebae9c067414e1a1a85deb5ecd74e9184f7faa6d47fcdf447d842259da3b6b6f60a4f80d2cef0d775ec08808ea302669c114adc4ea945f9fc942810d26777b0a845d75c0e1cc3b859d3e7839e0caf91d86e7dbe6446b8148da42a74e7d41d87fca3041a21a38d9d5b4bfd274d6d91038ad30953afdc559f37cc004c4d3b26cd0d4c238319ebe0dcbb9fd1bb3fefeba658f4e2943a6fc57d3a957d74baf8788d619f0ec878fa59ff3b3d4563ca8ccc00805f5626e79f98b8faa7eae3efde894cb9119ed8cf3eb879bc22a764bb5b29764fe27e2c0bc41418871900e2ab0e1080469b6637c28a51755f02a4b1cac63dd5df772c43a8bd3c82ff7c7c97468eda54ec3438756e25e0c561a1c83366218e49ee5f4b7cd06d8", + "signature": "f62f643ba003c29727f5085dbf5546933cf6c44a6afd83fff04484bee7c368bf6ed984a7a5c3ea64a12806c1626416dbe39428f5c66c6fbdaec94d35e1e28d07" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0401000500000123010003b29634a594d14f3a9fe340c2fa7c14432a649e907cd0ac3844944b7aa08b0003529bd76e01eaa38578d3282478487fdb0d175ac61cd10257a7148917224d0a33a69ab54e1de33b96d5ca20c8f9cc393a7c11e7ef7b2777524db93d2292533a19ad431e79989f5c16e9cd2d4bb16847f9ab40b569fb0778c0c1ccbcdcfa66a3b21b5dbecb9340425d2589c340d3d5a76914041762bee3a2cc47a2ba90086ba5f00c96d362cc29e713bc7e216da10410e4421dec9211be6f5b7c82e6af1c64075a886a8621898ab6def078e51de6134b7bffcc635e0eab03f96cd28e01e662b082f1d761af913d4eebd1eb98270f985abc12da1aec13bab63e23f4da123134c715ac28f8db459d61ad20c9ab75b7565983382f9bbe20bb469f9c8aeb6c943c2e0e", + "signature": "543a667f58641e766ba8058becceeab2499b5c1dbae114af3d340402eda23f0c42a814cac199a8cfde082fccadbb91676887eed04ea1fa2c332f2ec37bfca70d" + } + ] + }, + { + "closure": { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f059ad07f77d78ea3175e18d4aa73b8bb88cbd8a4a64aeaf27dca4c6948b1ef36bf5f36f271dc2d4be8a910394c1bb5ddcaa85adf9eb936690e22702e7ddeeb814e", + "signature": "a911a83d45f1dceec40b87f6f0a14c293bde4447c8850db6121507157085df876f0376f84161458518759ce2a341a60e421264d514e8171572c9e2e04ce6bd0b" + }, + "messages": [ + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0501000100000003010000", + "signature": "f2c60fd60cf51286c8eb690bf5d54fd523e0e1500cf0db8ae2c3ee73039e6ff7dc104d302ad848430dd1c190eb59f5e0bdb38e227c39b9ffbd5f36bfc875c007" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0501000200000003010000", + "signature": "026a6182612b8ebf357196fe37cd6afe34ace83af7a110588ef3ca9b73874d24aacd89b7a41661e051fa0ac8261922dc30aa0e46fd7c03fbf7c42b86ea5bec0d" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0501000300000003010000", + "signature": "cb17551016ad6ff3f8c8ec555155be34107a7dce71bc54ad8f5e14db82498cac361ca5a6d5e6d43328ee61a1d548ec13390b1809f0bcba8ab58524425553d600" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0501000400000003010000", + "signature": "004594d3a5c0361924490b6c965553a2601c7c3fa02656e040ac0b4efb69f1c6cc44b459fc37b7417ef6609d9f34bb2680e329cbfd64f95501eff00f48c61701" + }, + { + "body": "01990e9b7976bfb2eafc5d9e7b836e4357d1f2c87df458369c9be47f6a6fc9838f0501000500000003010000", + "signature": "b8356d196655497dd9ed4b2cf757556cc0d3e0c88131182f1383e7bd4aba3ea59b1a11ddf998b3aa6feee2edf9d38c7e4b7f0be63469b0b8447bd3b67bfbbc01" + } + ] + } + ], + "threshold": 3 + }, + "name": "genesis_3of5_answers_and_disqualification", + "operation": "dkg_verify_transcript", + "outputs": { + "commitments": [ + "ad1c70db3dd7721a305956fd7fa8cdf8b4ee47c58ee148d8b5d8707a8d7bdc385834db885b4187ac08098bc1b23947380373b0fad351f47522061cf83c896a4a909a1e0902e00dade2da92fa00058dfca753c5ecec34042e0ee906e221390020", + "804bcbb4fa645da7db40ac6b3590a4426b7b00fd1281daf1890b4a4cf8a787cad996734186729c8beaa04eb89f31cbdc0801fd5c3e0c2d51acc64b263abee54f7d3a1a8a56e4254b5da3ffa3e29092cab2a396153f09da3a237ed6a2579b090b", + "b2bc9d89761e137547d6b2d0dbacbf12c1698cdc018b277e919934d29b4f28c415058148d7dd40b4e75b4779ae41947c15e0bc9ee04a891227eb4823ebf38d3ac001b84e5172108b5f1e1922e91df2f56f8dfadf88e911b0aeebfd6fc9ee8bc6" + ], + "disqualified": [ + { + "member": 4, + "reason": "Equivocated", + "round": 1 + }, + { + "member": 5, + "reason": "MissingAnswer" + } + ], + "mpk": "ad1c70db3dd7721a305956fd7fa8cdf8b4ee47c58ee148d8b5d8707a8d7bdc385834db885b4187ac08098bc1b23947380373b0fad351f47522061cf83c896a4a909a1e0902e00dade2da92fa00058dfca753c5ecec34042e0ee906e221390020", + "public_shares": [ + { + "member": 1, + "point": "a2aa686881d2ee061bec22cfe47e9d82b79f2782e3eab2ab480a2af790a639bcaef48262c67c4cb64a46eae720edaf6d0af835a84a30be8dda054ee69521326522752ab4b063ad292f13393386d9ea07077a40af6a85561507a7402bad5522f7" + }, + { + "member": 2, + "point": "b3ebf5da32173bcbef5eefc6a68257803d38615129ccd35196ebb7e8521a4aa2e3d7ce969afacc8ce0483fe7e8f02ee908729774c1a5f5d3440715be0ad3ccb0b7a786a67998bffe7fb74bd46c52b875ec4aa6e32a0b01784bec9ceed8c2b415" + }, + { + "member": 3, + "point": "92730453472319cc2186ef21161050d0726bf965be1c882daabb3a3ddfdd69983321a4a44d9ed823b8fb000aac059f9f003bb044cc7c9e37529272490b524fee41279dd0057e47b6ee0cda8cf02afe71b782b9de031b6740cef467d2536aeacd" + }, + { + "member": 4, + "point": "8fdd7c553a3ca1f48bd118cf69d5ae084d22fc15997278d8728d0a8a528566b6dd28388fc40fd12c6499ac071caf84fd0909c79c801bd47bd137171b5d513bb4305277cfd46f37f003c66e9575aa2bf6e07aff38c894cf9b2df3228afd41ef92" + }, + { + "member": 5, + "point": "b99537ef9bad53e5b84b9b6bf3b1985fa13f98965afeb527b956c9db0e55e2a0ad20ce7271c947d6642faa486c892b33127f03792d8060f73cd50eaa64e80fdb281463d276ea5c76effb62cdf1bb2daad5725588af673a288677bdc836eabf01" + } + ], + "qual": [ + 1, + 2, + 3 + ], + "reconstructed": [], + "transcript_hash": "6ac54abc7f9b37ab336655f7d51235385c46759e3b1d870962ae5a581da2e5b6" + } + }, + { + "description": "Only member 1 sends R6 for faulted dealer 3.", + "error": "InsufficientRecovery", + "inputs": { + "ceremony": { + "body": "010165656565656565656565656565656565656565656565656565656565656565650000001b74696e79636c6f75643a746573743a636572746966696361746573000000186469643a6b65793a7463323232322d666978747572652d3000000200030000000000000000000000000000000000030001000000186469643a6b65793a7463323232322d666978747572652d310c921bb2dbf9625d307b45a3e800e74c8cefaa2aaea59768b0a61b5dc34534370002000000186469643a6b65793a7463323232322d666978747572652d32ac435ad03587a0504b1f9ccd79f73f0f62697061ec6fb85d8624a0bb3fd906580003000000186469643a6b65793a7463323232322d666978747572652d331d63300c37aff03b791c0860100f7ccccafa0f3f37b0ea4a6517eedae5b1dd10", + "signature": "57a016b3e7ccb688954c0f7379a4c41285cc717d8cc72bb14950d6ca0e16bb410034de94818c30ead4a15698f028c289b440a05a00fe8a650e262d4c6e11150d" + }, + "ceremony_digest": "1c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415", + "initial_transcript": "48084f9935d10b2f5ee1b9b73ab2587160a62cce255db36cd2a17a08dbbb5ef8", + "members": 3, + "rounds": [ + { + "closure": { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d4141501c6f30b1bf560381ef2b38bb897fab7edcc7b05703a1f7013d2f080e62296cf4348084f9935d10b2f5ee1b9b73ab2587160a62cce255db36cd2a17a08dbbb5ef8", + "signature": "64e474d84973c73f79bf8b8ac1d846b6afd510e9729d7548b21fab3522a4e02851b11fb17e99307f6e89d22427db2ce4af8f0882d5d621b9805259db0b340c0f" + }, + "messages": [ + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d4141501010001000002360100028b7f73e22c610236c1c4a7fd83017ed5aae69034b3a6098108226987f02f4f992add7b661a87c801ada6411e5e75213214ec30f3ff8dbc403b834924c76b2eea3a2956d310b8b520bf12190a067dd18aeb36c6f885438ff57ed9aad7c2ef13d69638be7ae9c0ff03c6f99a20ef89838ee8f7f04f69c74098e0111143da26cded88d815361dbcab9604f925268485dd6211b1ab1caa158c074900c79b54609d0173a26270a0720d67ffa9fc1cca4388a65a0edd54ec0d3580ad10c850a1ad025800030001000000750139897e5abaa26e60e18ee4c1600fc57b4a57899645a28672a41ca8fbb06b6c220000005096087fab26730e82e18f0386b134559dd0493d28959ae91873b10c53ae20373b746d8ccb6a58c23006d2f5d62d8974cef126a15f0e629dcb7e5316db4f08cb211b591b2dc6e7067c6b0dfa2e806b40620002000000750104e14c60bcb8b9487002851de8a0c215f392bcc7489134f14b827425ce83e566000000502c040403243be459a93fc0f6d1a8e1b8ccb122a1c9c732ead1afb84f5f402239277d50c0c3c3b82feab23190e7531b6017598864c6cc49a74d5a8090ffc8910d8969542e9b403f8f21c24dcf6bcfa6a800030000007501d99ae42421eb3e4f50ed658dedf9aaca89def359036de63c400c0297af42721a00000050e0fca38ab63f8955b1c1dfebb5d1d50431515316a27e03dc8c69b1cb686eba08dd32855bf513709bdeb495308fddd785637d7f6d0c7f7eebd61c0fc661f3873dfaaa2ee3502abb783b5de4c44c6e872e", + "signature": "7d74cdbc59d9e1a9f6895c6eed0806f90b2c950649712eeb895860e11cfd82f9a71a23319c19a79c48ea78a432e0175892411b46c5d73e998f33c69eea4f800f" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415010100020000023601000283fc1d5ec5946f8273423656c2c8fbed5317a258e284d9e70f4ca4dbf2b722665156037d0baa9a3ea344d2a577f8d9410ba209338dfaf6f80c12615fe24116b6bb1f078dd3e0052cdda9283495353d8738b4fdf0807f12ead94dd78276bca80bb2df4da488eef77a2138126dbf6f6e4b8082ae922bfe4c5a9f1e90d0461e4bc09f82f5d49277eff4d408826a1f6cd0fc0c0e21abd82a9a0ba73e91cac98b6c7682e567dc927e6c6ceba88d89235252ec790afe7bf9d24cbfe699137f90918fdf00030001000000750141416d53372425a41e2603910678172b13d23fd9839cdb725c2c91f06285b7780000005003304b20cc47fe9e3c5cd3bc2958e2089766093480bb791e7df5a66e3ab9a219ff17742a1183db8e54dd129da25c66719ec3ffb567bb8d1bed44f8518bff7307504a3a84117009c13c74ced995a4b1e700020000007501443589d6bc64ccf6d5f066419755b0f595402ef0887192da0c22a51e4c57896d000000500d0888eb5e6b17e014fcda41f53d5f186de18730e4784540748e58f49808cd0f5093ac6b3985b6e2fb67c2dc5ddfc2a2f43d2823b349aaee862a3a74f9a4cd336be65c4bc7a573a68147f102dc30799d00030000007501dcc002d670e2a62ccb1db3612f0f95495f387f436b41ce37300e2c978e847f2b00000050237df70b42cd3cc630f8dbdd49f6be7ca4094f82724a1a8cce6e3c2800213596dbc1019abc943876f0105e9a20987697efe8fd5e8c8a9fd9c10ec5a7405844cec8d066add80dc6e116aa94d0a167c4e8", + "signature": "285c3b2325b28df3a804f96b62a45188b8fa481ec4eee33cae35e5f783ee65be763bd3c6606950df7adc242ba4434c3b39b27f3d3189c0065da49cb4a7d35d0d" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415010100030000023601000284309c9bddb323d9fa49bc88bc0a32cd4280cb0a1386fba2305a425d027a2a316e6865c28199dcbdd939a1bddfd3ffd4042efad86de9094b2259da8618f1f8c2ff1379948a6472e5c6b56f1594db9db8cb734448cdebbf4c2c0e3b0a5a787dc882e0ff6e5ab3187476647e16ab9e8f90cce144915f1889f19f14b0e2e4e1ac3b310cf780513fb83da2f2f18a3328efc71935468421fa1c54abfe46f7c958375177dc0b69cc37327b4a65d0a3b64a8cf83c95bbb584415e4c036ac9f6a7644279000300010000007501b6ac73b1f13c90789bc795579a5686335058f93da3cbc04831f76233f21c331800000050f658efeae776778d124271f3b76f6385fa2ee14d50bd3b9d667d6480f1c16987c8ccc982514e0ef5fb533f997a781e1a7da72bdcebf776d7a360d5805452b75f9df12452ac4eebb015c8accef01f985d000200000075015d54b20fc06904ca33a421e2131dff377ef4ef7d7f47c0322c202fec95a2d04300000050b0c684282af147c296685f1da0e46f0a0dad5686a9911dc815f2125954c545ab49472a9881b4a96c41177936f923c29c2442053ea406b2ff25e74386557e325e9e57cbdc760508805492e2b0a6782e2a00030000007501b3d53d386a204a5a4d39212720134217c73086e46bedb537da405c33bf70165800000050f003af692661cf5cb05c3bb99d2cb0eab7b57f8d37bcdd74420912da883aa8a163b3d3f9d052ded8f29046c31365751ffc1be64e65bb8ed8c5b5aadcdcf7aff91cb70878624a5a1d795f76b89f9b6408", + "signature": "5db283ad1ab1179bb92df2240b261d145275c074e9bf15490d7d5e1e023c50c690ed08c432d8a805d8f2090647c004dba53aff755ae0c5b8192d6ee92ce3510b" + } + ] + }, + { + "closure": { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415021de7ffe50abbf1981e1648b6f6cbb0a1e00cdfd430d51c1a43855a27d8a1441fa9cce62d5f0bb9a6af136739b8fb1692760fceb18d760af4be18663dd69301f2", + "signature": "bb9a3edb1427f77488df29257b7f7ece104cb53074ce3e59c5ba45db37a8d5b693143863b911b183c5e4eb65cb3a83bcb51518cd62e86cd8497dcb2fa0ca6008" + }, + "messages": [ + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150201000100000003010000", + "signature": "485ed633633a904fed7de9b58f44ab1f29495f9cf9b81161fef98f8b8089b74335354becaa78cb9103d294e4f3a7d8f4619fbebe2859879a9c1306a63ee1e005" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150201000200000003010000", + "signature": "f9ce768fbbe191fd3dc1548ce1bb330951cf9d6a365eeba1269db57162ed3f17aeb03f306df9849a82f2531b3f63855a8fae64677beae495f26667b8d29bfa0e" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150201000300000003010000", + "signature": "9ae683ae562d07328508128bcf70034e0207b0c51395b3a5d17cb7cae372555a37def9e7d3c8c04bb8a06c54e250e33194389445783e15c91d5fe260da92b60e" + } + ] + }, + { + "closure": { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415032d55bfaf92c87e93f6831adcbc26a612caf27b9dc48f0d2d35dbe2337886ffb2ed163db3ddf951122d8b558e7d4385a43230b74974f429c8469d151709cb477d", + "signature": "b9cb47913217740aee96a4a82dd77ecaa7143acb1b0ac9e9918bc019a8de9aeb853f60be189e199f616379abeadf3df9247e82e55102fee2d5e823defe48d003" + }, + "messages": [ + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150301000100000003010000", + "signature": "1a3344ad7ae5a1e0d81a0bc207f0fc8df226961a2c86e197ac4ed7619d7205addcb278ccb4785112d7ec595f174e987196b937c308cb4d2d510ec2ebedfe830c" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150301000200000003010000", + "signature": "23f3be1a5a9ea57dd0bfa7a848d0f3e118e0ccdf50a1cd4c09c7046e548a9f8f71dd4aceffc51c8c5dd7119f765656ba62240656994445e9e94cd4a30e0a6005" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150301000300000003010000", + "signature": "a2722c105706e1e506590a4cbb68d033ac1f3dca3ed1a6a1e461cd22ff4ef5ed703677a8649d0101e3d82d87b7b21eccc17e8defc99884ca131d453f49421c0f" + } + ] + }, + { + "closure": { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d4141504f92a59d5d08d421e9663832098f4ea9aae23919787066ccfe2a2a1c5124bc833a1bf55577047cd94e6e4cefd3179cd55c2eba44328d33d1b7565f2191a24141a", + "signature": "6832e6163d1c3d8ac2072165d3527514d789378adbaadd75cc8ab385dfb29d80c33a34c089e3afd7999bd0d1484f95b841695adc41bc820ba283d9c6acfb0302" + }, + "messages": [ + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d4141504010001000000c3010002a4b9c509d40eba7621b374897546f07b7e6bba9741e6eb6749dcb111cd303f350266de4bcfce1c5490590341d882ba6b08be1376dd4db14000810ce7bccf9283b7471eb2160ec01dcc1f7352101872926af2a38ce1aa729123ddfe64bbf077f78f27e4908459adc651e44805fa694d4dd9e6e2b3a3fcd85905011ba99fff1e468e12aab05dda627515a19d9e8e9659b804334bf2f8a5125667e04a39a3e61dda8eebea0997c826e49ccdc333f5917d662145b3095ba98b60e3443264c5ddb95c", + "signature": "28609df98bcdb31fef280a7a8258c45d342d64aa785ce46fa3be1e7123cc43547e3dcb36e444e3ec898840b2ee7bc114419d80a83492f880a2ba81a8640e4d00" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d4141504010002000000c3010002b0461f1a708cce2431475429ad5c0719f075fcd262f84c53f84c3b61941ce1b19e56b0688d6cc4b27de5f16a5474175412d2874e1b7a50d3a54306fcdc5d55cd6719c801b65317643c75b8f9c25543146f5bda4f4d869b250d1353abcfa614dd81cfe98d0e724f70f8e3b86d5dec063b120c4c150b3d878e873a38dcfd06a95e139db75995881a242814356c24e3948908a9ffc41648c6227a5bf549f3f339fe7329621d9a4ca0397b5589f860da196568252e0762445c31687159e44c948fff", + "signature": "3af233dacc0faa24a1aebf82017bf53cfa1d68b35de7d5101603ed8526db61ccefa3f67e718f78207dee659ba14aa5a55dbf7c5921481805e1a992ff5e6f3a02" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d4141504010003000000c3010002864401f8d8f39df9f4e06963beb81b75796f4c6e26a4c3233f27df0883ea69d0105345545db9907934e3decb563349f1043bb2686fc9466f010db7edc83afff5c80a7ca9e82cdd08f7db1232a705a9232ba3624d29fdc7e74bc235d24c7f8a8c828c4840fd1f4653eae38fbad5cca2ab3b60cbeb47753aeb44a4d5437f49a016986edf6859b2e02b991d6d582bd6c5630b7f1ed38202952dbadf14890650226e65044a33d6d57a0e10b090acd2230004c38e9c7e8257b49f28bd2214e79fc031", + "signature": "c3b1dc8e6971708ac73b78eac1d987d926320a744fb1adcd42bcdb6af4578fa36cc1eb4062a893037c716919d96376e69677e24c7d8c891b4f9dc7a01f8a150d" + } + ] + }, + { + "closure": { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150575f88a756daabb4c5002d729657b11b25d2377a380d70faf26487aa60ac710a04b7f6777042f095bb868428f4ea091b016d22046be535bf77ccff152aedde054", + "signature": "dd0991df7d5baf1ef058dcae5d06c7091ee43fb820090fa084efb35d9a21037aa76f27a8eaf00e4de16ca2f23ceea2e12b91fd6d95711c30156838d6ccf10c0b" + }, + "messages": [ + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415050100010000004501000100033fe8fca9d79ef29d8d8b3e204d1d18e3765d8a7ce178e2187873c47bdd65a6ae734627d98186db20d3136ae4c634db8e3f2b6e03c7c8e6d3c7cd046dd7acea27", + "signature": "cb31797456217d53ebcb44cb27ee96aa1e375c85dfd6a0311934e23ca5b371738c34e151e581e3b51cf536dbe715c9b9ad0e6a63f2586207faa1d3c6d559a203" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415050100020000004501000100037369ac3a7e7c1d2d17ab1a7930b4461f4cf8749b50c619c5564d14e530e5b05507120515425fbd61085261d1b447a48ff697a8153550916774a29cf82cdf0e78", + "signature": "17ee673c88aabdeadc934138a18db583b8769137dcd49d7d6869c5d19ed6757e5c91ca199cd165c6465247f2407761e8bb2d5d9ae083b2c6e9a77390bb7dc302" + }, + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d414150501000300000003010000", + "signature": "a1eb1d15a47433403ea49879fcfbc0b887c87ce2f107a3c8a70caf0a749791705c2adb907bb2c533af6ff0781494deaa0e6b091fc97b261b8e41590a3d423b07" + } + ] + }, + { + "closure": { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d4141506a830e64a83667fc6babeb527cdaf689a2a883644716d40f0620a09ceb6b7f8ab0a79901fcd03a2bf5979ef6ba3d132da441fa588e612664a71bed627db9d2ab0", + "signature": "7faf38f08dff4bb542b8109b141475fb30b1a004dd9fb7e098bff8c178be996244f2b5a8c34f2e5c011c43e8b52430a71dc48ec025bf1868cb7b4899b68e700f" + }, + "messages": [ + { + "body": "011c3a9e2b73a8533f86236d692740fbfac73ab53963ad346aa97d9562a7d41415060100010000004501000100033fe8fca9d79ef29d8d8b3e204d1d18e3765d8a7ce178e2187873c47bdd65a6ae734627d98186db20d3136ae4c634db8e3f2b6e03c7c8e6d3c7cd046dd7acea27", + "signature": "e7dddd9453e999c83946c905770633ed82e2acdd42902f301532e8262039c8cee9fe788b7fd05a79564b7c044c9573c78acb8fd161d55f415cfd0da6b6ac6802" + } + ] + } + ], + "threshold": 2 + }, + "name": "insufficient_reconstruction_values", + "operation": "dkg_verify_transcript" + }, + { + "description": "Only dealer 1 publishes R1.", + "error": "InsufficientQualified", + "inputs": { + "ceremony": { + "body": "010166666666666666666666666666666666666666666666666666666666666666660000001b74696e79636c6f75643a746573743a636572746966696361746573000000186469643a6b65793a7463323232322d666978747572652d3000000200030000000000000000000000000000000000030001000000186469643a6b65793a7463323232322d666978747572652d313e586833f35c91c9985444777fa6cc0c4d8fcd4c615c247bef3a42e43cd40f540002000000186469643a6b65793a7463323232322d666978747572652d32c1fb47d90b9d208657741fc05eccf533f891eabb5207b985fb008705c2e47c6f0003000000186469643a6b65793a7463323232322d666978747572652d3376780196f3874c7483661b5027b6b902397475e479fa6fcc187b296155165079", + "signature": "b0672727bf08c14e0df862f2d6588821def7de6c383063b07457678ed922e056c0ca8aff15d5f13e0d6f9c82efec218b6efcbfc44eed22d60950ff8146ba4209" + }, + "ceremony_digest": "3a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f28", + "initial_transcript": "7c081b8a20f4b5452efc8ebca5ecdcc2231c71a81ca36cf8e8ed4277eeffefe5", + "members": 3, + "rounds": [ + { + "closure": { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f28016547e2484058e47c45b10b3a1065a4d3054c3d70c6fad4f0387daac035bff85e7c081b8a20f4b5452efc8ebca5ecdcc2231c71a81ca36cf8e8ed4277eeffefe5", + "signature": "a02ce670e09c10ccfeb242eca00b7c6b813b999ac881376e269987cb0d25469b3102b8762af3ae8f87540f3af8d4e14c453f096831af306913b619e6aebde700" + }, + "messages": [ + { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f2801010001000002360100028515c51b01f575e1e7f7022285e3dd997a45652896de32669c29323f930792356b828dfce45794b754c732ec8c6df2b304b66a073ac4bb307179361be99ae7a0848404e724afb6094722897c1ed83cb637324a65f2a595e8464089c71eff2366b7089d1a108e4b122e5fd0c4cfa02b05465bcd0690a8402176119d37194be51901155c2c38c955224142a624b8320aab09d1bf8d430d5eed518bcbb5fb2b5bc59929f840e588049f9f3c11ad2b21946871b8dcca631699a22e1e9d184866fd8a000300010000007501fdb9c67a06c2c9629575bb17e7045d2b9a32f2d1c1be52aa580933673b75c04b000000508fd9ce372926e6e1d86846ecbf76cf7adc1204745a714178f7e23da7abce0479c4c54180fa93c111a13d90a09b327ef44e27123392a699fd939ce4a86a4e2dbf177878d9a632d86b0b1d871a65c9defb00020000007501e98a25d1b546dd01d713df1167d8bb0150e58805890e755e39bd9e38441dc94400000050d9938db759a732c1ce448956eb3705e7b2c0d76d678ff226c71ea7b56760fe3fd11c5bc81e79563622d7ccced1d40275ee6f77fe767c02b75e3b804d23490ef345ed77edc15262e2303931d2d1f383d200030000007501f2789c1de58910eb088d2b2fdcf88c23b85dfe0d988461b88b5560f6c4f74936000000504b750492f6c13c1f0e1c1f069241957394cbd41bf73c169441a2b6a95581eda7461d5a67bcd25e3f861d143dc411fbd328e51750621a8016d37205951ee0e71ceff8dfcbedf8774802bb964966192b3a", + "signature": "67cb61dad6e7eba47727abeac98c81aaa73304497da60e5b241711d640211818d4cc7ca61681fe253be44bcb8112f3d34c0d2d89991493fb46a57c66563ddb0e" + } + ] + }, + { + "closure": { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f2802f6f61bf62dc6e4adf25e3e6628e605490efaf5d39c07a1065d9860c21532a987e23292e7aefc8ea23bd1af23c52109b745ec92dcc9947af3dddde751ed9307ec", + "signature": "c74a79f9d42e3fe02220184fb53ffb69ddc42fed1c71cf0d4aca77f2611496294bc2c67165fbe20860a35092fbc12708e3d924f532b0908942df1a2bf4eeb30c" + }, + "messages": [ + { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f280201000100000003010000", + "signature": "bbf53931c7c6f402fe1f287d26a9a3c41e79ac08b2a80ef05775481200bf3a7105d1a77d5975e9fbb5510a10ae9d5eb528e134d10c03daf195be75d2ca6ccd00" + }, + { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f280201000200000003010000", + "signature": "840c096cfd990a5816a2965b0c0885973c5e7f9e2cc09403ee41a262d7012657eb3eae77799e53d9b099dd609bb0298c87c6248aec815599f1793fb885d35a0c" + }, + { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f280201000300000003010000", + "signature": "dd42e9e11fefe89383a2ea7da0d73f966a01a14ea2ca23711cd17f79e0504c5670bb46d8a83685ce1e6b5342796b839c041ee7e4d5de5cedadd9f9a97337ba07" + } + ] + }, + { + "closure": { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f28035a4209392b39b58ecf527da78c6e00731ba652262cdb87604e93a5ea569dd28ec340ee234358e9145d0b3c801417fb6dd82c273a05a6b4286aec25a45b92bf89", + "signature": "72cfdf1fa6cc20c259120b2c29af5cdec8c04cc610a4108473756839f3b8b8f9506c49317d94f309b19a2e220797480b4be8250bd5ef6e855c503f9058a2030c" + }, + "messages": [ + { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f280301000100000003010000", + "signature": "aea1b9cd0f4615bb7f89c3e4dfac56d4b90932b7d2723419e3763516bee282ea2be460e42b6e1589469180a8538fa6c2bd3f84f40e6f8eb90dd3f608f1fab601" + }, + { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f280301000200000003010000", + "signature": "e2faac937923c60804b3e8611c783974b77d6422b79b1b0337773465e83f90ed6d548c9f0cda28dc65abe5e5c4e1d00038ba1c06ca85c4b02fd71db2cb36760f" + }, + { + "body": "013a418842a9461bcecd458ce346fb3d08d402e50bb0a56facbdf0652ae7562f280301000300000003010000", + "signature": "dd59d39746b76f87699344c797d56a6896d97181cb67f3c0832e819749cea48e14c70b98e491f6567b988596e0b5bbba7af4446f787cce696af8387690019b00" + } + ] + } + ], + "threshold": 2 + }, + "name": "insufficient_qualified_dealers", + "operation": "dkg_verify_transcript" + } + ], + "dst": { + "ceremony_auth": "TC-THRESHOLD-V1-CEREMONY-AUTH", + "ceremony_msg": "TC-THRESHOLD-V1-CEREMONY-MSG", + "commitments": "TC-THRESHOLD-V1-COMMITMENTS", + "dealing": "TC-THRESHOLD-V1-DEALING", + "dkg_start": "TC-THRESHOLD-V1-DKG-START", + "pedersen_h": "TC-THRESHOLD-V1-PEDERSEN-H_BLS12381G2_XMD:SHA-256_SSWU_RO_", + "pedersen_h_message": "pedersen-h", + "round_close": "TC-THRESHOLD-V1-ROUND-CLOSE", + "transcript": "TC-THRESHOLD-V1-TRANSCRIPT" + }, + "encoding": "hex; statements are exact signed bytes; scalars 32-byte big-endian; G2 96-byte compressed", + "format_version": 1, + "seeds": { + "ceremonies": [ + 97, + 98, + 99, + 100, + 101, + 102 + ] + }, + "signers": [ + { + "did": "did:key:tc2222-fixture-0", + "ed25519_public_key": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29" + }, + { + "did": "did:key:tc2222-fixture-1", + "ed25519_public_key": "8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c" + }, + { + "did": "did:key:tc2222-fixture-2", + "ed25519_public_key": "8139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394" + }, + { + "did": "did:key:tc2222-fixture-3", + "ed25519_public_key": "ed4928c628d1c2c6eae90338905995612959273a5c63f93636c14614ac8737d1" + }, + { + "did": "did:key:tc2222-fixture-4", + "ed25519_public_key": "ca93ac1705187071d67b83c7ff0efe8108e8ec4530575d7726879333dbdabe7c" + }, + { + "did": "did:key:tc2222-fixture-5", + "ed25519_public_key": "6e7a1cdd29b0b78fd13af4c5598feff4ef2a97166e3ca6f2e4fbfccd80505bf1" + } + ], + "spec_sections": [ + "3.2", + "3.3", + "3.7", + "3.10", + "S0.3 DKG appendix" + ], + "suite": "BLS12-381 G2 Pedersen/Feldman GJKR New-DKG; RFC 9180 base mode DHKEM(X25519, HKDF-SHA256)/HKDF-SHA256/ChaCha20-Poly1305 dealings; Ed25519 test signatures" +}