Skip to content

Commit f411841

Browse files
committed
Add module "musig" that implements MuSig2 multi-signatures (BIP 327)
1 parent 0be7966 commit f411841

23 files changed

+4361
-14
lines changed

.cirrus.yml

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ env:
2222
RECOVERY: no
2323
EXTRAKEYS: no
2424
SCHNORRSIG: no
25+
MUSIG: no
2526
ELLSWIFT: no
2627
### test options
2728
SECP256K1_TEST_ITERS:
@@ -69,6 +70,7 @@ task:
6970
RECOVERY: yes
7071
EXTRAKEYS: yes
7172
SCHNORRSIG: yes
73+
MUSIG: yes
7274
ELLSWIFT: yes
7375
matrix:
7476
# Currently only gcc-snapshot, the other compilers are tested on GHA with QEMU
@@ -86,6 +88,7 @@ task:
8688
RECOVERY: yes
8789
EXTRAKEYS: yes
8890
SCHNORRSIG: yes
91+
MUSIG: yes
8992
ELLSWIFT: yes
9093
WRAPPER_CMD: 'valgrind --error-exitcode=42'
9194
SECP256K1_TEST_ITERS: 2

.github/workflows/ci.yml

+24-13
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ env:
3333
RECOVERY: 'no'
3434
EXTRAKEYS: 'no'
3535
SCHNORRSIG: 'no'
36+
MUSIG: 'no'
3637
ELLSWIFT: 'no'
3738
### test options
3839
SECP256K1_TEST_ITERS:
@@ -72,18 +73,18 @@ jobs:
7273
matrix:
7374
configuration:
7475
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
75-
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
76+
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
7677
- env_vars: { WIDEMUL: 'int128' }
7778
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
78-
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
79-
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes' }
79+
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
80+
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' }
8081
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
81-
- env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes' }
82-
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', CPPFLAGS: '-DVERIFY' }
82+
- env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' }
83+
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', CPPFLAGS: '-DVERIFY' }
8384
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
8485
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
8586
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
86-
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
87+
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
8788
- env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 }
8889
- env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 }
8990
cc:
@@ -142,6 +143,7 @@ jobs:
142143
RECOVERY: 'yes'
143144
EXTRAKEYS: 'yes'
144145
SCHNORRSIG: 'yes'
146+
MUSIG: 'yes'
145147
ELLSWIFT: 'yes'
146148
CC: ${{ matrix.cc }}
147149

@@ -187,6 +189,7 @@ jobs:
187189
RECOVERY: 'yes'
188190
EXTRAKEYS: 'yes'
189191
SCHNORRSIG: 'yes'
192+
MUSIG: 'yes'
190193
ELLSWIFT: 'yes'
191194
CTIMETESTS: 'no'
192195

@@ -239,6 +242,7 @@ jobs:
239242
RECOVERY: 'yes'
240243
EXTRAKEYS: 'yes'
241244
SCHNORRSIG: 'yes'
245+
MUSIG: 'yes'
242246
ELLSWIFT: 'yes'
243247
CTIMETESTS: 'no'
244248

@@ -285,6 +289,7 @@ jobs:
285289
RECOVERY: 'yes'
286290
EXTRAKEYS: 'yes'
287291
SCHNORRSIG: 'yes'
292+
MUSIG: 'yes'
288293
ELLSWIFT: 'yes'
289294
CTIMETESTS: 'no'
290295

@@ -341,6 +346,7 @@ jobs:
341346
RECOVERY: 'yes'
342347
EXTRAKEYS: 'yes'
343348
SCHNORRSIG: 'yes'
349+
MUSIG: 'yes'
344350
ELLSWIFT: 'yes'
345351
CTIMETESTS: 'no'
346352

@@ -394,6 +400,7 @@ jobs:
394400
RECOVERY: 'yes'
395401
EXTRAKEYS: 'yes'
396402
SCHNORRSIG: 'yes'
403+
MUSIG: 'yes'
397404
ELLSWIFT: 'yes'
398405
CTIMETESTS: 'no'
399406
SECP256K1_TEST_ITERS: 2
@@ -446,6 +453,7 @@ jobs:
446453
RECOVERY: 'yes'
447454
EXTRAKEYS: 'yes'
448455
SCHNORRSIG: 'yes'
456+
MUSIG: 'yes'
449457
ELLSWIFT: 'yes'
450458
CTIMETESTS: 'no'
451459
CFLAGS: '-fsanitize=undefined,address -g'
@@ -511,6 +519,7 @@ jobs:
511519
RECOVERY: 'yes'
512520
EXTRAKEYS: 'yes'
513521
SCHNORRSIG: 'yes'
522+
MUSIG: 'yes'
514523
ELLSWIFT: 'yes'
515524
CC: 'clang'
516525
SECP256K1_TEST_ITERS: 32
@@ -558,6 +567,7 @@ jobs:
558567
RECOVERY: 'yes'
559568
EXTRAKEYS: 'yes'
560569
SCHNORRSIG: 'yes'
570+
MUSIG: 'yes'
561571
ELLSWIFT: 'yes'
562572
CTIMETESTS: 'no'
563573

