-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an experimental batch module #1134
base: master
Are you sure you want to change the base?
Conversation
This commit adds the foundational configuration, build scripts, and an initial structure for experimental batch module.
This commit adds the _batch_create and _batch_destroy APIs. Relevant Links: 1. _batch_scratch_size allocation formula is taken from bench ecmult: https://github.com/bitcoin-core/secp256k1/blob/694ce8fb2d1fd8a3d641d7c33705691d41a2a860/src/bench_ecmult.c#L312. 2. aux_rand16 param in _batch_create enables synthetic randomness for randomizer generation: sipa/bips#204.
This commit refactors _ecmult_strauss_batch and adds _batch_verify API. The current _ecmult_strauss_batch only works on empty scratch space. To make _batch_verify work, we need _ecmult_strauss_batch to support a scratch space pre-filled with scalars and points. So, it was refactored to do exactly that. The _batch_verify API always uses the Strauss algorithm. It doesn't switch to Pippenger (unlike _ecmult_multi_var). _ecmult_pippenger_batch represents points as secp256k1_ge whereas _ecmult_strauss_batch represents points as secp256k1_gej. This makes supporting both Pippenger and Strauss difficult (at least with the current batch object design). Hence, _batch_verify only supports Strauss for simplicity.
This commit adds the batch APIs: 1. _batch_add_schnorrsig Adds a Schnorr signature to the batch. 2. _batch_add_xonlypub_tweak_check Adds a tweaked x-only pubkey check to the batch. 3. _batch_usable Checks if a batch can be used by _batch_add_* APIs. **Side Note:** Exposing batch_add_schnorrsig in the `secp256k1_schnorrsig.h` header file (with batch module header guards) will force the user to define ENABLE_MODULE_BATCH during their code compilation. Hence, it is in a standalone `secp256k1_schnorrsig_batch` header file. A similar argument could be made for batch_add_xonlypub_tweak_check.
This commit adds an example C program using the batch API. GNU Autotools will compile this example only if both batch and schnorrsig modules are enabled.
This commit adds the following tests: 1. Cirrus scripts 2. Batch API tests (ordered) 3. Tagged SHA256 test 4. BIP340 test vectors: https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv 5. Large random test for `strauss_batch` refactor
This commit adds the following tests: 1. Random bitflip test for randomizer generating function 2. Random bitflip in Schnorr Signature (_batch_add_schnorrsig test) 3. NULL arg tests (for both _batch_add APIs)
For example,
Alternative Design Options
|
Questions
|
This commit adds benchmarks for Schnorr signature batch verification,Tweaked pubkey check batch verification, and Tweaked pubkey check (single verification). For batch verify benchmark, the number of sigs (or checks) in the batch varies from 1 to SECP256K1_BENCH_ITERS with a 20% increment.
This commit generates two semi-log graphs that visualize the batch verification speed up over single verification (y-axis) wrt the number of signatures (or tweak checks) in the batch (x-axis). The input data points are taken from the batch verify benchmark. GNU plot was used to generate these graphs (plot.gp file). The instructions to reproduce these graphs (on your local machine) are given in doc/speedup-batch.md file. The value of `STRAUSS_MAX_TERMS_PER_BATCH` was calculated (approx) from the generated graphs. Relevant discussion: #2 (comment)
f48072a
to
eb49e93
Compare
In |
|
||
schnorrsig_sign , 50.4 , 50.5 , 50.7 | ||
schnorrsig_verify , 89.1 , 89.2 , 89.3 | ||
schnorrsig_batch_verify_1 , 104.0 , 104.0 , 104.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
batch_verify_1 shouldn't be slower than non-batch verify. Is it possible to revert to using non-batch validation logic for this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not possible with the current design.
The non-batch validation (secp256k1_schnorrsig_verify
) logic looks something like this:
- calc
rj
usingsecp256k1_ecmult
: Rj = sG - eP - convert
rj
(gej) tor
(ge) - check if the
r.x = sig[0:32]
andr.y = even
one schnorrsig occupies two points in the batch, and one tweak check occupies one point in the batch. If a batch contains two points, there is no guarantee that they are from a schnorrsig (R, P). It could be from two tweak checks. So, we can't use the r.y = even
check.
Hence, I tried implementing a slightly modified schnorrsig_verify
logic (not implement in this PR):
- calc
neg_rj
usingsecp256k1_ecmult
:neg_Rj = -s*G + batch.scalars[1]*batch.points[1]
- check if
neg_rj + batch.points[0] == inf
using_gej_add_var
batch.scalars[0] = 1
always. So, we don't need to useecmult
again
This gives somewhat better benchmarks than before:
Benchmark , Min(us) , Avg(us) , Max(us)
schnorrsig_sign , 49.1 , 50.1 , 53.4
schnorrsig_verify , 86.6 , 87.2 , 88.4
schnorrsig_batch_verify_1 , 94.7 , 95.0 , 95.2
But schnorrsig_batch_verify_1
is still slower than schnorrsig_verify
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation does not strictly follow the BIP340 batch verification spec. [...] the random numbers (or randomizers) aren’t generated by passing a seed (hash of all inputs) to CSPRNG
The security argument for batch verification should relatively easily translate to the approach in this PR.
Is transparent verification required?
I initially wasn't a fan of transparent verification because developers generally want to know how long a certain function call takes and don't want to be surprised by some batch_add
calls taking much longer than others. But I changed my mind on this.
The batch verification API right now is used as follows:
for (i = 0; i < N_SIGS; i++) {
if(!batch_usable(batch) || !batch_add_schnorrsig(batch, sig[i], msg[i], sizeof(msg[i]), &pk)) {
return 0;
}
}
if(!batch_verify(ctx, batch)) {
return 0;
}
Without TV, then one option is to have the user create a batch that is large enough. Of course that's not alway possible because the batch can get larger than the available memory. Moreover, this would require adding an API that, given a number of schnorrsigs and tweaks (and more in the future) returns the size of the required batch. That seems way more complicated than TV.
If there's no TV and the batch is not guaranteed to be large enough, then users need to essentially reimplement something like transparent verification:
for (i = 0; i < N_SIGS; i++) {
if(!schnorrsig_batch_has_space(batch)) {
if(!batch_verify(batch)) {
return 0;
}
}
if (!batch_add_schnorrsig(batch, sig[i], msg[i], sizeof(msg[i]), &pk)) {
return 0;
}
}
if(!batch_verify(ctx, batch)) {
return 0;
}
- This is more code compared to having TV built into
batch_add
. *_batch_has_space
is specific to whatever you're trying to add to the batch, i.e., we would also have to addtweak_has_space
, for example.- The
batch_usable
function in the current implementation (with TV) only allows for earlier aborts and is not essential (unless you want to determine if abatch_add
failed because its input is obviously malformed or because the previousbatch_add
triggered abatch_verify
that failed). - If users don't want to use TV (for whatever reason), then they don't have to - even in the current implementation. This requires counting the terms that have been added to the batch and verifying before it's full (perhaps we can make this simpler).
xonly_pubkey_tweak_add_check recommends ctx to be initialized for verification, but it can work even if ctx is initialized as none. [...] Since batch_add_tweak_xonlypub_check is based on tweak_add_check, should it also recommend that ctx be initialized for verification?
I don't think so. #1126 removes "initialized for verification" from the API docs of xonly_pubkey_tweak_add_check
. For consistency it would be better if you'd remove "(can be initialized for none)" from the API doc.
I haven't had a closer look yet but I agree with @jonasnick's comments about TV. |
@siv2r Are you still working on this topic? Do you plan to address @jonasnick 's comments? |
@fjahr, this PR needs review from other contributors regarding the (batch API) design decision made here. The #1134 (review) comment showed its support for the "Transparent Verification" feature, which was implemented. IIRC, two documentation changes are required (at the time of writing):
I avoided making these changes immediately to keep the commit history clean for reviewers. I would be happy to work on any required changes after it gets enough review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have opened a draft PR for using this in bitcoin core: bitcoin/bitcoin#29491
Here is a rebased branch of the code that I am using: https://github.com/fjahr/secp256k1/tree/pr1134-rebase-2024
I hope this can create some new interest and motivate people to review here as well.
|
||
/* `_strauss_batch_internal` should not fail due to insufficient memory. | ||
* `batch_create` will allocate memeory needed by `_strauss_batch_internal`. */ | ||
VERIFY_CHECK(strauss_ret != 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting an unused variable warning about strauss_ret
here when building this as part of bitcoin core, probably because the check is removed with optimizations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The macro VERIFY_CHECK(code)
is defined to be the empty string in production builds, that's why you need to suppress the warning using (void)strauss_ret;
.
secp256k1_batch *batch_vrfy; | ||
secp256k1_batch *batch_both; | ||
secp256k1_batch *batch_sttc; | ||
unsigned char aux_rand16[32]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting another warning here when compiling together with Bitcoin Core:
src/modules/batch/tests_impl.h:68:19: warning: mixing declarations and code is a C99 extension [-Wdeclaration-after-statement]
unsigned char aux_rand16[32];
^
Overview
This PR adds support for batch verifying Schnorr signatures and tweaked x-only public key checks. It is based on the work of @jonasnick in #760.
Batch Verification
This implementation does not strictly follow the BIP340 batch verification spec. The API design is loosely based on this suggestion: #760 (comment). Prior development discussion of this PR can be found in siv2r#2.
Speed Up
Fixes #1087