Skip to content

Add ECDSA adaptor test vectors for edge cases - #219

Open
fametrano wants to merge 2 commits into
discreetlogcontracts:masterfrom
fametrano:ecdsa-adaptor-vectors
Open

fametrano wants to merge 2 commits into
discreetlogcontracts:masterfrom
fametrano:ecdsa-adaptor-vectors

Conversation

@fametrano

@fametrano fametrano commented Oct 10, 2026 •

Copy link
Copy Markdown

I implemented ECDSA-adaptor.md independently (btclib-ecc, Python) and compared it with libsecp256k1-zkp's module. I added 22 vectors, in the file's schema, for cases the file did not reach: recovery with -Y, a wrong key, a wrong s or s = 0; verification against another message, signing key or encryption key, with a changed s_a, or with a message hash above the curve order; a proof whose A_G and A_Y, or A_Y alone, are the point at infinity; R with r = 0; R or R_a with an invalid prefix, an x-coordinate of p + 1, or an x off the curve; and a proof b or c of zero, or c of n or 2^256 - 1. Vectors 12, 24, 32 and 33 are valid; the other eighteen must fail. Every value is recomputed from the specification's algorithms, and both my implementation and libsecp256k1-zkp pass all 33 vectors.

Three of them need your call, not only a vector. The specification never says r ≠ 0; vector 19 assumes ECDSA's rule, which libsecp256k1-zkp also applies. The specification reads the proof's scalars only in DLEQ_verify, so it does not say whether decrypt and recover must reject a proof c at or above n. Vectors 22 and 23 make such a c fail to parse, as the file already does for s_a; libsecp256k1-zkp rejects it in verify but accepts it in decrypt and recover, which skip the proof. I left out a proof b at or above n, which libsecp256k1-zkp reduces modulo n and accepts even in verify: my comment below proposes rejecting it.

Assisted-by: my usual tools (LLM, Internet, computer). Mistakes are mine.

Twelve vectors reach cases the file did not: recovery with -Y,
verification against another message, signing key or encryption key,
a changed s_a, a proof whose A_G and A_Y are the point at infinity, R
with an invalid prefix or with r = 0, recovery with a wrong key or a
wrong s, and a proof c of n or 2^256 - 1.

The last two vectors treat a proof c of n or more as failing to parse.
libsecp256k1-zkp rejects them in verify but accepts them in decrypt and
recover, which skip parsing the proof.

Assisted-by: my usual tools (LLM, Internet, computer). Mistakes are mine.
Signed-off-by: Ferdinando Ametrano <ferdinando@ametrano.net>
@fametrano

Copy link
Copy Markdown
Author

On the proof b at or above n, which I left out of the vectors: libsecp256k1-zkp reduces it modulo n and accepts it, on purpose. In its review LLFourn, who wrote this specification, noted that "overflow is fine here", since b is a hash output and an overflowing one is computationally unreachable (secp256k1-zkp#117). His ecdsa_fun compares b's bytes with the hash output instead, so it rejects b + n.

I suggest the specification require DLEQ_verify to reject b at or above n, like c. Its serialization vectors already ask that a parsed signature serialize back to the same bytes, which a reduced b does not, so only this reading can be tested by a vector. BIP374, whose DLEQ proof also carries its challenge, made the same choice in bips#2262. This is not a security issue: a second encoding b + n exists for about 2^-128 of proofs, and nobody can produce one at will. libsecp256k1-zkp and my implementation would change, and ecdsa_fun only for hashes at or above n. If you agree, I will add a vector with b at or above n that fails. I asked about c in decrypt and recover in secp256k1-zkp#379.

Ten vectors reach cases the file did not yet: a message hash above the
curve order, A_Y alone at the point at infinity, recovery from a
signature whose s is zero, R or R_a with an x-coordinate of p + 1 or
off the curve, R_a with an invalid prefix, and a proof b or c of zero,
which parses.

Assisted-by: my usual tools (LLM, Internet, computer). Mistakes are mine.
Signed-off-by: Ferdinando Ametrano <ferdinando@ametrano.net>
@fametrano

Copy link
Copy Markdown
Author

I added ten vectors, 24 to 33, and updated the description to list them. Two kinds are less obvious: R or R_a with an x-coordinate of p + 1 catches a parser that reduces x modulo p, since x = 1 is on the curve; a proof b or c of zero must parse, which catches one that refuses a zero scalar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant