Skip to content

Commit eb4e3a3

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

21 files changed

+4070
-20
lines changed

.cirrus.yml

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ env:
2121
ECDH: no
2222
RECOVERY: no
2323
SCHNORRSIG: no
24+
MUSIG: no
2425
ELLSWIFT: no
2526
### test options
2627
SECP256K1_TEST_ITERS:
@@ -67,6 +68,7 @@ task:
6768
ECDH: yes
6869
RECOVERY: yes
6970
SCHNORRSIG: yes
71+
MUSIG: yes
7072
ELLSWIFT: yes
7173
matrix:
7274
# Currently only gcc-snapshot, the other compilers are tested on GHA with QEMU
@@ -83,6 +85,7 @@ task:
8385
ECDH: yes
8486
RECOVERY: yes
8587
SCHNORRSIG: yes
88+
MUSIG: yes
8689
ELLSWIFT: yes
8790
WRAPPER_CMD: 'valgrind --error-exitcode=42'
8891
SECP256K1_TEST_ITERS: 2

.github/workflows/ci.yml

+24-13
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ env:
3232
ECDH: 'no'
3333
RECOVERY: 'no'
3434
SCHNORRSIG: 'no'
35+
MUSIG: 'no'
3536
ELLSWIFT: 'no'
3637
### test options
3738
SECP256K1_TEST_ITERS:
@@ -71,18 +72,18 @@ jobs:
7172
matrix:
7273
configuration:
7374
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
74-
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
75+
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
7576
- env_vars: { WIDEMUL: 'int128' }
76-
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
77-
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
78-
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes' }
79-
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
77+
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
78+
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
79+
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes' }
80+
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
8081
- env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes' }
8182
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', CPPFLAGS: '-DVERIFY' }
8283
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
8384
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
8485
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
85-
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
86+
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
8687
- env_vars: { ECMULTGENPRECISION: 2, ECMULTWINDOW: 2 }
8788
- env_vars: { ECMULTGENPRECISION: 8, ECMULTWINDOW: 4 }
8889
cc:
@@ -140,6 +141,7 @@ jobs:
140141
ECDH: 'yes'
141142
RECOVERY: 'yes'
142143
SCHNORRSIG: 'yes'
144+
MUSIG: 'yes'
143145
ELLSWIFT: 'yes'
144146
CC: ${{ matrix.cc }}
145147

@@ -184,6 +186,7 @@ jobs:
184186
ECDH: 'yes'
185187
RECOVERY: 'yes'
186188
SCHNORRSIG: 'yes'
189+
MUSIG: 'yes'
187190
ELLSWIFT: 'yes'
188191
CTIMETESTS: 'no'
189192

@@ -235,6 +238,7 @@ jobs:
235238
ECDH: 'yes'
236239
RECOVERY: 'yes'
237240
SCHNORRSIG: 'yes'
241+
MUSIG: 'yes'
238242
ELLSWIFT: 'yes'
239243
CTIMETESTS: 'no'
240244

@@ -280,6 +284,7 @@ jobs:
280284
ECDH: 'yes'
281285
RECOVERY: 'yes'
282286
SCHNORRSIG: 'yes'
287+
MUSIG: 'yes'
283288
ELLSWIFT: 'yes'
284289
CTIMETESTS: 'no'
285290

@@ -335,6 +340,7 @@ jobs:
335340
ECDH: 'yes'
336341
RECOVERY: 'yes'
337342
SCHNORRSIG: 'yes'
343+
MUSIG: 'yes'
338344
ELLSWIFT: 'yes'
339345
CTIMETESTS: 'no'
340346

