test: add live mixed-binary legacy signing interop harness - #32
Merged
Merged
Conversation
Closes the mixed-binary assurance gap left by the existing session-less legacy Bob/BobWC coverage (crypto/mta/legacy_bob_historical_witness_test.go, ecdsa/signing/round_3_test.go), which pins the shape of a historical witness using this implementation's own prover equations but never exercises an actual historical binary. Adds testdata/legacy_transcript/historical_signer, a subprocess entry point compiled only inside the pinned historical (threshold-network/tss-lib@ 2e71268) module (same pin as the existing oracle), driven over a bounded newline-delimited JSON protocol on stdin/stdout. Adds testdata/legacy_transcript/mixed_interop, the current-side orchestrator that drives a live 2-of-2 ECDSA signing ceremony against that subprocess and asserts: - reject: a default-configured current party (ProtocolModeLegacy, compat off) fails closed at round 3 against the historical peer's real, live-drawn Bob/BobWC witness, whose T1 (recovered from the actual wire bytes, not a hand-picked value) is asserted to exceed the default tight N+q^6 bound. - accept: the identical exchange with SetLegacyHistoricalBobCompatibility (true) succeeds at round 3 and provably progresses through round 8 in both directions. - homogeneous-control: two current-only parties complete the identical ceremony shape through round 8, proving reject is specific to the historical witness range and not a general legacy-mode defect. Adds verify_mixed_interop.sh, following verify.sh's existing pattern, as the durable script CI/developers run to catch regressions in historical/current round interop or the compatibility opt-in wiring; confirmed to fail when the opt-in wiring is deliberately broken (LegacyHistoricalBobCompatibility() forced to return false) and to pass again once restored. No production code changes; test/oracle files only under testdata/legacy_transcript.
…file split
Addresses PR review findings on the mixed-binary legacy signing interop
harness:
1. Independent Bob/BobWC per-proof verification: previously the reject
scenario only checked an aggregate round-3 pass/fail, which cannot
distinguish a regression where only one of Bob/BobWC is correctly
gated by the compatibility flag (e.g. Bob accepts the widened bound
but BobWC stays tight, or vice versa) from a fully-correct
implementation, as long as the OTHER proof still legitimately fails.
Both the reject and accept scenarios now independently re-verify Bob's
and BobWC's proofs via ProofBob.VerifyLegacy/ProofBobWC.VerifyLegacy,
using the actual captured wire proof and reconstructed public inputs
(Alice's own Paillier key and Ring-Pedersen parameters per
ecdsa/signing/round_3.go's exact calling convention, her round-1
ciphertext, Bob's round-2 response, and Bob's PrepareForSigning-derived
EC contribution — correctly resliced via
keygen.BuildLocalSaveDataSubset to the 2-party signing session, not the
fixture's underlying 20-party keygen ceremony), asserting each proof
independently rejects at the tight bound and accepts at the loose
bound.
2. Structural (non-text) rejection constraints: the reject scenario's
round-3 failure is now constrained to tss.Error.Round() == 3, the
historical peer's party ID present in tss.Error.Culprits(), and no
round-3 message ever emitted by the current party — replacing an
AliceProgressed-based proxy, and never parsing the error string.
3. Split mixed_interop/main.go (was 583 lines, over the repository's
500-line file cap) into main.go (entry point), peer.go (subprocess
wire protocol), scenarios.go (scenario logic and per-proof
verification), and homogeneous.go (the control scenario). All four
files are now under 500 lines.
Also fixed two bugs surfaced while implementing the above:
- runHomogeneousControl delivered a message back to its own sender
instead of routing it to the other party (a latent bug from an
earlier pass), and stopped after forwarding a round-8-or-later
message instead of before, cascading into an expected-but-unhandled
round-9 reconstruction error for this minimal fixture subset.
- The per-proof verification call was only reachable from the
non-rejecting exit path of runMixedScenario, so it silently never ran
for the reject scenario (leaving all four fields at their zero value)
until extracted into a shared helper called from every exit path.
Verified: 15 consecutive clean runs of the full harness after the fix;
confirmed the harness still fails when
LegacyHistoricalBobCompatibility() is patched to force-return false and
passes again once reverted.
Author
|
Final integration evidence at
Scope: the harness intentionally stops after round 8 because its two fixture shares come from a 2-of-20 key set with threshold 10; it does not claim final signature completion. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the mixed-binary assurance gap: the existing session-less legacy
Bob/BobWC coverage (
crypto/mta/legacy_bob_historical_witness_test.go,ecdsa/signing/round_3_test.go) pins the shape of a historical witnessusing this implementation's own prover equations, which proves the
verifier's bound math but never exercises an actual historical binary.
This PR adds a harness that drives a real, live signing exchange
against a subprocess running the pinned historical
threshold-network/tss-lib@2e712689commit — the same commit alreadyqualified by
testdata/legacy_transcript/verify.sh's oracle vectors.Test/oracle files only, all under
testdata/legacy_transcript/. Noproduction code changes.
What's added
testdata/legacy_transcript/historical_signer/main.go— a subprocessentry point, compiled only inside the pinned historical module (same
historical/go.mod.fixture/go.sum.fixturepin the oracle already uses).Drives one live historical
ecdsa/signing.LocalParty(party index 1,"Bob") over a bounded, newline-delimited JSON protocol on stdin/stdout
(
init/deliver/quitin,message/signature/error/turn_doneout). No network access at run time.
testdata/legacy_transcript/mixed_interop/main.go— the current-sideorchestrator (
go runfrom the repo root, same invocation style as theoracle). For a fixed 2-of-20
keygen_data_0/1fixture pair, deterministicseeds, and a fixed message, it drives:
ProtocolModeLegacywith the default(off)
SetLegacyHistoricalBobCompatibilityfails closed at round 3against the historical peer's real, live-drawn Bob/BobWC witness
(MtA blinding value sampled below the Paillier modulus N, per the
historical
BobMid/BobMidWC). The witness's T1 is recovered from theactual wire bytes received from the subprocess (via
SignRound2Message.UnmarshalProofBob/UnmarshalProofBobWC, not ahand-picked constant) and asserted to exceed the default tight
N + q^6bound — so the rejection is proven meaningful, not vacuous.SetLegacyHistoricalBobCompatibility(true)succeeds at round 3 andprovably progresses: the current party emits its round-3 message, the
historical peer accepts it, and the live exchange continues through
round 8 in both directions.
historical subprocess at all, drive the identical ceremony shape
through round 8 under the default configuration — proof that "reject"
is specific to the historical witness range and not a general
legacy-mode defect, so a homogeneous run is never conflated with a
mixed-binary one.
testdata/legacy_transcript/verify_mixed_interop.sh— the durable script,following
verify.sh's existing pattern, that sets up the temporarypinned module and runs the above end-to-end.
testdata/legacy_transcript/README.md— new section documenting theharness and its scope.
Why the exchange stops at round 8
This repository's own existing
round3Fixture(
ecdsa/signing/round_3_test.go) andhistoricalBobProofForWitnessY(
crypto/mta/legacy_bob_historical_witness_test.go) already establish theprecedent of driving a 2-of-20 minimal subset of the
test/_ecdsa_fixtureskeygen fixtures (threshold 1, not the fixture set'sreal threshold 10) for this exact class of round-level interop check. That
minimal subset is sufficient for every per-peer MtA/Schnorr check through
round 8 (each is a property of the two parties' own consistent local
computation), but round 9's final aggregate check (
U == T) verifies aglobal Shamir reconstruction identity that only holds for a
correctly-sized threshold+1 co-signer set.
Reaching a real, live-exchanged round 8 message already proves the
historical Bob/BobWC witness was accepted and every subsequent round 3–8
verification/decommitment step (Bob_end, the Gamma/Schnorr proofs, and both
decommitments) succeeded against a genuine historical binary. Driving a
full, globally-valid signature to completion is technically possible but
requires
testThreshold+1(11) correctly-thresholded co-signers ratherthan an arbitrary 2-of-20 subset — substantially more harness complexity
for a property (global reconstruction validity) that is orthogonal to the
specific Bob/BobWC compatibility mechanism this harness exists to exercise.
This is the "exact inaccessible prerequisite" for full-ceremony completion
within this minimal-fixture design, not a claim that a full ceremony is
impossible.
A note on exact-byte reproducibility
Signing round 2 (
ecdsa/signing/round_2.go) draws the Bob and BobWCwitnesses from two goroutines running concurrently against the
process-global
crypto/rand.Reader, so which goroutine consumes whichslice of the deterministic keystream is scheduler-dependent — the exact
witness scalar differs slightly run to run (confirmed empirically). Every
run nonetheless deterministically reproduces the qualitative property
under test: a high witness that exceeds the tight bound, a closed-by-default
rejection, and an opt-in acceptance that keeps progressing. That qualitative
property is what
verify_mixed_interop.shasserts and fails on. Theharness's own
deterministicReaderwas made mutex-protected specifically toavoid the corrupted (non-well-formed) draws a naive shared-stream
implementation would otherwise produce under this concurrency.
Verification
Ran clean (exit 0) across 5 consecutive runs. Confirmed the script fails
(non-zero exit,
FAIL: opt-in current party did not accept and progress past round 3) whentss.Parameters.LegacyHistoricalBobCompatibility()ispatched to unconditionally return
false(simulating a real wiringregression), and passes again once reverted — proving this is a durable
regression test for the compatibility opt-in wiring, not a tautology.
No error-wording/source-text assertions: all pass/fail signals are
behavioral (error non-nil/nil, message round-type via protobuf content type
switch, T1 field comparison against the computed bound).
Findings addressed: mixed-binary assurance gap for legacy signing
Bob/BobWC compatibility — closes the gap between the existing session-less,
same-binary-only proof-shape tests and a genuine live cross-version
exchange.