BIP Draft: Low-R Grinding for ECDSA Signatures - #2224
Conversation
Informational BIP describing low-R grinding: re-deriving the ECDSA nonce under a counter until r has its high bit unset, so the DER encoding needs no leading null byte.
|
For reviewers, see also discussion in bitcoinj/secp256k1-jdk#419 and the following posts by @Sjors (ordered by date inc) in the relevant Bitcoin Core PR: |
|
Thanks, adding this to my to-read list. |
|
Another important motivation is that signatures produced according to a standardised approach will be byte-identical. This means signers can be compared to ensure they are not leaking secrets by embedding them in signatures (e.g. Dark Skippy). |
murchandamus
left a comment
There was a problem hiding this comment.
Looks very good. I just have a couple nits for you.
| The *s* value of a signature can be normalized through simple negation, resulting in an equally valid signature whose *s* value has its most significant bit unset. A signature's *r* value cannot be modified in a similar way: it is the x-coordinate of the nonce point, and is fixed once the nonce is chosen. Its most significant bit is set about half the time. | ||
| Thus, about half of ECDSA signatures carry an extra padding byte when DER encoded. | ||
|
|
||
| The only way to obtain a different *r* is to re-sign with a different nonce. Low-R Grinding takes two signing operations on average[^geometric] and saves roughly 0.5 vbytes per signature in legacy inputs and 0.125 vbytes in v0 segwit inputs.[^optech] |
There was a problem hiding this comment.
When it is needed it saves a whole byte, so maybe add “in average”:
| The only way to obtain a different *r* is to re-sign with a different nonce. Low-R Grinding takes two signing operations on average[^geometric] and saves roughly 0.5 vbytes per signature in legacy inputs and 0.125 vbytes in v0 segwit inputs.[^optech] | |
| The only way to obtain a different *r* is to re-sign with a different nonce. Low-R Grinding takes two signing operations on average[^geometric] and saves in average roughly 0.5 vbytes per signature in legacy inputs and 0.125 vbytes in v0 segwit inputs.[^optech] |
|
|
||
| The only way to obtain a different *r* is to re-sign with a different nonce. Low-R Grinding takes two signing operations on average[^geometric] and saves roughly 0.5 vbytes per signature in legacy inputs and 0.125 vbytes in v0 segwit inputs.[^optech] | ||
|
|
||
| Combined with normalizing *s*, Low-R Grinding lets a signer produce ECDSA signatures for arbitrary messages with a fixed *maximum* size: 70 bytes of DER, 71 with the sighash flag. This allows for lower fees and more accurate transaction size predictions. Smaller *r* values could be ground for in theory, but are not in Low-R Grinding.[^pr13666] |
There was a problem hiding this comment.
You could also mention that for 2-of-3 P2SH inputs, having at least one high-r signature increases the input by an additional 2 bytes because the input script’s length indicator then requires three bytes. For 2-of-3 P2SH inputs, roughly 25% of inputs have two low-r signatures and take 293 bytes, about 50% have one high-r signature and take 296 bytes, and 25% of inputs have two high-r signatures that take 297 bytes. So for 2-of-3 P2SH inputs, the expected savings of low-r grinding is 2.5 vB.
|
The document should have a Rationale section. The Rationale should describe design decisions, compare to potential alternate designs and related work. You could mention the suggestion that Craig gave there. The explanation why incrementing a counter on top of RFC6979 is safe and recommend could also be located in that section. Some of the Footnotes would fit there as well, in fact, many BIPs use the Footnotes section as the Rationale. |
|
Since the reference implementation and test vectors are still marked TODO, I'd like to offer help there, if you aren't already deep into them. What I have in mind is an independent Python implementation of the recommended instantiation: pure Python secp256k1, RFC 6979 with the grind counter in the additional data, the grinding loop, and DER encoding, plus a script that generates the vectors and a separate one that verifies them, everything checked byte for byte against Bitcoin Core's signer. I built the spend-path vectors for the BIP 360 draft recently (#2232), so this is familiar ground. The reason I think an implementation that shares no code with libsecp is worth having: if it agrees with Core on the vectors, that's good evidence the text is unambiguous, and if it doesn't, the first diverging intermediate value shows exactly which part needs tightening. If that sounds useful, two things worth deciding up front:
Happy to open the result as a PR against your branch, or just post it here, whichever works better for you. |
Informational BIP describing low-R grinding: re-deriving the ECDSA nonce under a counter until r has its high bit unset, so the DER encoding needs no leading null byte.
This was discussed on the mailing list: https://groups.google.com/g/bitcoindev/c/boEZRlqczvw
Reference implementation and test vectors are TODO.
Feedback is very welcome.