@@ -387,6 +393,7 @@ jobs:
387393
ECDH: 'yes'
388394
RECOVERY: 'yes'
389395
SCHNORRSIG: 'yes'
396+
MUSIG: 'yes'
390397
ELLSWIFT: 'yes'
391398
CTIMETESTS: 'no'
392399
SECP256K1_TEST_ITERS: 2
@@ -438,6 +445,7 @@ jobs:
438445
ECDH: 'yes'
439446
RECOVERY: 'yes'
440447
SCHNORRSIG: 'yes'
448+
MUSIG: 'yes'
441449
ELLSWIFT: 'yes'
442450
CTIMETESTS: 'no'
443451
CFLAGS: '-fsanitize=undefined,address -g'
@@ -495,6 +503,7 @@ jobs:
495503
ECDH: 'yes'
496504
RECOVERY: 'yes'
497505
SCHNORRSIG: 'yes'
506+
MUSIG: 'yes'
498507
ELLSWIFT: 'yes'
499508
CTIMETESTS: 'yes'
500509
CC: 'clang'
@@ -542,6 +551,7 @@ jobs:
542551
ECDH: 'yes'
543552
RECOVERY: 'yes'
544553
SCHNORRSIG: 'yes'
554+
MUSIG: 'yes'
545555
ELLSWIFT: 'yes'
546556
CTIMETESTS: 'no'
547557

@@ -599,15 +609,15 @@ jobs:
599609
fail-fast: false
600610
matrix:
601611
env_vars:
602-
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
612+
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
603613
- { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 }
604-
- { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
614+
- { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
605615
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
606-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
607-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
608-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
609-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
610-
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
616+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
617+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc' }
618+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
619+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
620+
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
611621
- BUILD: 'distcheck'
612622

613623
steps:
@@ -717,6 +727,7 @@ jobs:
717727
ECDH: 'yes'
718728
RECOVERY: 'yes'
719729
SCHNORRSIG: 'yes'
730+
MUSIG: 'yes'
720731
ELLSWIFT: 'yes'
721732

722733
steps:

Makefile.am

+15
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,17 @@ schnorr_example_LDFLAGS += -lbcrypt
182182
endif
183183
TESTS += schnorr_example
184184
endif
185+
if ENABLE_MODULE_MUSIG
186+
noinst_PROGRAMS += musig_example
187+
musig_example_SOURCES = examples/musig.c
188+
musig_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
189+
musig_example_LDADD = libsecp256k1.la
190+
musig_example_LDFLAGS = -static
191+
if BUILD_WINDOWS
192+
musig_example_LDFLAGS += -lbcrypt
193+
endif
194+
TESTS += musig_example
195+
endif
185196
endif
186197

187198
### Precomputed tables
@@ -268,6 +279,10 @@ if ENABLE_MODULE_SCHNORRSIG
268279
include src/modules/schnorrsig/Makefile.am.include
269280
endif
270281

282+
if ENABLE_MODULE_MUSIG
283+
include src/modules/musig/Makefile.am.include
284+
endif
285+
271286
if ENABLE_MODULE_ELLSWIFT
272287
include src/modules/ellswift/Makefile.am.include
273288
endif

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Features:
2020
* Optional module for public key recovery.
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).
23+
* Optional module for the MuSig2 multi-signature scheme according to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki).
2324

2425
Implementation details
2526
----------------------

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 ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
16-
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG ELLSWIFT \
16+
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG MUSIG ELLSWIFT \
1717
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
1818
EXAMPLES \
1919
HOST WRAPPER_CMD \
@@ -77,6 +77,7 @@ esac
7777
--enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \
7878
--enable-module-ellswift="$ELLSWIFT" \
7979
--enable-module-schnorrsig="$SCHNORRSIG" \
80+
--enable-module-musig="$MUSIG" \
8081
--enable-examples="$EXAMPLES" \
8182
--enable-ctime-tests="$CTIMETESTS" \
8283
--with-valgrind="$WITH_VALGRIND" \

configure.ac

+19-6
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])])
@@ -387,6 +391,10 @@ SECP_CFLAGS="$SECP_CFLAGS $WERROR_CFLAGS"
387391
### Handle module options
388392
###
389393

394+
# Besides testing whether modules are enabled, the following code also enables
395+
# module dependencies. The order of the tests matters: the dependency must be
396+
# tested first.
397+
390398
if test x"$enable_module_ecdh" = x"yes"; then
391399
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1"
392400
fi
@@ -395,21 +403,24 @@ if test x"$enable_module_recovery" = x"yes"; then
395403
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_RECOVERY=1"
396404
fi
397405

