diff --git a/pkg/tbtc/signer/docs/roast-coordinator-seed-derivation.md b/pkg/tbtc/signer/docs/roast-coordinator-seed-derivation.md new file mode 100644 index 0000000000..b4eb24b90a --- /dev/null +++ b/pkg/tbtc/signer/docs/roast-coordinator-seed-derivation.md @@ -0,0 +1,68 @@ +# Coordinator-shuffle seed derivation (RFC-21 Annex A mirror) + +The normative definition of the ROAST coordinator-shuffle seed lives in +keep-core's RFC-21, *Annex A (normative): coordinator-shuffle seed +derivation* +(`docs/rfc/rfc-21-roast-coordinator-retry-and-transition-evidence.adoc` +on the `feat/frost-schnorr-migration-scaffold` branch). This file +mirrors the derivation for signer-side readers; if the two ever +disagree, the RFC annex wins. + +## Derivation + +```text +AttemptSeed32 = SHA256(KeyGroupBytes || SessionID || MessageDigest) +ShuffleSeed_i64 = int64_from_be_bytes(AttemptSeed32[0..8]) +SourceSeed_i64 = ShuffleSeed_i64 + int64(AttemptNumber) # two's-complement wrap +Coordinator = GoMathRandShuffle(sort_ascending(IncludedSet), SourceSeed_i64)[0] +``` + +- `KeyGroupBytes`: UTF-8 bytes of the canonical key-group handle. For + this engine that is the lowercase hex encoding of the serialized + group verifying key (the `key_group` string in `DkgResult`), treated + as an opaque string — never decoded to point bytes before hashing. +- `SessionID`: raw UTF-8 bytes. +- `MessageDigest`: the **raw signing message itself**, big-endian + left-padded with zeros to exactly 32 bytes (leading zero bytes are + insignificant; more than 32 significant bytes is rejected). This + mirrors keep-core's `messageDigestFromBigInt`: in BIP-340 production + the message the engine receives *is* the 32-byte sighash. It is + **not** the engine's internal transcript digest + (`SHA256(message_bytes)`), which continues to feed the + `round_id`/`attempt_id` derivations only. Implemented by + `rfc21_message_digest` in `src/engine.rs`; feeding the transcript + digest here instead was the cross-language coordinator divergence + caught in review of the unification PR. +- `AttemptNumber`: the RFC-21 **0-based** attempt number. The FFI + `AttemptContext.attempt_number` carries the **1-based** wire encoding + (`wire = AttemptNumber + 1`, zero rejected); the engine subtracts one + before composing the shuffle source (`validate_attempt_context`). +- `GoMathRandShuffle`: the bit-exact port of Go's legacy `math/rand` + shuffle in `src/go_math_rand.rs`, pinned by keep-core PRs #4026 and + #4027. + +Implemented by `roast_attempt_shuffle_seed` in `src/engine.rs`; the +end-to-end acceptance of a Go-derived context through strict +`StartSignRound` is pinned by +`start_sign_round_accepts_go_derived_attempt_context_in_strict_mode`. + +## Conformance vectors + +`testdata/coordinator_seed_vectors.json` is a byte-identical copy of +the canonical vector file generated from the Go implementation +(`pkg/frost/roast/testdata/coordinator_seed_vectors.json`, regenerated +there with `ROAST_SEED_VECTORS_REGEN=1 go test ./pkg/frost/roast -run +TestRegenerateCoordinatorSeedVectors`). The unit test +`coordinator_seed_derivation_matches_cross_language_vectors` pins the +seed, the selected coordinator, the 0-/1-based wire mapping, and full +`validate_attempt_context` acceptance for every vector. When the Go +side regenerates the file, re-copy it here verbatim. + +## History + +Before this unification the engine derived the seed from the first 8 +bytes of the raw message digest with the 1-based wire attempt number — +the legacy `signingAttemptSeed` convention of the pre-ROAST keep-core +signing loop. The divergence from the RFC-21 layer was flagged in +keep-core PR #4026 and resolved by adopting the Go derivation as +normative. diff --git a/pkg/tbtc/signer/src/engine.rs b/pkg/tbtc/signer/src/engine.rs index e03aad2f27..a80a7bbb2d 100644 --- a/pkg/tbtc/signer/src/engine.rs +++ b/pkg/tbtc/signer/src/engine.rs @@ -5141,21 +5141,77 @@ fn roast_hash_hex_with_components( Ok(hash_hex(&payload)) } -fn roast_attempt_seed_from_message_digest_hex( - message_digest_hex: &str, -) -> Result { - let message_digest_bytes = hex::decode(message_digest_hex).map_err(|_| { - EngineError::Internal("message digest hex must decode for attempt seed".to_string()) - })?; +/// Computes the RFC-21 `MessageDigest` from the raw signing message +/// bytes, mirroring keep-core's `messageDigestFromBigInt` exactly: the +/// message **is** the digest (in BIP-340 production the signed message is +/// already a 32-byte sighash), leading zero bytes are insignificant +/// (Go round-trips through `big.Int`, which strips them), the value is +/// big-endian left-padded with zeros to exactly 32 bytes, and anything +/// longer than 32 significant bytes is rejected. +/// +/// This is deliberately NOT the engine's internal transcript digest +/// (`hash_hex(message_bytes)` = SHA256 of the message), which continues +/// to feed `round_id`/`attempt_id` derivations. Feeding the transcript +/// digest into the shuffle seed was the cross-language divergence this +/// helper exists to prevent: the Go RFC-21 layer seeds from the padded +/// message itself. +fn rfc21_message_digest(message_bytes: &[u8]) -> Result<[u8; 32], EngineError> { + let significant_bytes = { + let first_significant_index = message_bytes + .iter() + .position(|byte| *byte != 0) + .unwrap_or(message_bytes.len()); + &message_bytes[first_significant_index..] + }; - if message_digest_bytes.len() < 8 { - return Err(EngineError::Internal( - "message digest must be at least 8 bytes for attempt seed".to_string(), - )); + if significant_bytes.len() > 32 { + return Err(EngineError::Validation(format!( + "message length [{}] exceeds the RFC-21 32-byte message digest; \ + attempt contexts only bind 32-byte signing digests", + significant_bytes.len() + ))); } + let mut digest = [0_u8; 32]; + digest[32 - significant_bytes.len()..].copy_from_slice(significant_bytes); + Ok(digest) +} + +/// Derives the legacy `i64` coordinator-shuffle seed per RFC-21 Annex A +/// (normative; see `docs/roast-coordinator-seed-derivation.md`): +/// +/// ```text +/// AttemptSeed32 = SHA256(KeyGroupBytes || SessionID || MessageDigest) +/// ShuffleSeed_i64 = int64_from_be_bytes(AttemptSeed32[0..8]) +/// ``` +/// +/// `key_group` is the canonical FROST key-group handle (for this engine: +/// the lowercase hex encoding of the serialized group verifying key); its +/// UTF-8 bytes feed the hash as an opaque string, matching keep-core's +/// `attempt.DeriveAttemptSeed` + `foldAttemptSeed` composition exactly. +/// `rfc21_message_digest` is the padded raw signing message (see +/// `rfc21_message_digest`), NOT the engine's SHA256 transcript digest. +/// The shuffle-source composition adds the RFC-21 **0-based** attempt +/// number; callers holding the 1-based wire attempt number must subtract +/// one before composing. +/// +/// Cross-language agreement is pinned by +/// `testdata/coordinator_seed_vectors.json`, a byte-identical copy of the +/// canonical file generated from the Go implementation in +/// `pkg/frost/roast` on the RFC-21 branch. +fn roast_attempt_shuffle_seed( + key_group: &str, + session_id: &str, + rfc21_message_digest: &[u8; 32], +) -> Result { + let mut hasher = Sha256::new(); + hasher.update(key_group.as_bytes()); + hasher.update(session_id.as_bytes()); + hasher.update(rfc21_message_digest); + let attempt_seed = hasher.finalize(); + let mut seed_bytes = [0_u8; 8]; - seed_bytes.copy_from_slice(&message_digest_bytes[..8]); + seed_bytes.copy_from_slice(&attempt_seed[..8]); Ok(i64::from_be_bytes(seed_bytes)) } @@ -5197,6 +5253,8 @@ fn roast_attempt_id_hex( fn validate_attempt_context( session_id: &str, + key_group: &str, + message_bytes: &[u8], message_digest_hex: &str, threshold: u16, attempt_context: Option<&AttemptContext>, @@ -5239,11 +5297,21 @@ fn validate_attempt_context( )); } - let attempt_seed = roast_attempt_seed_from_message_digest_hex(message_digest_hex)?; + // The shuffle seed binds the RFC-21 MessageDigest -- the padded raw + // signing message, exactly as the Go layer's + // `messageDigestFromBigInt` produces it -- NOT the engine's SHA256 + // transcript digest (`message_digest_hex`), which feeds only the + // `attempt_id` derivation below. Mixing the two was the + // coordinator-selection divergence flagged on the seed-unification + // review. + let attempt_seed = + roast_attempt_shuffle_seed(key_group, session_id, &rfc21_message_digest(message_bytes)?)?; + // The wire attempt_number is 1-based (enforced above); the RFC-21 + // Annex A shuffle composition uses the 0-based attempt number. let expected_coordinator_identifier = select_coordinator_identifier( &canonical_included_participants, attempt_seed, - attempt_context.attempt_number, + attempt_context.attempt_number - 1, ) .ok_or_else(|| { EngineError::Validation( @@ -6276,6 +6344,8 @@ pub fn start_sign_round(mut request: StartSignRoundRequest) -> Result, + } + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct CoordinatorSeedVector { + name: String, + key_group: String, + #[serde(rename = "sessionID")] + session_id: String, + message_digest_hex: String, + included_members: Vec, + attempt_number: u32, + wire_attempt_number: u32, + expected_shuffle_seed_int64: String, + expected_coordinator: u16, + } + + // Byte-identical copy of the canonical cross-language vector file + // generated from the Go implementation + // (pkg/frost/roast/testdata/coordinator_seed_vectors.json on the + // RFC-21 branch; regenerate there with ROAST_SEED_VECTORS_REGEN=1 + // and re-copy). Pins the RFC-21 Annex A derivation end to end so a + // semantic change on either side fails that side's own suite + // instead of fracturing coordinator agreement in a mixed + // deployment. + #[test] + fn coordinator_seed_derivation_matches_cross_language_vectors() { + let raw = include_str!("../testdata/coordinator_seed_vectors.json"); + let file: CoordinatorSeedVectorFile = + serde_json::from_str(raw).expect("coordinator seed vector file decodes"); + assert!( + !file.vectors.is_empty(), + "expected at least one coordinator seed vector" + ); + + let mut saw_negative_seed = false; + for vector in &file.vectors { + assert_eq!( + vector.wire_attempt_number, + vector.attempt_number + 1, + "wire attempt number must be the 1-based encoding in vector [{}]", + vector.name + ); + + // In production the engine receives the 32-byte signing + // digest AS its raw message; the seed binds that padded + // message directly. Treat the vector digest as the message + // so this test exercises the exact production relationship. + let message_bytes = + hex::decode(&vector.message_digest_hex).expect("vector digest decodes"); + let vector_rfc21_digest = + rfc21_message_digest(&message_bytes).expect("rfc21 message digest"); + assert_eq!( + hex::encode(vector_rfc21_digest), + vector.message_digest_hex.to_ascii_lowercase(), + "32-byte vector digest must round-trip the rfc21 padding in [{}]", + vector.name + ); + let shuffle_seed = roast_attempt_shuffle_seed( + &vector.key_group, + &vector.session_id, + &vector_rfc21_digest, + ) + .expect("shuffle seed derives"); + let expected_shuffle_seed: i64 = vector + .expected_shuffle_seed_int64 + .parse() + .expect("expected shuffle seed parses as i64"); + assert_eq!( + shuffle_seed, expected_shuffle_seed, + "shuffle seed mismatch in vector [{}]", + vector.name + ); + if expected_shuffle_seed < 0 { + saw_negative_seed = true; + } + + // The shuffle-source composition uses the RFC-21 0-based + // attempt number, exactly as `validate_attempt_context` + // composes it from the 1-based wire encoding. + let coordinator = select_coordinator_identifier( + &vector.included_members, + shuffle_seed, + vector.wire_attempt_number - 1, + ) + .expect("coordinator selects"); + assert_eq!( + coordinator, vector.expected_coordinator, + "coordinator mismatch in vector [{}]", + vector.name + ); + + // End to end: an attempt context carrying the wire-encoded + // attempt number and the vector's coordinator passes the + // engine's strict validation under the vector's key group. + // The attempt_id is bound to the engine's SHA256 transcript + // digest of the message, while the seed above bound the + // padded message itself -- the two-digest split the Go layer + // relies on. + let engine_message_digest_hex = hash_hex(&message_bytes); + let fingerprint = roast_included_participants_fingerprint_hex(&vector.included_members) + .expect("fingerprint"); + let attempt_id = roast_attempt_id_hex( + &vector.session_id, + &engine_message_digest_hex, + vector.wire_attempt_number, + coordinator, + &fingerprint, + ) + .expect("attempt id"); + let attempt_context = AttemptContext { + attempt_number: vector.wire_attempt_number, + coordinator_identifier: coordinator, + included_participants: vector.included_members.clone(), + included_participants_fingerprint: fingerprint, + attempt_id, + }; + validate_attempt_context( + &vector.session_id, + &vector.key_group, + &message_bytes, + &engine_message_digest_hex, + 2, + Some(&attempt_context), + true, + ) + .unwrap_or_else(|err| { + panic!( + "vector [{}] context failed engine validation: {err:?}", + vector.name + ) + }); + } + + assert!( + saw_negative_seed, + "vector file must pin at least one negative shuffle seed" + ); + } + + // Regression for the review finding on the seed-unification change: + // the engine must seed the coordinator shuffle from the padded raw + // message it receives (the Go layer's messageDigestFromBigInt + // output), NOT from its internal SHA256 transcript digest. This test + // reimplements the Go-side derivation inline -- independently of the + // engine helpers -- and proves the resulting context is accepted + // through the real strict-mode StartSignRound call path. + #[test] + fn start_sign_round_accepts_go_derived_attempt_context_in_strict_mode() { + let _guard = lock_test_state(); + reset_for_tests(); + let _roast_strict_mode = RoastStrictModeGuard::enable(); + + let session_id = "session-go-style-attempt-context"; + // A 32-byte signing digest, as production always supplies (the + // engine message IS the digest). + let message_hex = "5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953"; + + let dkg_result = run_dkg(RunDkgRequest { + session_id: session_id.to_string(), + participants: vec![ + crate::api::DkgParticipant { + identifier: 1, + public_key_hex: "02aa".to_string(), + }, + crate::api::DkgParticipant { + identifier: 2, + public_key_hex: "02bb".to_string(), + }, + ], + threshold: 2, + dkg_seed_hex: None, + }) + .expect("run dkg"); + + // --- Go-side derivation, reimplemented inline --- + // attempt.DeriveAttemptSeed(keyGroupBytes, sessionID, digest): + // SHA256 over the raw concatenation, digest = the 32 message + // bytes themselves. + let message_bytes = hex::decode(message_hex).expect("message decodes"); + let mut hasher = Sha256::new(); + hasher.update(dkg_result.key_group.as_bytes()); + hasher.update(session_id.as_bytes()); + hasher.update(&message_bytes); + let go_attempt_seed = hasher.finalize(); + // foldAttemptSeed: first 8 bytes, big-endian, reinterpreted i64. + let mut go_seed_bytes = [0_u8; 8]; + go_seed_bytes.copy_from_slice(&go_attempt_seed[..8]); + let go_shuffle_seed = i64::from_be_bytes(go_seed_bytes); + // SelectCoordinator(included, seed, attemptNumber): 0-based + // RFC-21 attempt number; first logical attempt = 0. + let included_participants = vec![1_u16, 2]; + let go_coordinator = + select_coordinator_identifier(&included_participants, go_shuffle_seed, 0) + .expect("go-style coordinator"); + + // --- FFI wire encoding of the same logical attempt --- + // wire attempt_number = RFC-21 AttemptNumber + 1; attempt_id is + // engine-defined over the SHA256 transcript digest. + let wire_attempt_number = 1_u32; + let engine_message_digest_hex = hash_hex(&message_bytes); + let fingerprint = roast_included_participants_fingerprint_hex(&included_participants) + .expect("fingerprint"); + let attempt_id = roast_attempt_id_hex( + session_id, + &engine_message_digest_hex, + wire_attempt_number, + go_coordinator, + &fingerprint, + ) + .expect("attempt id"); + + let round_state = start_sign_round(StartSignRoundRequest { + session_id: session_id.to_string(), + member_identifier: 1, + message_hex: message_hex.to_string(), + key_group: dkg_result.key_group, + taproot_merkle_root_hex: None, + signing_participants: Some(included_participants.clone()), + attempt_context: Some(AttemptContext { + attempt_number: wire_attempt_number, + coordinator_identifier: go_coordinator, + included_participants, + included_participants_fingerprint: fingerprint, + attempt_id, + }), + attempt_transition_evidence: None, + }) + .expect("strict StartSignRound must accept the Go-derived attempt context"); + assert_eq!(round_state.session_id, session_id); + } + struct InteractiveDkgFixture { pre_normalization_even_y: bool, part3_requests: BTreeMap, @@ -10410,6 +10733,29 @@ mod tests { } } + // Resolves the key group the engine will use when validating attempt + // contexts for `session_id`: the session's dealer-DKG key group when + // the session exists, otherwise a deterministic per-session stand-in + // for tests that exercise `validate_attempt_context` directly without + // creating a session. Construction and validation must use the same + // resolver or the RFC-21 Annex A seed derivation diverges. + fn attempt_context_key_group_for_tests(session_id: &str) -> String { + if let Ok(state) = state() { + if let Ok(guard) = state.lock() { + if let Some(key_group) = guard + .sessions + .get(session_id) + .and_then(|session| session.dkg_result.as_ref()) + .map(|dkg| dkg.key_group.clone()) + { + return key_group; + } + } + } + + format!("test-key-group:{session_id}") + } + fn build_deterministic_attempt_context( session_id: &str, message_hex: &str, @@ -10420,13 +10766,23 @@ mod tests { canonicalize_included_participants(&included_participants) .expect("canonical included participants"); let message_bytes = hex::decode(message_hex).expect("message hex"); - let message_digest_hex = hash_hex(&message_bytes); - let attempt_seed = roast_attempt_seed_from_message_digest_hex(&message_digest_hex) - .expect("attempt seed from message digest"); + let key_group = attempt_context_key_group_for_tests(session_id); + // RFC-21 seed input: the padded raw message, not the SHA256 + // transcript digest -- mirroring the Go layer's derivation. + let attempt_seed = roast_attempt_shuffle_seed( + &key_group, + session_id, + &rfc21_message_digest(&message_bytes).expect("rfc21 message digest"), + ) + .expect("attempt shuffle seed"); + assert!( + attempt_number >= 1, + "attempt_number is the 1-based wire encoding", + ); let coordinator_identifier = select_coordinator_identifier( &canonical_included_participants, attempt_seed, - attempt_number, + attempt_number - 1, ) .expect("deterministic coordinator"); @@ -10564,7 +10920,10 @@ mod tests { session_suffix in any::(), attempt_number in 1_u32..=16_u32, participants in participant_set_strategy(), - message_bytes in prop::collection::vec(any::(), 1..=128), + // RFC-21 attempt contexts only bind 32-byte signing digests + // (rfc21_message_digest rejects longer messages), so the + // strategy stays within that bound. + message_bytes in prop::collection::vec(any::(), 1..=32), ) { let session_id = format!("formal-attempt-session-{session_suffix}"); let message_hex = hex::encode(message_bytes); @@ -10593,11 +10952,13 @@ mod tests { &permuted_attempt_context.attempt_id ); - let message_digest_hex = hash_hex( - &hex::decode(&message_hex).expect("message hex should decode for validation"), - ); + let validation_message_bytes = + hex::decode(&message_hex).expect("message hex should decode for validation"); + let message_digest_hex = hash_hex(&validation_message_bytes); let validated_participants = validate_attempt_context( &session_id, + &attempt_context_key_group_for_tests(&session_id), + &validation_message_bytes, &message_digest_hex, 2, Some(&permuted_attempt_context), @@ -10616,7 +10977,10 @@ mod tests { session_suffix in any::(), attempt_number in 1_u32..=16_u32, participants in participant_set_strategy(), - message_bytes in prop::collection::vec(any::(), 1..=128), + // RFC-21 attempt contexts only bind 32-byte signing digests + // (rfc21_message_digest rejects longer messages), so the + // strategy stays within that bound. + message_bytes in prop::collection::vec(any::(), 1..=32), ) { let session_id = format!("formal-attempt-tamper-session-{session_suffix}"); let message_hex = hex::encode(message_bytes); @@ -10629,11 +10993,13 @@ mod tests { ); tampered_attempt_context.attempt_id = "11".repeat(32); - let message_digest_hex = hash_hex( - &hex::decode(&message_hex).expect("message hex should decode for validation"), - ); + let validation_message_bytes = + hex::decode(&message_hex).expect("message hex should decode for validation"); + let message_digest_hex = hash_hex(&validation_message_bytes); let err = validate_attempt_context( &session_id, + &attempt_context_key_group_for_tests(&session_id), + &validation_message_bytes, &message_digest_hex, 2, Some(&tampered_attempt_context), @@ -12666,7 +13032,22 @@ mod tests { }) .expect("start sign round"); - let mismatched_attempt = build_attempt_context(session_id, message_hex, 1, 1, vec![1, 2]); + // Pick the member that is provably not the deterministic + // coordinator so the test stays valid under any seed derivation. + let deterministic_attempt = + build_deterministic_attempt_context(session_id, message_hex, 1, vec![1, 2]); + let mismatched_coordinator = if deterministic_attempt.coordinator_identifier == 1 { + 2 + } else { + 1 + }; + let mismatched_attempt = build_attempt_context( + session_id, + message_hex, + 1, + mismatched_coordinator, + vec![1, 2], + ); let err = finalize_sign_round( FinalizeSignRoundRequest { session_id: session_id.to_string(), diff --git a/pkg/tbtc/signer/testdata/coordinator_seed_vectors.json b/pkg/tbtc/signer/testdata/coordinator_seed_vectors.json new file mode 100644 index 0000000000..6e9a722083 --- /dev/null +++ b/pkg/tbtc/signer/testdata/coordinator_seed_vectors.json @@ -0,0 +1,459 @@ +{ + "description": "Cross-language conformance vectors for the RFC-21 Annex A coordinator-shuffle seed derivation: ShuffleSeed_i64 = int64_be(SHA256(KeyGroupBytes || SessionID || MessageDigest)[0:8]); coordinator = GoMathRandShuffle(sorted(IncludedMembers), ShuffleSeed_i64 + int64(AttemptNumber))[0] with the 0-based RFC-21 AttemptNumber. wireAttemptNumber is the 1-based tbtc-signer FFI encoding of the same attempt. Canonical copy: pkg/frost/roast/testdata/coordinator_seed_vectors.json (Go); mirrored byte-identically to pkg/tbtc/signer/testdata/coordinator_seed_vectors.json (Rust).", + "vectors": [ + { + "name": "five-members-attempt-0", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5 + ], + "attemptNumber": 0, + "wireAttemptNumber": 1, + "expectedShuffleSeedInt64": "-2028522963755751589", + "expectedCoordinator": 3 + }, + { + "name": "five-members-attempt-1", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5 + ], + "attemptNumber": 1, + "wireAttemptNumber": 2, + "expectedShuffleSeedInt64": "-2028522963755751589", + "expectedCoordinator": 4 + }, + { + "name": "five-members-attempt-5", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5 + ], + "attemptNumber": 5, + "wireAttemptNumber": 6, + "expectedShuffleSeedInt64": "-2028522963755751589", + "expectedCoordinator": 5 + }, + { + "name": "sparse-members-attempt-0", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "includedMembers": [ + 2, + 7, + 9, + 11 + ], + "attemptNumber": 0, + "wireAttemptNumber": 1, + "expectedShuffleSeedInt64": "-2028522963755751589", + "expectedCoordinator": 11 + }, + { + "name": "different-session-changes-seed", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-2", + "messageDigestHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5 + ], + "attemptNumber": 0, + "wireAttemptNumber": 1, + "expectedShuffleSeedInt64": "-4761519992160790326", + "expectedCoordinator": 4 + }, + { + "name": "different-digest-changes-seed", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5 + ], + "attemptNumber": 0, + "wireAttemptNumber": 1, + "expectedShuffleSeedInt64": "4077301444353698382", + "expectedCoordinator": 1 + }, + { + "name": "opaque-key-group-handle", + "keyGroup": "roast-vector-opaque-key-group-handle", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5 + ], + "attemptNumber": 0, + "wireAttemptNumber": 1, + "expectedShuffleSeedInt64": "8068678687664591308", + "expectedCoordinator": 5 + }, + { + "name": "production-group-size-attempt-0", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100 + ], + "attemptNumber": 0, + "wireAttemptNumber": 1, + "expectedShuffleSeedInt64": "-2028522963755751589", + "expectedCoordinator": 55 + }, + { + "name": "production-group-size-attempt-3", + "keyGroup": "024d79b696a25e478a1c747fcaad380addbd8b2ef7c333126ab2e2c3b2533b7df2", + "sessionID": "session-roast-seed-vector-1", + "messageDigestHex": "f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100 + ], + "attemptNumber": 3, + "wireAttemptNumber": 4, + "expectedShuffleSeedInt64": "4077301444353698382", + "expectedCoordinator": 15 + }, + { + "name": "opaque-key-group-wide-set-attempt-7", + "keyGroup": "roast-vector-opaque-key-group-handle", + "sessionID": "session-roast-seed-vector-2", + "messageDigestHex": "f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1", + "includedMembers": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100 + ], + "attemptNumber": 7, + "wireAttemptNumber": 8, + "expectedShuffleSeedInt64": "-6872856820098921194", + "expectedCoordinator": 18 + } + ] +}