secp256k1_ec_seckey_negate
is meant to be used for secret keys, but it obviously works in general for scalars modulo the group order.
test: refactor: negate signature-s using libsecp256k1 #32436
pull theStack wants to merge 1 commits into bitcoin:master from theStack:202505-test-negate-signature_s-with-libsecp changing 1 files +9 −16-
theStack commented at 10:44 am on May 7, 2025: contributorThis small PR gets rid of manual mod-n inversion of the ECDSA signature-s part in unit tests (introduced a long time ago in #5256, triggered by https://github.com/bitcoin-core/secp256k1/pull/69) by using secp256k1 instead. The function wasn’t available at that time, but was introduced about three years later, see https://github.com/bitcoin-core/secp256k1/pull/408. Note that as the name suggests,
-
DrahtBot commented at 10:44 am on May 7, 2025: contributor
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Code Coverage & Benchmarks
For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32436.
Reviews
See the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
-
DrahtBot added the label Tests on May 7, 2025
-
w0xlt commented at 6:05 pm on May 7, 2025: contributor
-
in src/test/script_tests.cpp:152 in bfb7b88760 outdated
161- int n = (int)order[p] - s[p] - carry; 162- s[p] = (n + 256) & 0xFF; 163- carry = (n < 0); 164- } 165- assert(carry == 0); 166+ // Perform mod-n negation of s by (ab)using libsecp256k1
rkrux commented at 12:48 pm on May 8, 2025:Note that as the name suggests, secp256k1_ec_seckey_negate is meant to be used for secret keys, but it obviously works in general for scalars modulo the group order.
I can see a call to
secp256k1_scalar_negate
insidesecp256k1_ec_seckey_negate
.Nit: However, the current comment makes one wonder why it is (ab)using libsecp256k1, which is answered in the commit message - might as well add the reasoning in the comment here itself to get rid of any doubt in the future.
theStack commented at 2:22 pm on May 8, 2025:Added a comment.in src/test/script_tests.cpp:155 in bfb7b88760 outdated
162- s[p] = (n + 256) & 0xFF; 163- carry = (n < 0); 164- } 165- assert(carry == 0); 166+ // Perform mod-n negation of s by (ab)using libsecp256k1 167+ int ret = secp256k1_ec_seckey_negate(secp256k1_context_static, s.data() + 1);
rkrux commented at 12:57 pm on May 8, 2025:I can see the earlier implementation also ended the iteration at second index (1), which is ok.
However, the
+ 1
is quite apparent now & I’m not entirely sure why this is the case. It would be helpful to know the reason and document it in code if possible.
laanwj commented at 1:32 pm on May 8, 2025:The reason is that the first byte cannot have the upper bit set, as this would mean a negative number, which would be illegal here. So the number can be 33 bytes long (with a 0x00 prefixed) even though it fits in 32 bytes.
Always padding to 33 bytes then applying it to
data()+1
is just a convenience, i do suggest toassert(s[0] == 0)
to make it clearer that this a valid thing to do.
rkrux commented at 2:16 pm on May 8, 2025:Interesting, thanks. I did read this code comment earlier but the above comment is helpful in connecting the dots. https://github.com/bitcoin/bitcoin/blob/66c968b4b4a98b1d476cb79b0dd2f6b09420c945/src/key.cpp#L201-L204
Always padding to 33 bytes then applying it to data()+1 is just a convenience
Re:
Always padding
- In the case the first bit is not set already, even then prepending with 00 makes it easier to grasp. Otherwise,s[0] == 0
in some cases, and< 0x80
in the other cases.
theStack commented at 2:37 pm on May 8, 2025:As another reference and explanation, see also the corresponding serialization implementation in the functional test framework: https://github.com/bitcoin/bitcoin/blob/66c968b4b4a98b1d476cb79b0dd2f6b09420c945/test/functional/test_framework/key.py#L184-L188laanwj commented at 1:16 pm on May 8, 2025: memberACK i think this would make it slightly more clear what is going on with regard to ranges and validity:
0diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp 1index 33a4555dd595c9e4886f194cb113b905a55bddb3..6f251b094c5b68373f30d1d578088e65d4595a09 100644 2--- a/src/test/script_tests.cpp 3+++ b/src/test/script_tests.cpp 4@@ -148,11 +148,12 @@ void static NegateSignatureS(std::vector<unsigned char>& vchSig) { 5 while (s.size() < 33) { 6 s.insert(s.begin(), 0x00); 7 } 8+ assert(s[0] == 0); 9 // Perform mod-n negation of s by (ab)using libsecp256k1 10 int ret = secp256k1_ec_seckey_negate(secp256k1_context_static, s.data() + 1); 11 assert(ret); 12 13- if (s.size() > 1 && s[0] == 0 && s[1] < 0x80) { 14+ if (s[1] < 0x80) { 15 s.erase(s.begin()); 16 }
(if
s[0]
doesn’t start out as0
,secp256k1_ec_seckey_negate(..., s.data() + 1)
isn’t correct)rkrux commented at 1:24 pm on May 8, 2025: contributorConcept ACK bfb7b8876035774eddb200d741aa6a86859069b3
Conceptually, I’m in agreement with the replacement of custom negation code by using the one from secp256k1. Though I was a little surprised to note that git grep of this function doesn’t lead to any actual usages of the function besides the tests &
secp256k1_ec_privkey_negate
function that too doesn’t seem to be used elsewhere.0git grep -n "secp256k1_ec_seckey_negate" 1 2src/secp256k1/include/secp256k1.h:699:SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_negate( 3src/secp256k1/include/secp256k1.h:704:/** Same as secp256k1_ec_seckey_negate, but DEPRECATED. Will be removed in 4src/secp256k1/include/secp256k1.h:710: SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_negate instead"); 5src/secp256k1/src/ctime_tests.c:142: ret = secp256k1_ec_seckey_negate(ctx, key); 6src/secp256k1/src/secp256k1.c:622:int secp256k1_ec_seckey_negate(const secp256k1_context* ctx, unsigned char *seckey) { 7src/secp256k1/src/secp256k1.c:638: return secp256k1_ec_seckey_negate(ctx, seckey); 8src/secp256k1/src/tests.c:6246: CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1); 9src/secp256k1/src/tests.c:6248: CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1); 10src/secp256k1/src/tests.c:6252: CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 1); 11src/secp256k1/src/tests.c:6259: CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 0); 12src/secp256k1/src/tests.c:6269: CHECK(secp256k1_ec_seckey_negate(CTX, seckey) == 0);
test: refactor: negate signature-s using libsecp256k1
Co-authored-by: laanwj <126646+laanwj@users.noreply.github.com>
theStack force-pushed on May 8, 2025theStack commented at 2:38 pm on May 8, 2025: contributorThanks for the reviews! Applied your patch @laanwj and added a comment about the secp256k1 negation function usage.
Conceptually, I’m in agreement with the replacement of custom negation code by using the one from secp256k1. Though I was a little surprised to note that git grep of this function doesn’t lead to any actual usages of the function besides the tests & secp256k1_ec_privkey_negate function that too doesn’t seem to be used elsewhere.
It would have been used for the BIP 151 (v2 transport protocol) implementation and there was a
CKey::Negate
wrapper for it once, but with the replacement BIP 324 that was not needed any more and hence it got removed again, see #14047 and #30218.secp256k1_ec_privkey_negate
is just the older now deprecated name for the same function, which gets removed with the upcoming secp256k1 release, see https://github.com/bitcoin-core/secp256k1/pull/1593.laanwj approvedlaanwj commented at 3:01 pm on May 8, 2025: memberCode review ACK 1ee698fde2e8652d8eb083749edb8029c003b8dbDrahtBot requested review from w0xlt on May 8, 2025DrahtBot requested review from rkrux on May 8, 2025w0xlt commented at 9:40 pm on May 8, 2025: contributorin src/test/script_tests.cpp:158 in 1ee698fde2
169+ // (note that this function is meant to be used for negating secret keys, 170+ // but it works for any non-zero scalar modulo the group order, i.e. also for s) 171+ int ret = secp256k1_ec_seckey_negate(secp256k1_context_static, s.data() + 1); 172+ assert(ret); 173+ 174+ if (s[1] < 0x80) {
rkrux commented at 10:51 am on May 9, 2025:Given thats[0] == 0
is asserted above and the signature is negated froms[1]
onwards, I was trying to reason why this check is still needed before droppings[0]
& thought of the reason that ifs[0]
is dropped whens[1] >= 0x80
thens
will be treated as a negative number, which is illegal as mentioned in #32436 (review).
laanwj commented at 11:12 am on May 9, 2025:Right.
It’d be possible to structure this slightly more intuitive, by first removing the first byte (after asserting it’s 0x00), then doing
secp256_blabla
on the entire 32-byte thing, then conditionally readding it after.But meh. It’s not like this is a easty-to-digest function in the first place 😓
in src/test/script_tests.cpp:156 in 1ee698fde2
167+ assert(s[0] == 0); 168+ // Perform mod-n negation of s by (ab)using libsecp256k1 169+ // (note that this function is meant to be used for negating secret keys, 170+ // but it works for any non-zero scalar modulo the group order, i.e. also for s) 171+ int ret = secp256k1_ec_seckey_negate(secp256k1_context_static, s.data() + 1); 172+ assert(ret);
rkrux commented at 11:04 am on May 9, 2025:I checked that
ret
here means whether the passedsignature/seckey
in the second argument is valid according tosecp256k1_ec_seckey_verify
, which weirdly enough doesn’t seem to be called bysecp256k1_ec_seckey_negate
- maybe the doc is outdated, but I get the general idea of validating the input & returning the response of this check.rkrux approvedrkrux commented at 11:06 am on May 9, 2025: contributortACK 1ee698fde2e8652d8eb083749edb8029c003b8dbachow101 commented at 8:13 pm on May 9, 2025: memberACK 1ee698fde2e8652d8eb083749edb8029c003b8dbachow101 merged this on May 9, 2025achow101 closed this on May 9, 2025
theStack deleted the branch on May 9, 2025sipa commented at 8:30 pm on May 9, 2025: memberPosthumous Concept ACK
github-metadata-mirror
This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-05-25 21:12 UTC
This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me