406+
if test x"$enable_module_musig" = x"yes"; then
407+
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_MUSIG=1"
408+
enable_module_schnorrsig=yes
409+
fi
410+
398411
if test x"$enable_module_schnorrsig" = x"yes"; then
399412
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SCHNORRSIG=1"
400413
enable_module_extrakeys=yes
401414
fi
402415

403-
if test x"$enable_module_ellswift" = x"yes"; then
404-
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1"
405-
fi
406-
407-
# Test if extrakeys is set after the schnorrsig module to allow the schnorrsig
408-
# module to set enable_module_extrakeys=yes
409416
if test x"$enable_module_extrakeys" = x"yes"; then
410417
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_EXTRAKEYS=1"
411418
fi
412419

420+
if test x"$enable_module_ellswift" = x"yes"; then
421+
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1"
422+
fi
423+
413424
if test x"$enable_external_default_callbacks" = x"yes"; then
414425
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1"
415426
fi
@@ -446,6 +457,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"])
446457
AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"])
447458
AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"])
448459
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"])
460+
AM_CONDITIONAL([ENABLE_MODULE_MUSIG], [test x"$enable_module_musig" = x"yes"])
449461
AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"])
450462
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
451463
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
@@ -468,6 +480,7 @@ echo " module ecdh = $enable_module_ecdh"
468480
echo " module recovery = $enable_module_recovery"
469481
echo " module extrakeys = $enable_module_extrakeys"
470482
echo " module schnorrsig = $enable_module_schnorrsig"
483+
echo " module musig = $enable_module_musig"
471484
echo " module ellswift = $enable_module_ellswift"
472485
echo
473486
echo " asm = $set_asm"

doc/musig.md

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 to be as misuse resistant as possible.
10+
However, the MuSig protocol has some additional failure modes (mainly due to interactivity) that do not appear in single-signing.
11+
While the results can be catastrophic (e.g. leaking of the secret key), it is unfortunately not possible for the musig implementation to rule out 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 (Taproot) tweak can be added to the resulting public key with `secp256k1_xonly_pubkey_tweak_add` and a plain tweak can be added with `secp256k1_ec_pubkey_tweak_add`.
26+
27+
# Signing
28+
29+
This is covered by `examples/musig.c`.
30+
Essentially, the protocol proceeds in the following steps:
31+
32+
1. Generate a keypair with `secp256k1_keypair_create` and obtain the public key with `secp256k1_keypair_pub`.
33+
2. Call `secp256k1_musig_pubkey_agg` with the pubkeys of all participants.
34+
3. Optionally add a (Taproot) tweak with `secp256k1_musig_pubkey_xonly_tweak_add` and a plain tweak with `secp256k1_musig_pubkey_ec_tweak_add`.
35+
4. Generate a pair of secret and public nonce with `secp256k1_musig_nonce_gen` and send the public nonce to the other signers.
36+
5. Someone (not necessarily the signer) aggregates the public nonce with `secp256k1_musig_nonce_agg` and sends it to the signers.
37+
6. Process the aggregate nonce with `secp256k1_musig_nonce_process`.
38+
7. Create a partial signature with `secp256k1_musig_partial_sign`.
39+
8. Verify the partial signatures (optional in some scenarios) with `secp256k1_musig_partial_sig_verify`.
40+
9. Someone (not necessarily the signer) obtains all partial signatures and aggregates them into the final Schnorr signature using `secp256k1_musig_partial_sig_agg`.
41+
42+
The aggregate signature can be verified with `secp256k1_schnorrsig_verify`.
43+
44+
Note that steps 1 to 5 can happen before the message to be signed is known to the signers.
45+
Therefore, the communication round to exchange nonces can be viewed as a pre-processing step that is run whenever convenient to the signers.
46+
This disables some of the defense-in-depth measures that may protect against API misuse in some cases.
47+
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).
48+
49+
# Verification
50+
51+
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)