@@ -615,15 +625,15 @@ jobs:
615625
fail-fast: false
616626
matrix:
617627
env_vars:
618-
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
628+
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
619629
- { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 }
620-
- { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
630+
- { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
621631
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
622-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
623-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
624-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
625-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
626-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
632+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
633+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
634+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
635+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
636+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
627637
- BUILD: 'distcheck'
628638

629639
steps:
@@ -790,6 +800,7 @@ jobs:
790800
RECOVERY: 'yes'
791801
EXTRAKEYS: 'yes'
792802
SCHNORRSIG: 'yes'
803+
MUSIG: 'yes'
793804
ELLSWIFT: 'yes'
794805

795806
steps:

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ecdh_example
1111
ecdsa_example
1212
schnorr_example
1313
ellswift_example
14+
musig_example
1415
*.exe
1516
*.so
1617
*.a

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
#### Added
11+
- New module `musig` implements the MuSig2 multisignature scheme according to the [BIP 327 specification](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki). See:
12+
- Header file `include/secp256k1_musig.h` which defines the new API.
13+
- Document `doc/musig.md` for further notes on API usage.
14+
- Usage example `examples/musig.c`.
15+
1016
## [0.5.1] - 2024-08-01
1117

1218
#### Added

Makefile.am

+15
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ ellswift_example_LDFLAGS += -lbcrypt
195195
endif
196196
TESTS += ellswift_example
197197
endif
198+
if ENABLE_MODULE_MUSIG
199+
noinst_PROGRAMS += musig_example
200+
musig_example_SOURCES = examples/musig.c
201+
musig_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
202+
musig_example_LDADD = libsecp256k1.la
203+
musig_example_LDFLAGS = -static
204+
if BUILD_WINDOWS
205+
musig_example_LDFLAGS += -lbcrypt
206+
endif
207+
TESTS += musig_example
208+
endif
198209
endif
199210

200211
### Precomputed tables
@@ -281,6 +292,10 @@ if ENABLE_MODULE_SCHNORRSIG
281292
include src/modules/schnorrsig/Makefile.am.include
282293
endif
283294

295+
if ENABLE_MODULE_MUSIG
296+
include src/modules/musig/Makefile.am.include
297+
endif
298+
284299
if ENABLE_MODULE_ELLSWIFT
285300
include src/modules/ellswift/Makefile.am.include
286301
endif

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Features:
2121
* Optional module for ECDH key exchange.
2222
* Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
2323
* Optional module for ElligatorSwift key exchange according to [BIP-324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki).
24+
* Optional module for MuSig2 Schnorr multi-signatures according to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki).
2425

2526
Implementation details
2627
----------------------

ci/ci.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ print_environment() {
1313
# does not rely on bash.
1414
for var in WERROR_CFLAGS MAKEFLAGS BUILD \
1515
ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
16-
EXPERIMENTAL ECDH RECOVERY EXTRAKEYS SCHNORRSIG ELLSWIFT \
16+
EXPERIMENTAL ECDH RECOVERY EXTRAKEYS MUSIG SCHNORRSIG ELLSWIFT \
1717
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
1818
EXAMPLES \
1919
HOST WRAPPER_CMD \
@@ -79,6 +79,7 @@ esac
7979
--enable-module-ellswift="$ELLSWIFT" \
8080
--enable-module-extrakeys="$EXTRAKEYS" \
8181
--enable-module-schnorrsig="$SCHNORRSIG" \
82+
--enable-module-musig="$MUSIG" \
8283
--enable-examples="$EXAMPLES" \
8384
--enable-ctime-tests="$CTIMETESTS" \
8485
--with-valgrind="$WITH_VALGRIND" \

configure.ac

+14
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ AC_ARG_ENABLE(module_schnorrsig,
184184
AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module [default=yes]]), [],
185185
[SECP_SET_DEFAULT([enable_module_schnorrsig], [yes], [yes])])
186186

187+
AC_ARG_ENABLE(module_musig,
188+
AS_HELP_STRING([--enable-module-musig],[enable MuSig2 module [default=yes]]), [],
189+
[SECP_SET_DEFAULT([enable_module_musig], [yes], [yes])])
190+
187191
AC_ARG_ENABLE(module_ellswift,
188192
AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [],
189193
[SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])])
@@ -398,6 +402,14 @@ if test x"$enable_module_ellswift" = x"yes"; then
398402
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1"
399403
fi
400404

