Fixed-base comb: p256-wallet 179,890 → 147,113 B (and why Straus doesn't pay) - #159
Closed
icellan wants to merge 1 commit into
Closed
Fixed-base comb: p256-wallet 179,890 → 147,113 B (and why Straus doesn't pay)#159icellan wants to merge 1 commit into
icellan wants to merge 1 commit into
Conversation
One doubling and one conditional add per COLUMN instead of per bit, wherever
the base point is a compile-time constant: p256MulGen, p384MulGen, and the
u1*G half of ECDSA verification. u2*Q keeps the ladder — Q arrives in the
witness.
p256-wallet 958,792 -> 179,890 (sink) -> 147,113 (-84.7%)
p384-wallet 1,963,300 -> 272,678 -> 223,204 (-88.6%)
emitP256MulGen 90,676 -> 54,117 (-40.3%)
emitP384MulGen 136,599 -> 81,418 (-40.4%)
emitVerifyECDSA_P256 195,120 -> 158,560 (-18.7%)
Opt-in and inert by default: all 72 goldens still reproduce byte-for-byte and
script-size-check is 72/72 ok.
WHY NOT STRAUS
The obvious move is a joint ladder for u1*G + u2*Q. Measured, it does not pay.
The ladder's speed comes from the CHEAP incomplete mixed add, justified in
buildJacobianAddOrDoubleInline by an interval argument over c_i mod n — an
argument that holds because the accumulator is c_i*P and the addend is P, one
generator. A joint ladder makes the accumulator c_i*G + d_i*Q with Q supplied
by the caller; an attacker choosing Q = k*G solves c_i + d_i*k == 1 (mod n) for
k, so the exception becomes reachable at an arbitrary step. Completing the
addition costs a measured +299 B/round (a whole ladder goes 90,610 -> 167,410,
+84.8%), against 690 B/round for two independent ladders — so joint + complete
lands at ~700 B/round. A loss. The comb keeps a single generator, so the
argument survives.
SOUNDNESS
comb.ts re-derives the interval argument for the comb rather than assuming it,
as executable arithmetic:
- combParams searches for the scalar offset m with m*n >= 2^(w*d-1) and
(m+1)*n - 1 < 2^(w*d), so the first digit is never zero and the accumulator
never starts at infinity. For P-256 at w=3 that returns the same +3n the
ladder hardcodes; for P-384 at w=3 it returns +5n. Reusing +3n there would
have left the leading digit free to vanish.
- combSafeRounds proves, per round, that the pre-add accumulator cannot be 0,
+T[j] or -T[j] modulo n for any table entry, over the whole scalar domain.
Rounds it cannot prove get the complete add-or-double form. For P-256 at
w=3 it proves 81 of 86, so the fallback costs ~1.2 kB. `true` is never
assumed.
The window width is not hardcoded: cEmitCombBest renders w = 2, 3 and 4 in full
and keeps whichever estimateScriptBytes scores smallest.
TESTING
ec-comb.test.ts is a differential against the binary ladder on the real @bsv/sdk
engine over the scalars the argument turns on — 0, 1, small values, n-1, n, n+1,
2n, negatives, and the powers of two either side of each block boundary
(2^85, 2^86, 2^171, 2^172, 2^255) — plus a cross-check against the INDEPENDENT
generic-point ladder so a shared bug in the MulGen wrapper cannot hide. Then the
verifier under an OpenSSL oracle: genuine signature accepted, seven near-misses
rejected, comb and ladder agreeing on every one.
comb-table.test.ts pins the compile-time arithmetic against published vectors
(2G, n*G = infinity), checks every table entry is on the curve for both curves
and w in {2,3,4}, and checks the safety analysis is monotone under a widened
domain — a checker that proved every round would be broken, not clever.
NOT IN SCOPE
secp256k1. comb.ts is curve-generic, but the emitter uses the NIST codegen's
a = -3 doubling; ec-codegen.ts needs its own wiring, which is why ec-primitives,
ec-demo, schnorr-zkp, ec-unit and convergence-proof are unchanged here.
Owner
Author
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.
Stacked on #158 (targets
feat/ec-reduction-sinking). Opt-in via--ec-fixed-base-comb; inert by default, all 72 goldens reproduce byte-for-byte,script-size-check72/72 ok.Results
p256-walletp384-walletp256-primitivesp384-primitivesPer emitter:
emitP256MulGen90,676 → 54,117 (−40.3 %),emitP384MulGen136,599 → 81,418 (−40.4 %),
emitVerifyECDSA_P256195,120 → 158,560 (−18.7 %).Straus/Shamir was measured first, and rejected
The obvious move is a joint ladder for
u1·G + u2·Q. It does not pay, and thereason is worth recording.
The ladder's speed comes from the cheap incomplete mixed add.
buildJacobianAddOrDoubleInlinejustifies using it everywhere but the last stepwith an interval argument over
c_i mod n— which works because the accumulatoris
c_i·Pand the addend isP: one generator, coefficient fixed by the scalarand the step. A joint ladder makes the accumulator
c_i·G + d_i·Qwith Qsupplied by the caller. An attacker choosing
Q = k·Gsolvesc_i + d_i·k ≡ 1 (mod n)fork— one equation, one free variable — so theexception becomes reachable at an arbitrary step. The argument does not transfer.
Completing the addition costs a measured +299 B/round (a whole ladder goes
90,610 → 167,410, +84.8 %):
So Straus wins only by dropping an unconditional guarantee for a DLP-hardness
one, inside a signature verifier. The comb keeps a single generator, so the
argument survives — that is why this PR is a comb and not a joint ladder.
Soundness
passes/comb.tsre-derives the interval argument rather than assuming it, asexecutable arithmetic:
combParamssearches for the scalar offsetmwithm·n ≥ 2^(w·d−1)and(m+1)·n − 1 < 2^(w·d), so the first comb digit is never zero and theaccumulator never starts at infinity. For P-256 at w=3 it returns the same
+3nthe ladder hardcodes — and for P-384 at w=3 it returns+5n.Reusing
+3nthere would have left the leading digit free to vanish.combSafeRoundsproves per round that the pre-add accumulator cannot be0,+T[j]or−T[j]mod n for any table entry, over the whole scalardomain. Rounds it cannot prove get the complete add-or-double form. For P-256
at w=3 it proves 81 of 86, so the fallback costs ~1.2 kB.
trueis neverassumed.
The window width is not hardcoded either:
cEmitCombBestrenders w = 2, 3, 4 infull and keeps whichever
estimateScriptBytesscores smallest.Testing
ec-comb.test.ts— differential against the binary ladder on the real@bsv/sdkengine over the scalars the argument turns on: 0, 1, small values,
n−1,n,n+1,2n, negatives, and the powers of two either side of every block boundary(2^85, 2^86, 2^171, 2^172, 2^255). Plus a cross-check against the independent
generic-point ladder, so a shared bug in the
MulGenwrapper cannot hide. Thenthe verifier under an OpenSSL oracle: genuine signature accepted, seven
near-misses rejected, comb and ladder agreeing on each.
comb-table.test.ts— compile-time arithmetic against published vectors (2G,n·G= infinity), every table entry on-curve for both curves and w ∈ {2,3,4},and the safety analysis monotone under a widened domain. A checker that proved
every round would be broken, not clever, so that is asserted too.
Not in scope
secp256k1.
comb.tsis curve-generic, but the emitter uses the NISTcodegen's
a = −3doubling, soec-codegen.tsneeds its own wiring. That is whyec-primitives,ec-demo,schnorr-zkp,ec-unitandconvergence-proofareunchanged in the table above — worth roughly another −35 % on 4.5 MB of fixtures.
The 7-tier port. Same posture as #156 and #158: the flag ships off.