Skip to content

Commit 786cead

Browse files
authored
feat(frost/roast): RFC-21 Phase 3.3 -- aggregation + bundle verification (#3970)
## Summary Third Phase-3 implementation PR. Adds the methods that drive the ROAST coordinator-aggregation flow defined in RFC-21's **Resolved Decisions** section: | Method | Role | |---|---| | \`RecordEvidence(handle, snap)\` | Accept a peer's signed \`LocalEvidenceSnapshot\`. Validates structure, verifies the operator signature, checks the snapshot's \`AttemptContextHash\` matches the handle's bound context, applies first-write-wins / equal-or-reject. | | \`AggregateBundle(handle)\` | Called by the elected coordinator. Sorts accumulated snapshots ascending by \`SenderID\`, builds the \`TransitionMessage\`, signs the canonical bundle bytes, transitions state to \`Transitioned\`. | | \`VerifyBundle(handle, msg)\` | Called by every receiver. Verifies coordinator signature, every snapshot's operator signature, and -- if the receiver has submitted its own snapshot -- presence of that snapshot in the bundle (censorship detection). | **Stacked on #3969 (Phase 3.2).** ## What's new ### \`pkg/frost/roast/signature.go\` - \`Signer\` / \`SignatureVerifier\` interfaces (Phase 4 wires them to \`pkg/net\`'s operator-key + member-keys surfaces). - \`NoOpSigner\` / \`NoOpSignatureVerifier\` for tests that don't exercise the crypto pipeline. - \`CanonicalSnapshotBytes\` -- JSON of snapshot fields *excluding* \`OperatorSignature\`. - \`CanonicalBundleBytes\` -- JSON of bundle fields *excluding* \`CoordinatorSignature\` but *including* every snapshot's \`OperatorSignature\`, so the coordinator's signature attests to the exact assembled set. - \`verifySnapshotSignature\` / \`verifyBundleSignature\` / \`verifyOwnObservationsPresent\` -- the receiver-side checks, each testable in isolation. - Sentinels: \`ErrSignatureInvalid\`, \`ErrSignatureMissing\`, \`ErrCensorshipDetected\`. ### \`pkg/frost/roast/coordinator_state.go\` (extended) - \`Coordinator\` interface gains \`RecordEvidence\`, \`AggregateBundle\`, \`VerifyBundle\`. - \`NewInMemoryCoordinatorWithSigning(selfMember, signer, verifier)\` -- production constructor (Phase 4 callers). - \`NewInMemoryCoordinator()\` preserved as a Phase-3.1-compatible convenience that uses no-op signing. - New sentinels: \`ErrNotAggregator\`, \`ErrAttemptStateInvalid\`, \`ErrAttemptContextMismatch\`, \`ErrSnapshotConflict\`. ### \`pkg/frost/roast/transition_message.go\` (touched) - \`validate()\` methods promoted to public \`Validate()\` on both \`LocalEvidenceSnapshot\` and \`TransitionMessage\` so callers that construct messages in memory can validate without a marshal/unmarshal round-trip. ## What's tested ### \`signature_test.go\` (13 cases) Signature interfaces, canonical encodings, signature verification round-trips (via a deterministic SHA-256 fake signer/verifier), tampered-payload rejection, coordinator-mismatch rejection, censorship-detection helper (missing snapshot, mutated signature, skip semantics). ### \`bundle_aggregation_test.go\` (11 cases) - \`RecordEvidence\`: nil rejection, unknown handle, context hash mismatch, bad signature, valid-and-idempotent re-submission, conflict rejection, self-submission tracking. - \`AggregateBundle\`: non-aggregator rejection, signed bundle build (size, ordering, signature, terminal state), **deterministic bundle JSON across different record orderings**. - \`VerifyBundle\`: valid acceptance, **censorship detection**, coordinator-signature forgery, snapshot-signature forgery, attempt-context mismatch, nil message, unknown attempt, concurrent record-and-aggregate safety. ### Verification | Command | Result | |---|---| | \`go build ./...\` | clean | | \`go test ./pkg/frost/roast/...\` | pass | | \`go test -race ./pkg/frost/roast/...\` | pass | | \`go test -tags 'frost_native frost_tbtc_signer' ./pkg/frost/...\` | pass (5 packages) | | \`staticcheck -checks '-SA1019' ./pkg/frost/roast/...\` | silent | | \`go vet ./pkg/frost/roast/...\` | clean | | \`gofmt -l ./pkg/frost/roast/\` | silent | ## Why the censorship-detection check is what it is A receiver that has submitted its own snapshot but is missing from the bundle has two possible explanations: (1) the elected coordinator maliciously dropped the snapshot, or (2) the bundle was assembled before the receiver's submission arrived. In either case, feeding the bundle into \`NextAttempt\` would penalise the receiver (via silence-parking), so the bundle must be rejected pending re-broadcast on the next attempt. \`ErrCensorshipDetected\` is the unambiguous signal. When the receiver has not yet submitted (selfMember == 0 or selfSubmission == nil), the check is skipped: there is no submitted snapshot whose presence to verify. ## Phase 3 status | PR | Scope | State | |---|---|---| | 3.1 (#3968) | Coordinator skeleton + seed bridge | open | | 3.2 (#3969) | TransitionMessage + LocalEvidenceSnapshot | open | | **3.3 (this)** | **Aggregation + bundle verification** | **open** | | 3.4 | NextAttempt policy + thresholds | next | ## Test plan - [ ] CI green. - [ ] Reviewer confirms the censorship-detection semantics are acceptable (specifically that we *reject* the bundle on missing self-snapshot rather than accept it and let silence-parking catch it). - [ ] Reviewer confirms the canonical-encoding contract (snapshot omits its own signature; bundle includes snapshot signatures but omits its own).
2 parents d9450ed + 634b2ed commit 786cead

5 files changed

Lines changed: 1365 additions & 27 deletions

File tree

0 commit comments

Comments
 (0)