405+
if test x"$enable_module_musig" = x"yes"; then
406+
if test x"$enable_module_schnorrsig" = x"no"; then
407+
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
408+
fi
409+
enable_module_schnorrsig=yes
410+
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_MUSIG=1"
411+
fi
412+
401413
if test x"$enable_module_schnorrsig" = x"yes"; then
402414
if test x"$enable_module_extrakeys" = x"no"; then
403415
AC_MSG_ERROR([Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the schnorrsig module.])
@@ -449,6 +461,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"])
449461
AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"])
450462
AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"])
451463
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"])
464+
AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = x"yes"])
452465
AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"])
453466
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
454467
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
@@ -471,6 +484,7 @@ echo " module ecdh = $enable_module_ecdh"
471484
echo " module recovery = $enable_module_recovery"
472485
echo " module extrakeys = $enable_module_extrakeys"
473486
echo " module schnorrsig = $enable_module_schnorrsig"
487+
echo " module musig = $enable_module_musig"
474488
echo " module ellswift = $enable_module_ellswift"
475489
echo
476490
echo " asm = $set_asm"

doc/musig.md

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Notes on the musig module API
2+
===========================
3+
4+
The following sections contain additional notes on the API of the musig module (`include/secp256k1_musig.h`).
5+
A usage example can be found in `examples/musig.c`.
6+
7+
## API misuse
8+
9+
The musig API is designed with a focus on misuse resistance.
10+
However, due to the interactive nature of the MuSig protocol, there are additional failure modes that are not present in regular (single-party) Schnorr signature creation.
11+
While the results can be catastrophic (e.g. leaking of the secret key), it is unfortunately not possible for the musig implementation to prevent all such failure modes.
12+
13+
Therefore, users of the musig module must take great care to make sure of the following:
14+
15+
1. A unique nonce per signing session is generated in `secp256k1_musig_nonce_gen`.
16+
See the corresponding comment in `include/secp256k1_musig.h` for how to ensure that.
17+
2. The `secp256k1_musig_secnonce` structure is never copied or serialized.
18+
See also the comment on `secp256k1_musig_secnonce` in `include/secp256k1_musig.h`.
19+
3. Opaque data structures are never written to or read from directly.
20+
Instead, only the provided accessor functions are used.
21+
22+
## Key Aggregation and (Taproot) Tweaking
23+
24+
Given a set of public keys, the aggregate public key is computed with `secp256k1_musig_pubkey_agg`.
25+
A plain tweak can be added to the resulting public key with `secp256k1_ec_pubkey_tweak_add` by setting the `tweak32` argument to the hash defined in BIP 32. Similarly, a Taproot tweak can be added with `secp256k1_xonly_pubkey_tweak_add` by setting the `tweak32` argument to the TapTweak hash defined in BIP 341.
26+
Both types of tweaking can be combined and invoked multiple times if the specific application requires it.
27+
28+
## Signing
29+
30+
This is covered by `examples/musig.c`.
31+
Essentially, the protocol proceeds in the following steps:
32+
33+
1. Generate a keypair with `secp256k1_keypair_create` and obtain the public key with `secp256k1_keypair_pub`.
34+
2. Call `secp256k1_musig_pubkey_agg` with the pubkeys of all participants.
35+
3. Optionally add a (Taproot) tweak with `secp256k1_musig_pubkey_xonly_tweak_add` and a plain tweak with `secp256k1_musig_pubkey_ec_tweak_add`.
36+
4. Generate a pair of secret and public nonce with `secp256k1_musig_nonce_gen` and send the public nonce to the other signers.
37+
5. Someone (not necessarily the signer) aggregates the public nonces with `secp256k1_musig_nonce_agg` and sends it to the signers.
38+
6. Process the aggregate nonce with `secp256k1_musig_nonce_process`.
39+
7. Create a partial signature with `secp256k1_musig_partial_sign`.
40+
8. Verify the partial signatures (optional in some scenarios) with `secp256k1_musig_partial_sig_verify`.
41+
9. Someone (not necessarily the signer) obtains all partial signatures and aggregates them into the final Schnorr signature using `secp256k1_musig_partial_sig_agg`.
42+
43+
The aggregate signature can be verified with `secp256k1_schnorrsig_verify`.
44+
45+
Steps 1 through 5 above can occur before or after the signers are aware of the message to be signed.
46+
Whenever possible, it is recommended to generate the nonces only after the message is known.
47+
This provides enhanced defense-in-depth measures, protecting against potential API misuse in certain scenarios.
48+
However, it does require two rounds of communication during the signing process.
49+
The alternative, generating the nonces in a pre-processing step before the message is known, eliminates these additional protective measures but allows for non-interactive signing.
50+
Similarly, the API supports an alternative protocol flow where generating the aggregate key (steps 1 to 3) is allowed to happen after exchanging nonces (steps 4 to 5).
51+
52+
## Verification
53+
54+
A participant who wants to verify the partial signatures, but does not sign itself may do so using the above instructions except that the verifier skips steps 1, 4 and 7.

0 commit comments

Comments
 (0)