Compare commits

...

11 Commits

Author SHA1 Message Date
Jesse Posner
536b0458ad
frost trusted dealer: use set_int instead of clear 2024-05-15 12:53:45 -07:00
Jesse Posner
d3ef472559
frost trusted dealer: improve doc based upon PR feedback 2024-05-15 12:52:48 -07:00
Jesse Posner
e94367c83e
frost trusted dealer: Add documentation file
This commit adds a documentation file with instructions for how to use
the module.
2024-05-14 15:29:07 -07:00
Jesse Posner
1b9567289b
frost trusted dealer: add tests
Add api tests, nonce tests, tweak tests, sha256 tag tests, and constant
time tests.
2024-05-14 15:29:03 -07:00
Jesse Posner
fb34b29d7f
frost trusted dealer: add example file
This commit adds an example file to demonstrate how to use the module.
2024-05-14 15:28:12 -07:00
Jesse Posner
9170dd337c
frost trusted dealer: signature generation and aggregation
This commit adds signature generation and aggregation, as well as
partial signature serialization and parsing.
2024-05-14 15:28:12 -07:00
Jesse Posner
ea059393f0
frost trusted dealer: nonce aggregation and adaptor signatures
This commit adds nonce aggregation, as well as adaptor signatures.
2024-05-14 15:28:12 -07:00
Jesse Posner
5368c81a3c
frost trusted dealer: key tweaking
This commits add BIP-341 ("Taproot") and BIP-32 ("ordinary") public key
tweaking.
2024-05-14 15:28:00 -07:00
Jesse Posner
9b852191de
frost trusted dealer: nonce generation
This commits adds nonce generation, as well as serialization and
parsing.
2024-05-14 15:26:35 -07:00
Jesse Posner
8969cee21c
frost trusted dealer: share generation
This commit adds trusted share generation, as well as share
serialization and parsing.
2024-05-14 14:32:10 -07:00
Jesse Posner
6f47d2eb22
frost trusted dealer: initialize project
This commit adds the foundational configuration and building scripts
and an initial structure for the project.
2024-05-14 14:32:05 -07:00
24 changed files with 2973 additions and 35 deletions

View File

@ -41,6 +41,7 @@ env:
ECDSAADAPTOR: 'no' ECDSAADAPTOR: 'no'
BPPP: 'no' BPPP: 'no'
SCHNORRSIG_HALFAGG: 'no' SCHNORRSIG_HALFAGG: 'no'
FROST: 'no'
### test options ### test options
SECP256K1_TEST_ITERS: SECP256K1_TEST_ITERS:
BENCH: 'yes' BENCH: 'yes'
@ -79,14 +80,14 @@ jobs:
matrix: matrix:
configuration: configuration:
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' } - env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'} - env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
- env_vars: { WIDEMUL: 'int128' } - env_vars: { WIDEMUL: 'int128' }
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'} - env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' } - env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
- env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'} - env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CPPFLAGS: '-DVERIFY' } - env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CPPFLAGS: '-DVERIFY' }
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' } - env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' } - env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' } - env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
@ -158,6 +159,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CC: ${{ matrix.cc }} CC: ${{ matrix.cc }}
steps: steps:
@ -211,6 +213,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
steps: steps:
@ -271,6 +274,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
steps: steps:
@ -325,6 +329,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
strategy: strategy:
@ -389,6 +394,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
steps: steps:
@ -450,6 +456,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
SECP256K1_TEST_ITERS: 2 SECP256K1_TEST_ITERS: 2
@ -510,6 +517,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
CFLAGS: '-fsanitize=undefined,address -g' CFLAGS: '-fsanitize=undefined,address -g'
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1' UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
@ -576,6 +584,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'yes' CTIMETESTS: 'yes'
CC: 'clang' CC: 'clang'
SECP256K1_TEST_ITERS: 32 SECP256K1_TEST_ITERS: 32
@ -632,6 +641,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
CTIMETESTS: 'no' CTIMETESTS: 'no'
strategy: strategy:
@ -688,15 +698,15 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
env_vars: env_vars:
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' } - { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
- { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 } - { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 }
- { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' } - { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc' } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CC: 'gcc' }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' } - { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
- BUILD: 'distcheck' - BUILD: 'distcheck'
steps: steps:
@ -816,6 +826,7 @@ jobs:
ECDSAADAPTOR: 'yes' ECDSAADAPTOR: 'yes'
BPPP: 'yes' BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes' SCHNORRSIG_HALFAGG: 'yes'
FROST: 'yes'
steps: steps:
- name: Checkout - name: Checkout

1
.gitignore vendored
View File

@ -66,6 +66,7 @@ libsecp256k1.pc
contrib/gh-pr-create.sh contrib/gh-pr-create.sh
musig_example musig_example
frost_example
### CMake ### CMake
/CMakeUserPresets.json /CMakeUserPresets.json

View File

@ -195,6 +195,17 @@ musig_example_LDFLAGS += -lbcrypt
endif endif
TESTS += musig_example TESTS += musig_example
endif endif
if ENABLE_MODULE_FROST
noinst_PROGRAMS += frost_example
frost_example_SOURCES = examples/frost.c
frost_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
frost_example_LDADD = libsecp256k1.la
frost_example_LDFLAGS = -static
if BUILD_WINDOWS
frost_example_LDFLAGS += -lbcrypt
endif
TESTS += frost_example
endif
endif endif
### Precomputed tables ### Precomputed tables
@ -320,3 +331,7 @@ endif
if ENABLE_MODULE_ECDSA_ADAPTOR if ENABLE_MODULE_ECDSA_ADAPTOR
include src/modules/ecdsa_adaptor/Makefile.am.include include src/modules/ecdsa_adaptor/Makefile.am.include
endif endif
if ENABLE_MODULE_FROST
include src/modules/frost/Makefile.am.include
endif

View File

@ -12,6 +12,7 @@ Added features:
* Experimental module for Confidential Assets (Pedersen commitments, range proofs, and [surjection proofs](src/modules/surjection/surjection.md)). * Experimental module for Confidential Assets (Pedersen commitments, range proofs, and [surjection proofs](src/modules/surjection/surjection.md)).
* Experimental module for Bulletproofs++ range proofs. * Experimental module for Bulletproofs++ range proofs.
* Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md). * Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md).
* Experimental module for FROST.
Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable. Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable.

View File

@ -14,7 +14,7 @@ print_environment() {
for var in WERROR_CFLAGS MAKEFLAGS BUILD \ for var in WERROR_CFLAGS MAKEFLAGS BUILD \
ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \ ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG SCHNORRSIG_HALFAGG ELLSWIFT \ EXPERIMENTAL ECDH RECOVERY SCHNORRSIG SCHNORRSIG_HALFAGG ELLSWIFT \
ECDSA_S2C GENERATOR RANGEPROOF WHITELIST MUSIG ECDSAADAPTOR BPPP \ ECDSA_S2C GENERATOR RANGEPROOF WHITELIST MUSIG ECDSAADAPTOR BPPP FROST \
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\ SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
EXAMPLES \ EXAMPLES \
HOST WRAPPER_CMD \ HOST WRAPPER_CMD \
@ -83,6 +83,7 @@ esac
--enable-module-schnorrsig="$SCHNORRSIG" --enable-module-musig="$MUSIG" --enable-module-ecdsa-adaptor="$ECDSAADAPTOR" \ --enable-module-schnorrsig="$SCHNORRSIG" --enable-module-musig="$MUSIG" --enable-module-ecdsa-adaptor="$ECDSAADAPTOR" \
--enable-module-schnorrsig="$SCHNORRSIG" \ --enable-module-schnorrsig="$SCHNORRSIG" \
--enable-module-schnorrsig-halfagg="$SCHNORRSIG_HALFAGG" \ --enable-module-schnorrsig-halfagg="$SCHNORRSIG_HALFAGG" \
--enable-module-frost="$FROST" \
--enable-examples="$EXAMPLES" \ --enable-examples="$EXAMPLES" \
--enable-ctime-tests="$CTIMETESTS" \ --enable-ctime-tests="$CTIMETESTS" \
--with-valgrind="$WITH_VALGRIND" \ --with-valgrind="$WITH_VALGRIND" \

View File

@ -236,6 +236,11 @@ AC_ARG_ENABLE(module_ecdsa-adaptor,
[], [],
[SECP_SET_DEFAULT([enable_module_ecdsa_adaptor], [no], [yes])]) [SECP_SET_DEFAULT([enable_module_ecdsa_adaptor], [no], [yes])])
AC_ARG_ENABLE(module_frost,
AS_HELP_STRING([--enable-module-frost],[enable FROST module [default=no]]),
[],
[SECP_SET_DEFAULT([enable_module_frost], [no], [yes])])
AC_ARG_ENABLE(external_default_callbacks, AC_ARG_ENABLE(external_default_callbacks,
AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
[SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
@ -470,6 +475,14 @@ if test x"$enable_module_ecdsa_adaptor" = x"yes"; then
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDSA_ADAPTOR=1" SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDSA_ADAPTOR=1"
fi fi
if test x"$enable_module_frost" = x"yes"; then
if test x"$enable_module_schnorrsig" = x"no"; then
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
fi
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_FROST=1"
enable_module_schnorrsig=yes
fi
if test x"$enable_module_musig" = x"yes"; then if test x"$enable_module_musig" = x"yes"; then
if test x"$enable_module_schnorrsig" = x"no"; then if test x"$enable_module_schnorrsig" = x"no"; then
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.]) AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
@ -579,6 +592,9 @@ else
if test x"$enable_module_generator" = x"yes"; then if test x"$enable_module_generator" = x"yes"; then
AC_MSG_ERROR([NUMS generator module is experimental. Use --enable-experimental to allow.]) AC_MSG_ERROR([NUMS generator module is experimental. Use --enable-experimental to allow.])
fi fi
if test x"$enable_module_frost" = x"yes"; then
AC_MSG_ERROR([FROST module is experimental. Use --enable-experimental to allow.])
fi
if test x"$set_asm" = x"arm32"; then if test x"$set_asm" = x"arm32"; then
AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.]) AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.])
fi fi
@ -611,6 +627,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDSA_S2C], [test x"$enable_module_ecdsa_s2c" = x"
AM_CONDITIONAL([ENABLE_MODULE_ECDSA_ADAPTOR], [test x"$enable_module_ecdsa_adaptor" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ECDSA_ADAPTOR], [test x"$enable_module_ecdsa_adaptor" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_BPPP], [test x"$enable_module_bppp" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_BPPP], [test x"$enable_module_bppp" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG_HALFAGG], [test x"$enable_module_schnorrsig_halfagg" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG_HALFAGG], [test x"$enable_module_schnorrsig_halfagg" = x"yes"])
AM_CONDITIONAL([ENABLE_MODULE_FROST], [test x"$enable_module_frost" = x"yes"])
AM_CONDITIONAL([USE_REDUCED_SURJECTION_PROOF_SIZE], [test x"$use_reduced_surjection_proof_size" = x"yes"]) AM_CONDITIONAL([USE_REDUCED_SURJECTION_PROOF_SIZE], [test x"$use_reduced_surjection_proof_size" = x"yes"])
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
@ -649,6 +666,7 @@ echo " module whitelist = $enable_module_whitelist"
echo " module musig = $enable_module_musig" echo " module musig = $enable_module_musig"
echo " module ecdsa-s2c = $enable_module_ecdsa_s2c" echo " module ecdsa-s2c = $enable_module_ecdsa_s2c"
echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor" echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor"
echo " module frost = $enable_module_frost"
echo " module bppp = $enable_module_bppp" echo " module bppp = $enable_module_bppp"
echo " module schnorrsig-halfagg = $enable_module_schnorrsig_halfagg" echo " module schnorrsig-halfagg = $enable_module_schnorrsig_halfagg"
echo echo

224
examples/frost.c Normal file
View File

@ -0,0 +1,224 @@
/***********************************************************************
* Copyright (c) 2021-2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
/**
* This file demonstrates how to use the FROST module to create a threshold
* signature. Additionally, see the documentation in include/secp256k1_frost.h.
*/
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <secp256k1.h>
#include <secp256k1_schnorrsig.h>
#include <secp256k1_frost.h>
#include "examples_util.h"
/* Number of public keys involved in creating the aggregate signature */
#define N_SIGNERS 5
/* Threshold required in creating the aggregate signature */
#define THRESHOLD 3
struct signer_secrets {
secp256k1_frost_share share;
secp256k1_frost_secnonce secnonce;
};
struct signer {
secp256k1_pubkey pubshare;
secp256k1_frost_pubnonce pubnonce;
secp256k1_frost_session session;
secp256k1_frost_partial_sig partial_sig;
};
/* Create shares and coefficient commitments */
int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signers, secp256k1_xonly_pubkey *pk) {
int i;
secp256k1_frost_share shares[N_SIGNERS];
secp256k1_pubkey pubshares[N_SIGNERS];
unsigned char seed[32];
if (!fill_random(seed, sizeof(seed))) {
return 0;
}
if (!secp256k1_frost_shares_trusted_gen(ctx, shares, pubshares, pk, seed, THRESHOLD, N_SIGNERS)) {
return 0;
}
for (i = 0; i < N_SIGNERS; i++) {
signer_secrets[i].share = shares[i];
signers[i].pubshare = pubshares[i];
}
return 1;
}
/* Tweak the pubkey corresponding to the provided tweak cache, update the cache
* and return the tweaked aggregate pk. */
int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_frost_tweak_cache *cache) {
secp256k1_pubkey output_pk;
unsigned char ordinary_tweak[32] = "this could be a BIP32 tweak....";
unsigned char xonly_tweak[32] = "this could be a taproot tweak..";
if (!secp256k1_frost_pubkey_tweak(ctx, cache, pk)) {
return 0;
}
/* Ordinary tweaking which, for example, allows deriving multiple child
* public keys from a single aggregate key using BIP32 */
if (!secp256k1_frost_pubkey_ec_tweak_add(ctx, NULL, cache, ordinary_tweak)) {
return 0;
}
/* If one is not interested in signing, the same output_pk can be obtained
* by calling `secp256k1_frost_pubkey_get` right after key aggregation to
* get the full pubkey and then call `secp256k1_ec_pubkey_tweak_add`. */
/* Xonly tweaking which, for example, allows creating taproot commitments */
if (!secp256k1_frost_pubkey_xonly_tweak_add(ctx, &output_pk, cache, xonly_tweak)) {
return 0;
}
/* Note that if we wouldn't care about signing, we can arrive at the same
* output_pk by providing the untweaked public key to
* `secp256k1_xonly_pubkey_tweak_add` (after converting it to an xonly pubkey
* if necessary with `secp256k1_xonly_pubkey_from_pubkey`). */
/* Now we convert the output_pk to an xonly pubkey to allow to later verify
* the Schnorr signature against it. For this purpose we can ignore the
* `pk_parity` output argument; we would need it if we would have to open
* the taproot commitment. */
if (!secp256k1_xonly_pubkey_from_pubkey(ctx, pk, NULL, &output_pk)) {
return 0;
}
return 1;
}
/* Sign a message hash with the given threshold and aggregate shares and store
* the result in sig */
int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char* msg32, secp256k1_xonly_pubkey *pk, unsigned char *sig64, const secp256k1_frost_tweak_cache *cache) {
int i;
size_t signer_id = 0;
int signers[THRESHOLD];
int is_signer[N_SIGNERS];
const secp256k1_frost_pubnonce *pubnonces[THRESHOLD];
size_t ids[THRESHOLD];
const secp256k1_frost_partial_sig *partial_sigs[THRESHOLD];
for (i = 0; i < N_SIGNERS; i++) {
unsigned char session_id[32];
/* Create random session ID. It is absolutely necessary that the session ID
* is unique for every call of secp256k1_frost_nonce_gen. Otherwise
* it's trivial for an attacker to extract the secret key! */
if (!fill_random(session_id, sizeof(session_id))) {
return 0;
}
/* Initialize session and create secret nonce for signing and public
* nonce to send to the other signers. */
if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_id, &signer_secrets[i].share, msg32, pk, NULL)) {
return 0;
}
is_signer[i] = 0; /* Initialize is_signer */
}
/* Select a random subset of signers */
for (i = 0; i < THRESHOLD; i++) {
size_t subset_seed;
while (1) {
if (!fill_random((unsigned char*)&subset_seed, sizeof(subset_seed))) {
return 0;
}
signer_id = subset_seed % N_SIGNERS;
/* Check if signer has already been assigned */
if (!is_signer[signer_id]) {
is_signer[signer_id] = 1;
signers[i] = signer_id;
break;
}
}
/* Mark signer as assigned */
pubnonces[i] = &signer[signer_id].pubnonce;
/* pubkeys[i] = &signer[signer_id].pubkey; */
ids[i] = signer_id + 1;
}
/* Signing communication round 1: Exchange nonces */
for (i = 0; i < THRESHOLD; i++) {
signer_id = signers[i];
if (!secp256k1_frost_nonce_process(ctx, &signer[signer_id].session, pubnonces, THRESHOLD, msg32, pk, signer_id + 1, ids, cache, NULL)) {
return 0;
}
/* partial_sign will clear the secnonce by setting it to 0. That's because
* you must _never_ reuse the secnonce (or use the same session_id to
* create a secnonce). If you do, you effectively reuse the nonce and
* leak the secret key. */
if (!secp256k1_frost_partial_sign(ctx, &signer[signer_id].partial_sig, &signer_secrets[signer_id].secnonce, &signer_secrets[signer_id].share, &signer[signer_id].session, cache)) {
return 0;
}
partial_sigs[i] = &signer[signer_id].partial_sig;
}
/* Communication round 2: A production system would exchange
* partial signatures here before moving on. */
for (i = 0; i < THRESHOLD; i++) {
signer_id = signers[i];
/* To check whether signing was successful, it suffices to either verify
* the aggregate signature with the aggregate public key using
* secp256k1_schnorrsig_verify, or verify all partial signatures of all
* signers individually. Verifying the aggregate signature is cheaper but
* verifying the individual partial signatures has the advantage that it
* can be used to determine which of the partial signatures are invalid
* (if any), i.e., which of the partial signatures cause the aggregate
* signature to be invalid and thus the protocol run to fail. It's also
* fine to first verify the aggregate sig, and only verify the individual
* sigs if it does not work.
*/
if (!secp256k1_frost_partial_sig_verify(ctx, &signer[signer_id].partial_sig, &signer[signer_id].pubnonce, &signer[signer_id].pubshare, &signer[signer_id].session, cache)) {
return 0;
}
}
return secp256k1_frost_partial_sig_agg(ctx, sig64, &signer[signer_id].session, partial_sigs, THRESHOLD);
}
int main(void) {
secp256k1_context* ctx;
struct signer_secrets signer_secrets[N_SIGNERS];
struct signer signers[N_SIGNERS];
secp256k1_xonly_pubkey pk;
secp256k1_frost_tweak_cache cache;
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!";
unsigned char sig[64];
/* Create a context for signing and verification */
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
printf("Creating shares.........");
if (!create_shares(ctx, signer_secrets, signers, &pk)) {
printf("FAILED\n");
return 1;
}
printf("ok\n");
printf("Tweaking................");
/* Optionally tweak the key */
if (!tweak(ctx, &pk, &cache)) {
printf("FAILED\n");
return 1;
}
printf("ok\n");
printf("Signing message.........");
if (!sign(ctx, signer_secrets, signers, msg, &pk, sig, &cache)) {
printf("FAILED\n");
return 1;
}
printf("ok\n");
printf("Verifying signature.....");
if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &pk)) {
printf("FAILED\n");
return 1;
}
printf("ok\n");
secp256k1_context_destroy(ctx);
return 0;
}

606
include/secp256k1_frost.h Normal file
View File

@ -0,0 +1,606 @@
#ifndef SECP256K1_FROST_H
#define SECP256K1_FROST_H
#include "secp256k1_extrakeys.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/** This code is currently a work in progress. It's not secure nor stable. IT
* IS EXTREMELY DANGEROUS AND RECKLESS TO USE THIS MODULE IN PRODUCTION!
* This module implements a variant of Flexible Round-Optimized Schnorr
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
* (https://crysp.uwaterloo.ca/software/frost/). Signatures are compatible with
* BIP-340 ("Schnorr"). There's an example C source file in the module's
* directory (examples/frost.c) that demonstrates how it can be used.
*
* The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public
* key tweaking, and adaptor signatures.
*
* Following the convention used in the MuSig module, the API uses the singular
* term "nonce" to refer to the two "nonces" used by the FROST scheme.
*/
/** Opaque data structures
*
* The exact representation of data inside is implementation defined and not
* guaranteed to be portable between different platforms or versions. If you
* need to convert to a format suitable for storage, transmission, or
* comparison, use the corresponding serialization and parsing functions.
*/
/** Opaque data structure that caches information about key tweaking.
*
* Guaranteed to be 101 bytes in size. It can be safely copied/moved. No
* serialization and parsing functions.
*/
typedef struct {
unsigned char data[101];
} secp256k1_frost_tweak_cache;
/** Opaque data structure that holds a signer's _secret_ share.
*
* Guaranteed to be 36 bytes in size. Serialized and parsed with
* `frost_share_serialize` and `frost_share_parse`.
*/
typedef struct {
unsigned char data[36];
} secp256k1_frost_share;
/** Opaque data structure that holds a signer's _secret_ nonce.
*
* Guaranteed to be 68 bytes in size.
*
* WARNING: This structure MUST NOT be copied or read or written to directly.
* A signer who is online throughout the whole process and can keep this
* structure in memory can use the provided API functions for a safe standard
* workflow. See
* https://blockstream.com/2019/02/18/musig-a-new-multisignature-standard/ for
* more details about the risks associated with serializing or deserializing
* this structure.
*
* We repeat, copying this data structure can result in nonce reuse which will
* leak the secret signing key.
*/
typedef struct {
unsigned char data[68];
} secp256k1_frost_secnonce;
/** Opaque data structure that holds a signer's public nonce.
*
* Guaranteed to be 132 bytes in size. It can be safely copied/moved.
* Serialized and parsed with `frost_pubnonce_serialize` and
* `frost_pubnonce_parse`.
*/
typedef struct {
unsigned char data[132];
} secp256k1_frost_pubnonce;
/** Opaque data structure that holds a FROST session.
*
* This structure is not required to be kept secret for the signing protocol
* to be secure. Guaranteed to be 133 bytes in size. It can be safely
* copied/moved. No serialization and parsing functions.
*/
typedef struct {
unsigned char data[133];
} secp256k1_frost_session;
/** Opaque data structure that holds a partial FROST signature.
*
* Guaranteed to be 36 bytes in size. Serialized and parsed with
* `frost_partial_sig_serialize` and `frost_partial_sig_parse`.
*/
typedef struct {
unsigned char data[36];
} secp256k1_frost_partial_sig;
/** Parse a signer's public nonce.
*
* Returns: 1 when the nonce could be parsed, 0 otherwise.
* Args: ctx: pointer to a context object
* Out: nonce: pointer to a nonce object
* In: in66: pointer to the 66-byte nonce to be parsed
*/
SECP256K1_API int secp256k1_frost_pubnonce_parse(
const secp256k1_context *ctx,
secp256k1_frost_pubnonce *nonce,
const unsigned char *in66
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Serialize a signer's public nonce
*
* Returns: 1 when the nonce could be serialized, 0 otherwise
* Args: ctx: pointer to a context object
* Out: out66: pointer to a 66-byte array to store the serialized nonce
* In: nonce: pointer to the nonce
*/
SECP256K1_API int secp256k1_frost_pubnonce_serialize(
const secp256k1_context *ctx,
unsigned char *out66,
const secp256k1_frost_pubnonce *nonce
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Serialize a FROST partial signature
*
* Returns: 1 when the signature could be serialized, 0 otherwise
* Args: ctx: pointer to a context object
* Out: out32: pointer to a 32-byte array to store the serialized signature
* In: sig: pointer to the signature
*/
SECP256K1_API int secp256k1_frost_partial_sig_serialize(
const secp256k1_context *ctx,
unsigned char *out32,
const secp256k1_frost_partial_sig *sig
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Parse a FROST partial signature.
*
* Returns: 1 when the signature could be parsed, 0 otherwise.
* Args: ctx: pointer to a context object
* Out: sig: pointer to a signature object
* In: in32: pointer to the 32-byte signature to be parsed
*
* After the call, sig will always be initialized. If parsing failed or the
* encoded numbers are out of range, signature verification with it is
* guaranteed to fail for every message and public key.
*/
SECP256K1_API int secp256k1_frost_partial_sig_parse(
const secp256k1_context *ctx,
secp256k1_frost_partial_sig *sig,
const unsigned char *in32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Serialize a FROST share
*
* Returns: 1 when the share could be serialized, 0 otherwise
* Args: ctx: pointer to a context object
* Out: out32: pointer to a 32-byte array to store the serialized share
* In: share: pointer to the share
*/
SECP256K1_API int secp256k1_frost_share_serialize(
const secp256k1_context *ctx,
unsigned char *out32,
const secp256k1_frost_share *share
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Parse a FROST share.
*
* Returns: 1 when the share could be parsed, 0 otherwise.
* Args: ctx: pointer to a context object
* Out: share: pointer to a share object
* In: in32: pointer to the 32-byte share to be parsed
*/
SECP256K1_API int secp256k1_frost_share_parse(
const secp256k1_context *ctx,
secp256k1_frost_share *share,
const unsigned char *in32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Creates key shares
*
* To generate a key, a trusted dealer generates a share for each other
* participant.
*
* The trusted dealer must transmit shares over secure channels to
* participants.
*
* Each call to this function must have a UNIQUE and uniformly RANDOM seed32
* that must that must NOT BE REUSED in subsequent calls to this function and
* must be KEPT SECRET (even from other participants).
*
* Returns: 0 if the arguments are invalid, 1 otherwise
* Args: ctx: pointer to a context object
* Out: shares: pointer to the key shares
* pubshares: pointer to the public verification shares
* pk: pointer to the x-only public key
* In: seed32: a 32-byte random seed as explained above. Must be
* unique to this call to
* secp256k1_frost_shares_trusted_gen and must be
* uniformly random.
* threshold: the minimum number of signers required to produce a
* signature
* n_participants: the total number of participants
*/
SECP256K1_API int secp256k1_frost_shares_trusted_gen(
const secp256k1_context *ctx,
secp256k1_frost_share *shares,
secp256k1_pubkey *pubshares,
secp256k1_xonly_pubkey *pk,
const unsigned char *seed32,
size_t threshold,
size_t n_participants
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
/** Obtain the aggregate public key from a FROST x-only aggregate public key.
*
* This is only useful if you need the non-xonly public key, in particular for
* ordinary (non-xonly) tweaking or batch-verifying multiple key aggregations
* (not implemented).
*
* Returns: 0 if the arguments are invalid, 1 otherwise
* Args: ctx: pointer to a context object
* Out: ec_agg_pk: the FROST-aggregated public key.
* In: xonly_agg_pk: the aggregated x-only public key that is the output of
* `secp256k1_frost_shares_gen`
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get(
const secp256k1_context *ctx,
secp256k1_pubkey *ec_agg_pk,
const secp256k1_xonly_pubkey *xonly_agg_pk
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Initializes a tweak cache used for applying tweaks to a FROST key
*
* Returns: 0 if the arguments are invalid, 1 otherwise
* Args: ctx: pointer to a context object
* Out: tweak_cache: pointer to a frost_tweak_cache struct that is required
* for key tweaking
* In: agg_pk: the aggregated x-only public key that is the output of
* `secp256k1_frost_shares_gen`
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_tweak(
const secp256k1_context *ctx,
secp256k1_frost_tweak_cache *tweak_cache,
const secp256k1_xonly_pubkey *agg_pk
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Apply ordinary "EC" tweaking to a public key in a given tweak_cache by
* adding the generator multiplied with `tweak32` to it. This is useful for
* deriving child keys from an aggregate public key via BIP32.
*
* The tweaking method is the same as `secp256k1_ec_pubkey_tweak_add`. So after
* the following pseudocode buf and buf2 have identical contents (absent
* earlier failures).
*
* secp256k1_frost_shares_gen(..., xonly_agg_pk, ...)
* secp256k1_frost_pubkey_tweak(..., tweak_cache, xonly_agg_pk)
* secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, tweak_cache, tweak32)
* secp256k1_ec_pubkey_serialize(..., buf, output_pk)
* secp256k1_frost_pubkey_get(..., ec_agg_pk, xonly_agg_pk)
* secp256k1_ec_pubkey_tweak_add(..., ec_agg_pk, tweak32)
* secp256k1_ec_pubkey_serialize(..., buf2, ec_agg_pk)
*
* This function is required if you want to _sign_ for a tweaked aggregate key.
* On the other hand, if you are only computing a public key, but not intending
* to create a signature for it, you can just use
* `secp256k1_ec_pubkey_tweak_add`.
*
* Returns: 0 if the arguments are invalid or the resulting public key would be
* invalid (only when the tweak is the negation of the corresponding
* secret key). 1 otherwise.
* Args: ctx: pointer to a context object
* Out: output_pubkey: pointer to a public key to store the result. Will be set
* to an invalid value if this function returns 0. If you
* do not need it, this arg can be NULL.
* In/Out: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by
* `frost_pubkey_tweak`
* In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid
* according to `secp256k1_ec_seckey_verify`, this function
* returns 0. For uniformly random 32-byte arrays the
* chance of being invalid is negligible (around 1 in
* 2^128).
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_add(
const secp256k1_context *ctx,
secp256k1_pubkey *output_pubkey,
secp256k1_frost_tweak_cache *tweak_cache,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Apply x-only tweaking to a public key in a given tweak_cache by adding the
* generator multiplied with `tweak32` to it. This is useful for creating
* Taproot outputs.
*
* The tweaking method is the same as `secp256k1_xonly_pubkey_tweak_add`. So in
* the following pseudocode xonly_pubkey_tweak_add_check (absent earlier
* failures) returns 1.
*
* secp256k1_frost_shares_gen(..., agg_pk, ...)
* secp256k1_frost_pubkey_tweak(..., tweak_cache, agg_pk)
* secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, tweak_cache, tweak32)
* secp256k1_xonly_pubkey_serialize(..., buf, output_pk)
* secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32)
*
* This function is required if you want to _sign_ for a tweaked aggregate key.
* On the other hand, if you are only computing a public key, but not intending
* to create a signature for it, you can just use
* `secp256k1_xonly_pubkey_tweak_add`.
*
* Returns: 0 if the arguments are invalid or the resulting public key would be
* invalid (only when the tweak is the negation of the corresponding
* secret key). 1 otherwise.
* Args: ctx: pointer to a context object
* Out: output_pubkey: pointer to a public key to store the result. Will be set
* to an invalid value if this function returns 0. If you
* do not need it, this arg can be NULL.
* In/Out: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by
* `frost_pubkey_tweak`
* In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid
* according to secp256k1_ec_seckey_verify, this function
* returns 0. For uniformly random 32-byte arrays the
* chance of being invalid is negligible (around 1 in
* 2^128).
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_tweak_add(
const secp256k1_context *ctx,
secp256k1_pubkey *output_pubkey,
secp256k1_frost_tweak_cache *tweak_cache,
const unsigned char *tweak32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Starts a signing session by generating a nonce
*
* This function outputs a secret nonce that will be required for signing and a
* corresponding public nonce that is intended to be sent to other signers.
*
* FROST, like MuSig, differs from regular Schnorr signing in that
* implementers _must_ take special care to not reuse a nonce. This can be
* ensured by following these rules:
*
* 1. Each call to this function must have a UNIQUE session_id32 that must NOT BE
* REUSED in subsequent calls to this function.
* If you do not provide a seckey, session_id32 _must_ be UNIFORMLY RANDOM
* AND KEPT SECRET (even from other signers). If you do provide a seckey,
* session_id32 can instead be a counter (that must never repeat!). However,
* it is recommended to always choose session_id32 uniformly at random.
* 2. If you already know the seckey, message or aggregate public key, they
* can be optionally provided to derive the nonce and increase
* misuse-resistance. The extra_input32 argument can be used to provide
* additional data that does not repeat in normal scenarios, such as the
* current time.
* 3. Avoid copying (or serializing) the secnonce. This reduces the possibility
* that it is used more than once for signing.
*
* Remember that nonce reuse will leak the secret key!
* Note that using the same agg_share for multiple FROST sessions is fine.
*
* Returns: 0 if the arguments are invalid and 1 otherwise
* Args: ctx: pointer to a context object (not secp256k1_context_static)
* Out: secnonce: pointer to a structure to store the secret nonce
* pubnonce: pointer to a structure to store the public nonce
* In: session_id32: a 32-byte session_id32 as explained above. Must be
* unique to this call to secp256k1_frost_nonce_gen and
* must be uniformly random unless you really know what you
* are doing.
* agg_share: the aggregated share that will later be used for
* signing, if already known (can be NULL)
* msg32: the 32-byte message that will later be signed, if
* already known (can be NULL)
* agg_pk: the FROST-aggregated public key (can be NULL)
* extra_input32: an optional 32-byte array that is input to the nonce
* derivation function (can be NULL)
*/
SECP256K1_API int secp256k1_frost_nonce_gen(
const secp256k1_context *ctx,
secp256k1_frost_secnonce *secnonce,
secp256k1_frost_pubnonce *pubnonce,
const unsigned char *session_id32,
const secp256k1_frost_share *agg_share,
const unsigned char *msg32,
const secp256k1_xonly_pubkey *agg_pk,
const unsigned char *extra_input32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Takes the public nonces of all signers and computes a session that is
* required for signing and verification of partial signatures. The participant
* IDs can be sorted before combining, but the corresponding pubnonces must be
* resorted as well. All signers must use the same sorting of pubnonces,
* otherwise signing will fail.
*
* Returns: 0 if the arguments are invalid or if some signer sent invalid
* pubnonces, 1 otherwise
* Args: ctx: pointer to a context object
* Out: session: pointer to a struct to store the session
* In: pubnonces: array of pointers to public nonces sent by the signers
* n_pubnonces: number of elements in the pubnonces array. Must be
* greater than 0.
* msg32: the 32-byte message to sign
* agg_pk: the FROST-aggregated public key
* my_id: the ID of the participant who will use the session for
* signing
* ids: array of the IDs of the signers
* tweak_cache: pointer to frost_tweak_cache struct (can be NULL)
* adaptor: optional pointer to an adaptor point encoded as a
* public key if this signing session is part of an
* adaptor signature protocol (can be NULL)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process(
const secp256k1_context *ctx,
secp256k1_frost_session *session,
const secp256k1_frost_pubnonce * const *pubnonces,
size_t n_pubnonces,
const unsigned char *msg32,
const secp256k1_xonly_pubkey *agg_pk,
size_t my_id,
const size_t *ids,
const secp256k1_frost_tweak_cache *tweak_cache,
const secp256k1_pubkey *adaptor
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8);
/** Produces a partial signature
*
* This function overwrites the given secnonce with zeros and will abort if given a
* secnonce that is all zeros. This is a best effort attempt to protect against nonce
* reuse. However, this is of course easily defeated if the secnonce has been
* copied (or serialized). Remember that nonce reuse will leak the secret key!
*
* Returns: 0 if the arguments are invalid or the provided secnonce has already
* been used for signing, 1 otherwise
* Args: ctx: pointer to a context object
* Out: partial_sig: pointer to struct to store the partial signature
* In/Out: secnonce: pointer to the secnonce struct created in
* frost_nonce_gen that has been never used in a
* partial_sign call before
* In: agg_share: the aggregated share
* session: pointer to the session that was created with
* frost_nonce_process
* tweak_cache: pointer to frost_tweak_cache struct (can be NULL)
*/
SECP256K1_API int secp256k1_frost_partial_sign(
const secp256k1_context *ctx,
secp256k1_frost_partial_sig *partial_sig,
secp256k1_frost_secnonce *secnonce,
const secp256k1_frost_share *agg_share,
const secp256k1_frost_session *session,
const secp256k1_frost_tweak_cache *tweak_cache
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
/** Verifies an individual signer's partial signature
*
* The signature is verified for a specific signing session. In order to avoid
* accidentally verifying a signature from a different or non-existing signing
* session, you must ensure the following:
* 1. The `tweak_cache` argument is identical to the one used to create the
* `session` with `frost_nonce_process`.
* 2. The `pubshare` argument must be the output of
* `secp256k1_frost_shares_trusted_gen` for the signer's 'pk'.
* 3. The `pubnonce` argument must be identical to the one sent by the
* signer and used to create the `session` with `frost_nonce_process`.
*
* This function can be used to assign blame for a failed signature.
*
* Returns: 0 if the arguments are invalid or the partial signature does not
* verify, 1 otherwise
* Args ctx: pointer to a context object
* In: partial_sig: pointer to partial signature to verify, sent by
* the signer associated with `pubnonce` and `pubkey`
* pubnonce: public nonce of the signer in the signing session
* pubshare: public verification share of the signer in the signing
* session that is the output of
* `secp256k1_frost_shares_trusted_gen`
* session: pointer to the session that was created with
* `frost_nonce_process`
* tweak_cache: pointer to frost_tweak_cache struct (can be NULL)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_verify(
const secp256k1_context *ctx,
const secp256k1_frost_partial_sig *partial_sig,
const secp256k1_frost_pubnonce *pubnonce,
const secp256k1_pubkey *pubshare,
const secp256k1_frost_session *session,
const secp256k1_frost_tweak_cache *tweak_cache
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
/** Aggregates partial signatures
*
* Returns: 0 if the arguments are invalid, 1 otherwise (which does NOT mean
* the resulting signature verifies).
* Args: ctx: pointer to a context object
* Out: sig64: complete (but possibly invalid) Schnorr signature
* In: session: pointer to the session that was created with
* frost_nonce_process
* partial_sigs: array of pointers to partial signatures to aggregate
* n_sigs: number of elements in the partial_sigs array. Must be
* greater than 0.
*/
SECP256K1_API int secp256k1_frost_partial_sig_agg(
const secp256k1_context *ctx,
unsigned char *sig64,
const secp256k1_frost_session *session,
const secp256k1_frost_partial_sig * const *partial_sigs,
size_t n_sigs
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Extracts the nonce_parity bit from a session
*
* This is used for adaptor signatures.
*
* Returns: 0 if the arguments are invalid, 1 otherwise
* Args: ctx: pointer to a context object
* Out: nonce_parity: pointer to an integer that indicates the parity
* of the aggregate public nonce. Used for adaptor
* signatures.
* In: session: pointer to the session that was created with
* frost_nonce_process
*/
SECP256K1_API int secp256k1_frost_nonce_parity(
const secp256k1_context *ctx,
int *nonce_parity,
const secp256k1_frost_session *session
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Verifies that the adaptor can be extracted by combining the adaptor
* pre-signature and the completed signature.
*
* Returns: 0 if the arguments are invalid or the adaptor signature does not
* verify, 1 otherwise
* Args: ctx: pointer to a context object
* In: pre_sig64: 64-byte pre-signature
* msg32: the 32-byte message being verified
* pubkey: pointer to an x-only public key to verify with
* adaptor: pointer to the adaptor point being verified
* nonce_parity: the output of `frost_nonce_parity` called with the
* session used for producing the pre-signature
*/
SECP256K1_API int secp256k1_frost_verify_adaptor(
const secp256k1_context *ctx,
const unsigned char *pre_sig64,
const unsigned char *msg32,
const secp256k1_xonly_pubkey *pubkey,
const secp256k1_pubkey *adaptor,
int nonce_parity
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
/** Creates a signature from a pre-signature and an adaptor.
*
* If the sec_adaptor32 argument is incorrect, the output signature will be
* invalid. This function does not verify the signature.
*
* Returns: 0 if the arguments are invalid, or pre_sig64 or sec_adaptor32 contain
* invalid (overflowing) values. 1 otherwise (which does NOT mean the
* signature or the adaptor are valid!)
* Args: ctx: pointer to a context object
* Out: sig64: 64-byte signature. This pointer may point to the same
* memory area as `pre_sig`.
* In: pre_sig64: 64-byte pre-signature
* sec_adaptor32: 32-byte secret adaptor to add to the pre-signature
* nonce_parity: the output of `frost_nonce_parity` called with the
* session used for producing the pre-signature
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_adapt(
const secp256k1_context *ctx,
unsigned char *sig64,
const unsigned char *pre_sig64,
const unsigned char *sec_adaptor32,
int nonce_parity
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Extracts a secret adaptor from a FROST pre-signature and corresponding
* signature
*
* This function will not fail unless given grossly invalid data; if it is
* merely given signatures that do not verify, the returned value will be
* nonsense. It is therefore important that all data be verified at earlier
* steps of any protocol that uses this function. In particular, this includes
* verifying all partial signatures that were aggregated into pre_sig64.
*
* Returns: 0 if the arguments are NULL, or sig64 or pre_sig64 contain
* grossly invalid (overflowing) values. 1 otherwise (which does NOT
* mean the signatures or the adaptor are valid!)
* Args: ctx: pointer to a context object
* Out:sec_adaptor32: 32-byte secret adaptor
* In: sig64: complete, valid 64-byte signature
* pre_sig64: the pre-signature corresponding to sig64, i.e., the
* aggregate of partial signatures without the secret
* adaptor
* nonce_parity: the output of `frost_nonce_parity` called with the
* session used for producing sig64
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_extract_adaptor(
const secp256k1_context *ctx,
unsigned char *sec_adaptor32,
const unsigned char *sig64,
const unsigned char *pre_sig64,
int nonce_parity
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -47,6 +47,10 @@
#include "../include/secp256k1_musig.h" #include "../include/secp256k1_musig.h"
#endif #endif
#ifdef ENABLE_MODULE_FROST
#include "include/secp256k1_frost.h"
#endif
static void run_tests(secp256k1_context *ctx, unsigned char *key); static void run_tests(secp256k1_context *ctx, unsigned char *key);
int main(void) { int main(void) {
@ -349,4 +353,79 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
CHECK(ret == 1); CHECK(ret == 1);
} }
#endif #endif
#ifdef ENABLE_MODULE_FROST
{
secp256k1_xonly_pubkey pk;
unsigned char session_id[32];
secp256k1_frost_secnonce secnonce[2];
secp256k1_frost_pubnonce pubnonce[2];
const secp256k1_frost_pubnonce *pubnonce_ptr[2];
secp256k1_frost_tweak_cache cache;
secp256k1_frost_session session;
secp256k1_frost_partial_sig partial_sig;
const secp256k1_frost_partial_sig *partial_sig_ptr[1];
unsigned char extra_input[32];
unsigned char sec_adaptor[32];
secp256k1_pubkey adaptor;
unsigned char pre_sig[64];
int nonce_parity;
secp256k1_frost_share shares[2];
secp256k1_pubkey pubshares[2];
size_t ids[2];
pubnonce_ptr[0] = &pubnonce[0];
pubnonce_ptr[1] = &pubnonce[1];
SECP256K1_CHECKMEM_DEFINE(key, 32);
memcpy(extra_input, key, sizeof(extra_input));
extra_input[0] = extra_input[0] + 1;
memcpy(sec_adaptor, key, sizeof(sec_adaptor));
sec_adaptor[0] = extra_input[0] + 2;
memcpy(session_id, key, sizeof(session_id));
session_id[0] = session_id[0] + 3;
partial_sig_ptr[0] = &partial_sig;
ids[0] = 1;
ids[1] = 2;
/* shares_gen */
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
ret = secp256k1_frost_shares_trusted_gen(ctx, shares, pubshares, &pk, key, 2, 2);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_UNDEFINE(&shares[0], sizeof(shares[0]));
SECP256K1_CHECKMEM_UNDEFINE(&shares[1], sizeof(shares[1]));
/* nonce_gen */
SECP256K1_CHECKMEM_UNDEFINE(session_id, sizeof(session_id));
CHECK(secp256k1_ec_pubkey_create(ctx, &adaptor, sec_adaptor));
SECP256K1_CHECKMEM_UNDEFINE(extra_input, sizeof(extra_input));
SECP256K1_CHECKMEM_UNDEFINE(sec_adaptor, sizeof(sec_adaptor));
CHECK(secp256k1_frost_pubkey_tweak(ctx, &cache, &pk) == 1);
ret = secp256k1_frost_nonce_gen(ctx, &secnonce[0], &pubnonce[0], session_id, &shares[0], msg, &pk, extra_input);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
ret = secp256k1_frost_nonce_gen(ctx, &secnonce[1], &pubnonce[1], session_id, &shares[1], msg, &pk, extra_input);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
/* partial_sign */
CHECK(secp256k1_frost_nonce_process(ctx, &session, pubnonce_ptr, 2, msg, &pk, 1, ids, &cache, &adaptor) == 1);
ret = secp256k1_keypair_create(ctx, &keypair, key);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
ret = secp256k1_frost_partial_sign(ctx, &partial_sig, &secnonce[0], &shares[0], &session, &cache);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
/* adapt */
SECP256K1_CHECKMEM_DEFINE(&partial_sig, sizeof(partial_sig));
CHECK(secp256k1_frost_partial_sig_agg(ctx, pre_sig, &session, partial_sig_ptr, 1));
SECP256K1_CHECKMEM_DEFINE(pre_sig, sizeof(pre_sig));
CHECK(secp256k1_frost_nonce_parity(ctx, &nonce_parity, &session));
ret = secp256k1_frost_adapt(ctx, sig, pre_sig, sec_adaptor, nonce_parity);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
/* extract_adaptor */
ret = secp256k1_frost_extract_adaptor(ctx, sec_adaptor, sig, pre_sig, nonce_parity);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
}
#endif
} }

View File

@ -202,6 +202,10 @@ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf);
*/ */
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge); static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge);
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge);
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data);
/** Check invariants on an affine group element (no-op unless VERIFY is enabled). */ /** Check invariants on an affine group element (no-op unless VERIFY is enabled). */
static void secp256k1_ge_verify(const secp256k1_ge *a); static void secp256k1_ge_verify(const secp256k1_ge *a);
#define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a) #define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a)

View File

@ -914,6 +914,23 @@ static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) {
return secp256k1_fe_is_square_var(&yz); return secp256k1_fe_is_square_var(&yz);
} }
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) {
if (secp256k1_ge_is_infinity(ge)) {
memset(data, 0, 64);
} else {
secp256k1_ge_to_bytes(data, ge);
}
}
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) {
unsigned char zeros[64] = { 0 };
if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) {
secp256k1_ge_set_infinity(ge);
} else {
secp256k1_ge_from_bytes(ge, data);
}
}
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) { static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) {
#ifdef EXHAUSTIVE_TEST_ORDER #ifdef EXHAUSTIVE_TEST_ORDER
secp256k1_gej out; secp256k1_gej out;

View File

@ -0,0 +1,8 @@
include_HEADERS += include/secp256k1_frost.h
noinst_HEADERS += src/modules/frost/main_impl.h
noinst_HEADERS += src/modules/frost/keygen.h
noinst_HEADERS += src/modules/frost/keygen_impl.h
noinst_HEADERS += src/modules/frost/session.h
noinst_HEADERS += src/modules/frost/session_impl.h
noinst_HEADERS += src/modules/frost/adaptor_impl.h
noinst_HEADERS += src/modules/frost/tests_impl.h

View File

@ -0,0 +1,168 @@
/***********************************************************************
* Copyright (c) 2022-2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_FROST_ADAPTOR_IMPL_H
#define SECP256K1_MODULE_FROST_ADAPTOR_IMPL_H
#include <string.h>
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_frost.h"
#include "session.h"
#include "../../scalar.h"
int secp256k1_frost_nonce_parity(const secp256k1_context* ctx, int *nonce_parity, const secp256k1_frost_session *session) {
secp256k1_frost_session_internal session_i;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(nonce_parity != NULL);
ARG_CHECK(session != NULL);
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
return 0;
}
*nonce_parity = session_i.fin_nonce_parity;
return 1;
}
int secp256k1_frost_verify_adaptor(const secp256k1_context* ctx, const unsigned char *pre_sig64, const unsigned char *msg32, const secp256k1_xonly_pubkey *pubkey, const secp256k1_pubkey *adaptor, int nonce_parity) {
secp256k1_scalar s;
secp256k1_scalar e;
secp256k1_gej rj;
secp256k1_ge pk;
secp256k1_gej pkj;
secp256k1_ge r;
unsigned char buf[32];
int overflow;
secp256k1_ge adaptorp;
secp256k1_xonly_pubkey noncepk;
secp256k1_gej fin_nonce_ptj;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(pre_sig64 != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(pubkey != NULL);
ARG_CHECK(adaptor != NULL);
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
if (!secp256k1_xonly_pubkey_parse(ctx, &noncepk, &pre_sig64[0])) {
return 0;
}
if (!secp256k1_xonly_pubkey_load(ctx, &r, &noncepk)) {
return 0;
}
if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) {
return 0;
}
if (!nonce_parity) {
secp256k1_ge_neg(&adaptorp, &adaptorp);
}
secp256k1_gej_set_ge(&fin_nonce_ptj, &adaptorp);
secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &r, NULL);
if (secp256k1_gej_is_infinity(&fin_nonce_ptj)) {
/* unreachable with overwhelming probability */
return 0;
}
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
if (overflow) {
return 0;
}
if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
return 0;
}
/* Compute e. */
secp256k1_fe_get_b32(buf, &pk.x);
secp256k1_schnorrsig_challenge(&e, &pre_sig64[0], msg32, 32, buf);
/* Compute rj = s*G + (-e)*pkj */
secp256k1_scalar_negate(&e, &e);
secp256k1_gej_set_ge(&pkj, &pk);
secp256k1_ecmult(&rj, &pkj, &e, &s);
/* secp256k1_ge_set_gej_var(&r, &rj); */
if (secp256k1_gej_is_infinity(&rj)) {
return 0;
}
secp256k1_gej_neg(&rj, &rj);
secp256k1_gej_add_var(&rj, &rj, &fin_nonce_ptj, NULL);
return secp256k1_gej_is_infinity(&rj);
}
int secp256k1_frost_adapt(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *pre_sig64, const unsigned char *sec_adaptor32, int nonce_parity) {
secp256k1_scalar s;
secp256k1_scalar t;
int overflow;
int ret = 1;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(sig64 != NULL);
ARG_CHECK(pre_sig64 != NULL);
ARG_CHECK(sec_adaptor32 != NULL);
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
if (overflow) {
return 0;
}
secp256k1_scalar_set_b32(&t, sec_adaptor32, &overflow);
ret &= !overflow;
/* Determine if the secret adaptor should be negated.
*
* The frost_session stores the X-coordinate and the parity of the "final nonce"
* (r + t)*G, where r*G is the aggregate public nonce and t is the secret adaptor.
*
* Since a BIP340 signature requires an x-only public nonce, in the case where
* (r + t)*G has odd Y-coordinate (i.e. nonce_parity == 1), the x-only public nonce
* corresponding to the signature is actually (-r - t)*G. Thus adapting a
* pre-signature requires negating t in this case.
*/
if (nonce_parity) {
secp256k1_scalar_negate(&t, &t);
}
secp256k1_scalar_add(&s, &s, &t);
secp256k1_scalar_get_b32(&sig64[32], &s);
memmove(sig64, pre_sig64, 32);
secp256k1_scalar_clear(&t);
return ret;
}
int secp256k1_frost_extract_adaptor(const secp256k1_context* ctx, unsigned char *sec_adaptor32, const unsigned char *sig64, const unsigned char *pre_sig64, int nonce_parity) {
secp256k1_scalar t;
secp256k1_scalar s;
int overflow;
int ret = 1;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(sec_adaptor32 != NULL);
ARG_CHECK(sig64 != NULL);
ARG_CHECK(pre_sig64 != NULL);
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
secp256k1_scalar_set_b32(&t, &sig64[32], &overflow);
ret &= !overflow;
secp256k1_scalar_negate(&t, &t);
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
if (overflow) {
return 0;
}
secp256k1_scalar_add(&t, &t, &s);
if (!nonce_parity) {
secp256k1_scalar_negate(&t, &t);
}
secp256k1_scalar_get_b32(sec_adaptor32, &t);
secp256k1_scalar_clear(&t);
return ret;
}
#endif

View File

@ -0,0 +1,78 @@
Notes on the frost module API
===========================
The following sections contain additional notes on the API of the frost module
(`include/secp256k1_frost.h`). A usage example can be found in
`examples/frost.c`.
# API misuse
Users of the frost module must take great care to make sure of the following:
1. The dealer establishes a secure communications channel with each participant
and uses that channel to transmit shares during key generation.
2. A unique set of coefficients per key generation session is generated in
`secp256k1_frost_share_gen`. See the corresponding comment in
`include/secp256k1_frost.h` for how to ensure that.
3. The `pubnonces` provided to `secp256k1_frost_nonce_process` are sorted by
the corresponding lexicographic ordering of the x-only pubkey of each
participant, and the `pubkeys` provided to `secp256k1_frost_nonce_process`
are sorted lexicographically.
4. A unique nonce per signing session is generated in
`secp256k1_frost_nonce_gen`. See the corresponding comment in
`include/secp256k1_frost.h` for how to ensure that.
5. The `secp256k1_frost_secnonce` structure is never copied or serialized. See
also the comment on `secp256k1_frost_secnonce` in
`include/secp256k1_frost.h`.
6. Opaque data structures are never written to or read from directly. Instead,
only the provided accessor functions are used.
7. If adaptor signatures are used, all partial signatures are verified.
# Key Generation
1. A trusted dealer generates shares with `secp256k1_frost_shares_trusted_gen`
and distributes a share and the public key to each participant using a
secure channel.
# Tweaking
A (Taproot) tweak can be added to the resulting public key with
`secp256k1_xonly_pubkey_tweak_add`, after converting it to an xonly pubkey if
necessary with `secp256k1_xonly_pubkey_from_pubkey`.
An ordinary tweak can be added to the resulting public key with
`secp256k1_ec_pubkey_tweak_add`, after converting it to an ordinary pubkey if
necessary with `secp256k1_frost_pubkey_get`.
Tweaks can also be chained together by tweaking an already tweaked key.
# Signing
1. Optionally add a tweak by calling `secp256k1_frost_pubkey_tweak` and then
`secp256k1_frost_pubkey_xonly_tweak_add` for a Taproot tweak and
`secp256k1_frost_pubkey_ec_tweak_add` for an ordinary tweak.
2. Generate a pair of secret and public nonce with `secp256k1_frost_nonce_gen`
and send the public nonce to the other signers.
3. Process the aggregate nonce with `secp256k1_frost_nonce_process`.
4. Create a partial signature with `secp256k1_frost_partial_sign`.
5. Verify the partial signatures (optional in some scenarios) with
`secp256k1_frost_partial_sig_verify`.
6. Someone (not necessarily the signer) obtains all partial signatures and
aggregates them into the final Schnorr signature using
`secp256k1_frost_partial_sig_agg`.
The aggregate signature can be verified with `secp256k1_schnorrsig_verify`.
Note that steps 1 to 3 can happen before the message to be signed is known to
the signers. Therefore, the communication round to exchange nonces can be
viewed as a pre-processing step that is run whenever convenient to the signers.
This disables some of the defense-in-depth measures that may protect against
API misuse in some cases. Similarly, the API supports an alternative protocol
flow where generating the key (see Key Generation above) is allowed to happen
after exchanging nonces (step 2).
# Verification
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 2 and 4.

View File

@ -0,0 +1,26 @@
/**********************************************************************
* Copyright (c) 2021-2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef SECP256K1_MODULE_FROST_KEYGEN_H
#define SECP256K1_MODULE_FROST_KEYGEN_H
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_frost.h"
#include "../../group.h"
#include "../../scalar.h"
typedef struct {
secp256k1_ge pk;
secp256k1_scalar tweak;
int parity_acc;
} secp256k1_tweak_cache_internal;
static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache);
static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share);
#endif

View File

@ -0,0 +1,257 @@
/**********************************************************************
* Copyright (c) 2021-2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef SECP256K1_MODULE_FROST_KEYGEN_IMPL_H
#define SECP256K1_MODULE_FROST_KEYGEN_IMPL_H
#include <string.h>
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_extrakeys.h"
#include "../../../include/secp256k1_frost.h"
#include "keygen.h"
#include "../../ecmult.h"
#include "../../field.h"
#include "../../group.h"
#include "../../hash.h"
#include "../../scalar.h"
static const unsigned char secp256k1_frost_tweak_cache_magic[4] = { 0x40, 0x25, 0x2e, 0x41 };
/* A tweak cache consists of
* - 4 byte magic set during initialization to allow detecting an uninitialized
* object.
* - 64 byte aggregate (and potentially tweaked) public key
* - 1 byte the parity of the internal key (if tweaked, otherwise 0)
* - 32 byte tweak
*/
/* Requires that cache_i->pk is not infinity. */
static void secp256k1_tweak_cache_save(secp256k1_frost_tweak_cache *cache, secp256k1_tweak_cache_internal *cache_i) {
unsigned char *ptr = cache->data;
memcpy(ptr, secp256k1_frost_tweak_cache_magic, 4);
ptr += 4;
secp256k1_point_save_ext(ptr, &cache_i->pk);
ptr += 64;
*ptr = cache_i->parity_acc;
ptr += 1;
secp256k1_scalar_get_b32(ptr, &cache_i->tweak);
}
static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache) {
const unsigned char *ptr = cache->data;
ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_tweak_cache_magic, 4) == 0);
ptr += 4;
secp256k1_point_load_ext(&cache_i->pk, ptr);
ptr += 64;
cache_i->parity_acc = *ptr & 1;
ptr += 1;
secp256k1_scalar_set_b32(&cache_i->tweak, ptr, NULL);
return 1;
}
static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 };
static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_scalar *s) {
memcpy(&share->data[0], secp256k1_frost_share_magic, 4);
secp256k1_scalar_get_b32(&share->data[4], s);
}
static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share) {
int overflow;
/* The magic is non-secret so it can be declassified to allow branching. */
secp256k1_declassify(ctx, &share->data[0], 4);
ARG_CHECK(secp256k1_memcmp_var(&share->data[0], secp256k1_frost_share_magic, 4) == 0);
secp256k1_scalar_set_b32(s, &share->data[4], &overflow);
/* Parsed shares cannot overflow */
VERIFY_CHECK(!overflow);
return 1;
}
int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_share* share) {
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(out32 != NULL);
ARG_CHECK(share != NULL);
memcpy(out32, &share->data[4], 32);
return 1;
}
int secp256k1_frost_share_parse(const secp256k1_context* ctx, secp256k1_frost_share* share, const unsigned char *in32) {
secp256k1_scalar tmp;
int overflow;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(share != NULL);
ARG_CHECK(in32 != NULL);
secp256k1_scalar_set_b32(&tmp, in32, &overflow);
if (overflow) {
return 0;
}
secp256k1_frost_share_save(share, &tmp);
return 1;
}
int secp256k1_frost_shares_trusted_gen(const secp256k1_context *ctx, secp256k1_frost_share *shares, secp256k1_pubkey *pubshares, secp256k1_xonly_pubkey *pk, const unsigned char *seed32, size_t threshold, size_t n_participants) {
secp256k1_sha256 sha;
secp256k1_gej rj;
secp256k1_ge rp;
unsigned char polygen[32];
size_t i, j;
int ret = 1;
int pk_parity = 0;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(shares != NULL);
for (i = 0; i < n_participants; i++) {
memset(&shares[i], 0, sizeof(shares[i]));
}
ARG_CHECK(pubshares != NULL);
ARG_CHECK(pk != NULL);
ARG_CHECK(seed32 != NULL);
ARG_CHECK(threshold > 1);
ARG_CHECK(n_participants >= threshold);
/* Commit to threshold, n_participants, and seed */
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/trusted-shares-polygen", sizeof("FROST/trusted-shares-polygen") - 1);
secp256k1_sha256_write(&sha, seed32, 32);
secp256k1_write_be64(&polygen[0], threshold);
secp256k1_write_be64(&polygen[8], n_participants);
secp256k1_sha256_write(&sha, polygen, 16);
secp256k1_sha256_finalize(&sha, polygen);
/* Derive shares */
/* See draft-irtf-cfrg-frost-08#appendix-C.1 */
for (i = 0; i < n_participants; i++) {
secp256k1_scalar share_i, idx;
secp256k1_scalar_set_int(&share_i, 0);
for (j = 0; j < threshold; j++) {
unsigned char buf[32];
secp256k1_scalar coeff_i;
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/trusted-shares-coeffgen", sizeof("FROST/trusted-shares-coeffgen") - 1);
secp256k1_sha256_write(&sha, polygen, 32);
secp256k1_write_be64(&buf[0], j);
secp256k1_sha256_write(&sha, buf, 8);
secp256k1_sha256_finalize(&sha, buf);
secp256k1_scalar_set_b32(&coeff_i, buf, NULL);
/* Horner's method to evaluate polynomial to derive shares */
secp256k1_scalar_add(&share_i, &share_i, &coeff_i);
if (j < threshold - 1) {
secp256k1_scalar_set_int(&idx, i + 1);
secp256k1_scalar_mul(&share_i, &share_i, &idx);
}
/* Compute x-only public key for constant term */
if (i == 0 && j == threshold - 1) {
/* Compute commitment to constant term */
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &coeff_i);
secp256k1_ge_set_gej(&rp, &rj);
/* The commitment is non-secret so it can be declassified to
* allow branching. */
secp256k1_declassify(ctx, &rp, sizeof(rp));
secp256k1_fe_normalize_var(&rp.y);
pk_parity = secp256k1_extrakeys_ge_even_y(&rp);
secp256k1_xonly_pubkey_save(pk, &rp);
}
}
if (pk_parity == 1) {
secp256k1_scalar_negate(&share_i, &share_i);
}
secp256k1_frost_share_save(&shares[i], &share_i);
/* Compute pubshare */
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &share_i);
secp256k1_ge_set_gej(&rp, &rj);
secp256k1_pubkey_save(&pubshares[i], &rp);
}
return ret;
}
int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *ec_pk, const secp256k1_xonly_pubkey *xonly_pk) {
secp256k1_ge pk;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(ec_pk != NULL);
memset(ec_pk, 0, sizeof(*ec_pk));
ARG_CHECK(xonly_pk != NULL);
/* The output of keygen is an aggregated public key that *always* has an
* even Y coordinate. */
if (!secp256k1_xonly_pubkey_load(ctx, &pk, xonly_pk)) {
return 0;
}
secp256k1_pubkey_save(ec_pk, &pk);
return 1;
}
int secp256k1_frost_pubkey_tweak(const secp256k1_context* ctx, secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_xonly_pubkey *pk) {
secp256k1_tweak_cache_internal cache_i = { 0 };
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(tweak_cache != NULL);
ARG_CHECK(pk != NULL);
/* The output of keygen is an aggregated public key that *always* has an
* even Y coordinate. */
if (!secp256k1_xonly_pubkey_load(ctx, &cache_i.pk, pk)) {
return 0;
}
secp256k1_tweak_cache_save(tweak_cache, &cache_i);
return 1;
}
static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32, int xonly) {
secp256k1_tweak_cache_internal cache_i;
int overflow = 0;
secp256k1_scalar tweak;
VERIFY_CHECK(ctx != NULL);
if (output_pubkey != NULL) {
memset(output_pubkey, 0, sizeof(*output_pubkey));
}
ARG_CHECK(tweak_cache != NULL);
ARG_CHECK(tweak32 != NULL);
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
return 0;
}
secp256k1_scalar_set_b32(&tweak, tweak32, &overflow);
if (overflow) {
return 0;
}
if (xonly && secp256k1_extrakeys_ge_even_y(&cache_i.pk)) {
cache_i.parity_acc ^= 1;
secp256k1_scalar_negate(&cache_i.tweak, &cache_i.tweak);
}
secp256k1_scalar_add(&cache_i.tweak, &cache_i.tweak, &tweak);
if (!secp256k1_eckey_pubkey_tweak_add(&cache_i.pk, &tweak)) {
return 0;
}
/* eckey_pubkey_tweak_add fails if cache_i.pk is infinity */
VERIFY_CHECK(!secp256k1_ge_is_infinity(&cache_i.pk));
secp256k1_tweak_cache_save(tweak_cache, &cache_i);
if (output_pubkey != NULL) {
secp256k1_pubkey_save(output_pubkey, &cache_i.pk);
}
return 1;
}
int secp256k1_frost_pubkey_ec_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32) {
return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 0);
}
int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32) {
return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 1);
}
#endif

View File

@ -0,0 +1,14 @@
/**********************************************************************
* Copyright (c) 2021-2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef SECP256K1_MODULE_FROST_MAIN
#define SECP256K1_MODULE_FROST_MAIN
#include "keygen_impl.h"
#include "session_impl.h"
#include "adaptor_impl.h"
#endif

View File

@ -0,0 +1,25 @@
/**********************************************************************
* Copyright (c) 2021-2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef SECP256K1_MODULE_FROST_SESSION_H
#define SECP256K1_MODULE_FROST_SESSION_H
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_frost.h"
#include "../../scalar.h"
typedef struct {
int fin_nonce_parity;
unsigned char fin_nonce[32];
secp256k1_scalar noncecoef;
secp256k1_scalar challenge;
secp256k1_scalar s_part;
} secp256k1_frost_session_internal;
static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_frost_session_internal *session_i, const secp256k1_frost_session *session);
#endif

View File

@ -0,0 +1,658 @@
/**********************************************************************
* Copyright (c) 2021-2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef SECP256K1_MODULE_FROST_SESSION_IMPL_H
#define SECP256K1_MODULE_FROST_SESSION_IMPL_H
#include <string.h>
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_extrakeys.h"
#include "../../../include/secp256k1_frost.h"
#include "keygen.h"
#include "session.h"
#include "../../eckey.h"
#include "../../hash.h"
#include "../../scalar.h"
#include "../../util.h"
static const unsigned char secp256k1_frost_secnonce_magic[4] = { 0x84, 0x7d, 0x46, 0x25 };
static void secp256k1_frost_secnonce_save(secp256k1_frost_secnonce *secnonce, secp256k1_scalar *k) {
memcpy(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4);
secp256k1_scalar_get_b32(&secnonce->data[4], &k[0]);
secp256k1_scalar_get_b32(&secnonce->data[36], &k[1]);
}
static int secp256k1_frost_secnonce_load(const secp256k1_context* ctx, secp256k1_scalar *k, secp256k1_frost_secnonce *secnonce) {
int is_zero;
ARG_CHECK(secp256k1_memcmp_var(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4) == 0);
secp256k1_scalar_set_b32(&k[0], &secnonce->data[4], NULL);
secp256k1_scalar_set_b32(&k[1], &secnonce->data[36], NULL);
/* We make very sure that the nonce isn't invalidated by checking the values
* in addition to the magic. */
is_zero = secp256k1_scalar_is_zero(&k[0]) & secp256k1_scalar_is_zero(&k[1]);
secp256k1_declassify(ctx, &is_zero, sizeof(is_zero));
ARG_CHECK(!is_zero);
return 1;
}
/* If flag is true, invalidate the secnonce; otherwise leave it. Constant-time. */
static void secp256k1_frost_secnonce_invalidate(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, int flag) {
secp256k1_memczero(secnonce->data, sizeof(secnonce->data), flag);
/* The flag argument is usually classified. So, above code makes the magic
* classified. However, we need the magic to be declassified to be able to
* compare it during secnonce_load. */
secp256k1_declassify(ctx, secnonce->data, sizeof(secp256k1_frost_secnonce_magic));
}
static const unsigned char secp256k1_frost_pubnonce_magic[4] = { 0x8b, 0xcf, 0xe2, 0xc2 };
/* Requires that none of the provided group elements is infinity. Works for both
* frost_pubnonce and frost_aggnonce. */
static void secp256k1_frost_pubnonce_save(secp256k1_frost_pubnonce* nonce, secp256k1_ge* ge) {
int i;
memcpy(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4);
for (i = 0; i < 2; i++) {
secp256k1_point_save_ext(nonce->data + 4+64*i, &ge[i]);
}
}
/* Works for both frost_pubnonce and frost_aggnonce. Returns 1 unless the nonce
* wasn't properly initialized */
static int secp256k1_frost_pubnonce_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_frost_pubnonce* nonce) {
int i;
ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4) == 0);
for (i = 0; i < 2; i++) {
secp256k1_point_load_ext(&ge[i], nonce->data + 4+64*i);
}
return 1;
}
static const unsigned char secp256k1_frost_session_cache_magic[4] = { 0x5c, 0x11, 0xa8, 0x3 };
/* A session consists of
* - 4 byte session cache magic
* - 1 byte the parity of the final nonce
* - 32 byte serialized x-only final nonce
* - 32 byte nonce coefficient b
* - 32 byte signature challenge hash e
* - 32 byte scalar s that is added to the partial signatures of the signers
*/
static void secp256k1_frost_session_save(secp256k1_frost_session *session, const secp256k1_frost_session_internal *session_i) {
unsigned char *ptr = session->data;
memcpy(ptr, secp256k1_frost_session_cache_magic, 4);
ptr += 4;
*ptr = session_i->fin_nonce_parity;
ptr += 1;
memcpy(ptr, session_i->fin_nonce, 32);
ptr += 32;
secp256k1_scalar_get_b32(ptr, &session_i->noncecoef);
ptr += 32;
secp256k1_scalar_get_b32(ptr, &session_i->challenge);
ptr += 32;
secp256k1_scalar_get_b32(ptr, &session_i->s_part);
}
static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_frost_session_internal *session_i, const secp256k1_frost_session *session) {
const unsigned char *ptr = session->data;
ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_session_cache_magic, 4) == 0);
ptr += 4;
session_i->fin_nonce_parity = *ptr;
ptr += 1;
memcpy(session_i->fin_nonce, ptr, 32);
ptr += 32;
secp256k1_scalar_set_b32(&session_i->noncecoef, ptr, NULL);
ptr += 32;
secp256k1_scalar_set_b32(&session_i->challenge, ptr, NULL);
ptr += 32;
secp256k1_scalar_set_b32(&session_i->s_part, ptr, NULL);
return 1;
}
static const unsigned char secp256k1_frost_partial_sig_magic[4] = { 0x8d, 0xd8, 0x31, 0x6e };
static void secp256k1_frost_partial_sig_save(secp256k1_frost_partial_sig* sig, secp256k1_scalar *s) {
memcpy(&sig->data[0], secp256k1_frost_partial_sig_magic, 4);
secp256k1_scalar_get_b32(&sig->data[4], s);
}
static int secp256k1_frost_partial_sig_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_partial_sig* sig) {
int overflow;
ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_frost_partial_sig_magic, 4) == 0);
secp256k1_scalar_set_b32(s, &sig->data[4], &overflow);
/* Parsed signatures can not overflow */
VERIFY_CHECK(!overflow);
return 1;
}
int secp256k1_frost_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_frost_pubnonce* nonce) {
secp256k1_ge ge[2];
int i;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(out66 != NULL);
memset(out66, 0, 66);
ARG_CHECK(nonce != NULL);
if (!secp256k1_frost_pubnonce_load(ctx, ge, nonce)) {
return 0;
}
for (i = 0; i < 2; i++) {
int ret;
size_t size = 33;
ret = secp256k1_eckey_pubkey_serialize(&ge[i], &out66[33*i], &size, 1);
#ifdef VERIFY
/* serialize must succeed because the point was just loaded */
VERIFY_CHECK(ret && size == 33);
#else
(void) ret;
#endif
}
return 1;
}
int secp256k1_frost_pubnonce_parse(const secp256k1_context* ctx, secp256k1_frost_pubnonce* nonce, const unsigned char *in66) {
secp256k1_ge ge[2];
int i;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(nonce != NULL);
ARG_CHECK(in66 != NULL);
for (i = 0; i < 2; i++) {
if (!secp256k1_eckey_pubkey_parse(&ge[i], &in66[33*i], 33)) {
return 0;
}
if (!secp256k1_ge_is_in_correct_subgroup(&ge[i])) {
return 0;
}
}
/* The group elements can not be infinity because they were just parsed */
secp256k1_frost_pubnonce_save(nonce, ge);
return 1;
}
int secp256k1_frost_partial_sig_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_partial_sig* sig) {
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(out32 != NULL);
ARG_CHECK(sig != NULL);
memcpy(out32, &sig->data[4], 32);
return 1;
}
int secp256k1_frost_partial_sig_parse(const secp256k1_context* ctx, secp256k1_frost_partial_sig* sig, const unsigned char *in32) {
secp256k1_scalar tmp;
int overflow;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(sig != NULL);
ARG_CHECK(in32 != NULL);
secp256k1_scalar_set_b32(&tmp, in32, &overflow);
if (overflow) {
return 0;
}
secp256k1_frost_partial_sig_save(sig, &tmp);
return 1;
}
static void secp256k1_nonce_function_frost(secp256k1_scalar *k, const unsigned char *session_id, const unsigned char *msg32, const unsigned char *key32, const unsigned char *pk32, const unsigned char *extra_input32) {
secp256k1_sha256 sha;
unsigned char seed[32];
unsigned char i;
enum { n_extra_in = 4 };
const unsigned char *extra_in[n_extra_in];
/* TODO: this doesn't have the same sidechannel resistance as the BIP340
* nonce function because the seckey feeds directly into SHA. */
/* Subtract one from `sizeof` to avoid hashing the implicit null byte */
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/nonce", sizeof("FROST/nonce") - 1);
secp256k1_sha256_write(&sha, session_id, 32);
extra_in[0] = msg32;
extra_in[1] = key32;
extra_in[2] = pk32;
extra_in[3] = extra_input32;
for (i = 0; i < n_extra_in; i++) {
unsigned char len;
if (extra_in[i] != NULL) {
len = 32;
secp256k1_sha256_write(&sha, &len, 1);
secp256k1_sha256_write(&sha, extra_in[i], 32);
} else {
len = 0;
secp256k1_sha256_write(&sha, &len, 1);
}
}
secp256k1_sha256_finalize(&sha, seed);
for (i = 0; i < 2; i++) {
unsigned char buf[32];
secp256k1_sha256_initialize(&sha);
secp256k1_sha256_write(&sha, seed, 32);
secp256k1_sha256_write(&sha, &i, sizeof(i));
secp256k1_sha256_finalize(&sha, buf);
secp256k1_scalar_set_b32(&k[i], buf, NULL);
}
}
int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, secp256k1_frost_pubnonce *pubnonce, const unsigned char *session_id32, const secp256k1_frost_share *share, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, const unsigned char *extra_input32) {
secp256k1_scalar k[2];
secp256k1_ge nonce_pt[2];
int i;
unsigned char pk_ser[32];
unsigned char *pk_ser_ptr = NULL;
unsigned char sk_ser[32];
unsigned char *sk_ser_ptr = NULL;
int sk_serialize_success;
int ret = 1;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secnonce != NULL);
memset(secnonce, 0, sizeof(*secnonce));
ARG_CHECK(pubnonce != NULL);
memset(pubnonce, 0, sizeof(*pubnonce));
ARG_CHECK(session_id32 != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
if (share == NULL) {
/* Check in constant time that the session_id is not 0 as a
* defense-in-depth measure that may protect against a faulty RNG. */
unsigned char acc = 0;
for (i = 0; i < 32; i++) {
acc |= session_id32[i];
}
ret &= !!acc;
memset(&acc, 0, sizeof(acc));
}
if (share != NULL) {
/* Check that the share is valid to be able to sign for it later. */
secp256k1_scalar sk;
ret &= secp256k1_frost_share_load(ctx, &sk, share);
secp256k1_scalar_clear(&sk);
sk_serialize_success = secp256k1_frost_share_serialize(ctx, sk_ser, share);
sk_ser_ptr = sk_ser;
#ifdef VERIFY
VERIFY_CHECK(sk_serialize_success);
#else
(void) sk_serialize_success;
#endif
}
if (pk != NULL) {
if (!secp256k1_xonly_pubkey_serialize(ctx, pk_ser, pk)) {
return 0;
}
pk_ser_ptr = pk_ser;
}
secp256k1_nonce_function_frost(k, session_id32, msg32, sk_ser_ptr, pk_ser_ptr, extra_input32);
VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[0]));
VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[1]));
VERIFY_CHECK(!secp256k1_scalar_eq(&k[0], &k[1]));
secp256k1_frost_secnonce_save(secnonce, k);
secp256k1_frost_secnonce_invalidate(ctx, secnonce, !ret);
for (i = 0; i < 2; i++) {
secp256k1_gej nonce_ptj;
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj, &k[i]);
secp256k1_ge_set_gej(&nonce_pt[i], &nonce_ptj);
secp256k1_declassify(ctx, &nonce_pt[i], sizeof(nonce_pt));
secp256k1_scalar_clear(&k[i]);
}
/* nonce_pt won't be infinity because k != 0 with overwhelming probability */
secp256k1_frost_pubnonce_save(pubnonce, nonce_pt);
return ret;
}
static int secp256k1_frost_sum_nonces(const secp256k1_context* ctx, secp256k1_gej *summed_nonces, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces) {
size_t i;
int j;
secp256k1_gej_set_infinity(&summed_nonces[0]);
secp256k1_gej_set_infinity(&summed_nonces[1]);
for (i = 0; i < n_pubnonces; i++) {
secp256k1_ge nonce_pt[2];
if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonces[i])) {
return 0;
}
for (j = 0; j < 2; j++) {
secp256k1_gej_add_ge_var(&summed_nonces[j], &summed_nonces[j], &nonce_pt[j], NULL);
}
}
return 1;
}
/* TODO: consider updating to frost-08 to address maleability at the cost of performance */
/* See https://github.com/cfrg/draft-irtf-cfrg-frost/pull/217 */
static int secp256k1_frost_compute_noncehash(const secp256k1_context* ctx, unsigned char *noncehash, const unsigned char *msg, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *pk32, const size_t *ids) {
unsigned char buf[66];
secp256k1_sha256 sha;
size_t i;
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/noncecoef", sizeof("FROST/noncecoef") - 1);
for (i = 0; i < n_pubnonces; i++) {
secp256k1_scalar idx;
secp256k1_scalar_set_int(&idx, ids[i]);
secp256k1_scalar_get_b32(buf, &idx);
secp256k1_sha256_write(&sha, buf, 32);
if (!secp256k1_frost_pubnonce_serialize(ctx, buf, pubnonces[i])) {
return 0;
}
secp256k1_sha256_write(&sha, buf, sizeof(buf));
}
secp256k1_sha256_write(&sha, pk32, 32);
secp256k1_sha256_write(&sha, msg, 32);
secp256k1_sha256_finalize(&sha, noncehash);
return 1;
}
static int secp256k1_frost_nonce_process_internal(const secp256k1_context* ctx, int *fin_nonce_parity, unsigned char *fin_nonce, secp256k1_scalar *b, secp256k1_gej *aggnoncej, const unsigned char *msg, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *pk32, const size_t *ids) {
unsigned char noncehash[32];
secp256k1_ge fin_nonce_pt;
secp256k1_gej fin_nonce_ptj;
secp256k1_ge aggnonce[2];
secp256k1_ge_set_gej(&aggnonce[0], &aggnoncej[0]);
secp256k1_ge_set_gej(&aggnonce[1], &aggnoncej[1]);
if (!secp256k1_frost_compute_noncehash(ctx, noncehash, msg, pubnonces, n_pubnonces, pk32, ids)) {
return 0;
}
/* fin_nonce = aggnonce[0] + b*aggnonce[1] */
secp256k1_scalar_set_b32(b, noncehash, NULL);
secp256k1_ecmult(&fin_nonce_ptj, &aggnoncej[1], b, NULL);
secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &aggnonce[0], NULL);
secp256k1_ge_set_gej(&fin_nonce_pt, &fin_nonce_ptj);
if (secp256k1_ge_is_infinity(&fin_nonce_pt)) {
/* unreachable with overwhelming probability */
return 0;
}
secp256k1_fe_normalize_var(&fin_nonce_pt.x);
secp256k1_fe_get_b32(fin_nonce, &fin_nonce_pt.x);
secp256k1_fe_normalize_var(&fin_nonce_pt.y);
*fin_nonce_parity = secp256k1_fe_is_odd(&fin_nonce_pt.y);
return 1;
}
static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const size_t *ids, size_t n_participants, size_t my_id) {
size_t i;
secp256k1_scalar num;
secp256k1_scalar den;
secp256k1_scalar party_idx;
secp256k1_scalar_set_int(&num, 1);
secp256k1_scalar_set_int(&den, 1);
secp256k1_scalar_set_int(&party_idx, my_id);
for (i = 0; i < n_participants; i++) {
secp256k1_scalar mul;
secp256k1_scalar_set_int(&mul, ids[i]);
if (secp256k1_scalar_eq(&mul, &party_idx)) {
continue;
}
secp256k1_scalar_negate(&mul, &mul);
secp256k1_scalar_mul(&num, &num, &mul);
secp256k1_scalar_add(&mul, &mul, &party_idx);
secp256k1_scalar_mul(&den, &den, &mul);
}
secp256k1_scalar_inverse_var(&den, &den);
secp256k1_scalar_mul(r, &num, &den);
return 1;
}
int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_session *session, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, size_t my_id, const size_t *ids, const secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_pubkey *adaptor) {
secp256k1_ge aggnonce_pt[2];
secp256k1_gej aggnonce_ptj[2];
unsigned char fin_nonce[32];
secp256k1_frost_session_internal session_i = { 0 };
unsigned char pk32[32];
size_t i;
secp256k1_scalar l;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(session != NULL);
ARG_CHECK(msg32 != NULL);
ARG_CHECK(pubnonces != NULL);
ARG_CHECK(ids != NULL);
ARG_CHECK(n_pubnonces > 1);
ARG_CHECK(my_id != 0);
for (i = 0; i < n_pubnonces; i++) {
ARG_CHECK(ids[i] != 0);
}
if (!secp256k1_xonly_pubkey_serialize(ctx, pk32, pk)) {
return 0;
}
if (!secp256k1_frost_sum_nonces(ctx, aggnonce_ptj, pubnonces, n_pubnonces)) {
return 0;
}
for (i = 0; i < 2; i++) {
if (secp256k1_gej_is_infinity(&aggnonce_ptj[i])) {
/* There must be at least one dishonest signer. If we would return 0
here, we will never be able to determine who it is. Therefore, we
should continue such that the culprit is revealed when collecting
and verifying partial signatures.
However, dealing with the point at infinity (loading,
de-/serializing) would require a lot of extra code complexity.
Instead, we set the aggregate nonce to some arbitrary point (the
generator). This is secure, because it only restricts the
abilities of the attacker: an attacker that forces the sum of
nonces to be infinity by sending some maliciously generated nonce
pairs can be turned into an attacker that forces the sum to be
the generator (by simply adding the generator to one of the
malicious nonces), and this does not change the winning condition
of the EUF-CMA game. */
aggnonce_pt[i] = secp256k1_ge_const_g;
} else {
secp256k1_ge_set_gej(&aggnonce_pt[i], &aggnonce_ptj[i]);
}
}
/* Add public adaptor to nonce */
if (adaptor != NULL) {
secp256k1_ge adaptorp;
if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) {
return 0;
}
secp256k1_gej_add_ge_var(&aggnonce_ptj[0], &aggnonce_ptj[0], &adaptorp, NULL);
}
if (!secp256k1_frost_nonce_process_internal(ctx, &session_i.fin_nonce_parity, fin_nonce, &session_i.noncecoef, aggnonce_ptj, msg32, pubnonces, n_pubnonces, pk32, ids)) {
return 0;
}
secp256k1_schnorrsig_challenge(&session_i.challenge, fin_nonce, msg32, 32, pk32);
/* If there is a tweak then set `challenge` times `tweak` to the `s`-part.*/
secp256k1_scalar_set_int(&session_i.s_part, 0);
if (tweak_cache != NULL) {
secp256k1_tweak_cache_internal cache_i;
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
return 0;
}
if (!secp256k1_scalar_is_zero(&cache_i.tweak)) {
secp256k1_scalar e_tmp;
secp256k1_scalar_mul(&e_tmp, &session_i.challenge, &cache_i.tweak);
if (secp256k1_fe_is_odd(&cache_i.pk.y)) {
secp256k1_scalar_negate(&e_tmp, &e_tmp);
}
secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &e_tmp);
}
}
/* Update the challenge by multiplying the Lagrange coefficient to prepare
* for signing. */
if (!secp256k1_frost_lagrange_coefficient(&l, ids, n_pubnonces, my_id)) {
return 0;
}
secp256k1_scalar_mul(&session_i.challenge, &session_i.challenge, &l);
memcpy(session_i.fin_nonce, fin_nonce, sizeof(session_i.fin_nonce));
secp256k1_frost_session_save(session, &session_i);
return 1;
}
void secp256k1_frost_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) {
secp256k1_scalar_clear(sk);
secp256k1_scalar_clear(&k[0]);
secp256k1_scalar_clear(&k[1]);
}
int secp256k1_frost_partial_sign(const secp256k1_context* ctx, secp256k1_frost_partial_sig *partial_sig, secp256k1_frost_secnonce *secnonce, const secp256k1_frost_share *share, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) {
secp256k1_scalar sk;
secp256k1_scalar k[2];
secp256k1_scalar s;
secp256k1_frost_session_internal session_i;
int ret;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secnonce != NULL);
/* Fails if the magic doesn't match */
ret = secp256k1_frost_secnonce_load(ctx, k, secnonce);
/* Set nonce to zero to avoid nonce reuse. This will cause subsequent calls
* of this function to fail */
memset(secnonce, 0, sizeof(*secnonce));
if (!ret) {
secp256k1_frost_partial_sign_clear(&sk, k);
return 0;
}
ARG_CHECK(partial_sig != NULL);
ARG_CHECK(share != NULL);
ARG_CHECK(session != NULL);
if (!secp256k1_frost_share_load(ctx, &sk, share)) {
secp256k1_frost_partial_sign_clear(&sk, k);
return 0;
}
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
secp256k1_frost_partial_sign_clear(&sk, k);
return 0;
}
if (tweak_cache != NULL) {
secp256k1_tweak_cache_internal cache_i;
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
secp256k1_frost_partial_sign_clear(&sk, k);
return 0;
}
if (secp256k1_fe_is_odd(&cache_i.pk.y) != cache_i.parity_acc) {
secp256k1_scalar_negate(&sk, &sk);
}
}
if (session_i.fin_nonce_parity) {
secp256k1_scalar_negate(&k[0], &k[0]);
secp256k1_scalar_negate(&k[1], &k[1]);
}
/* Sign */
secp256k1_scalar_mul(&s, &session_i.challenge, &sk);
secp256k1_scalar_mul(&k[1], &session_i.noncecoef, &k[1]);
secp256k1_scalar_add(&k[0], &k[0], &k[1]);
secp256k1_scalar_add(&s, &s, &k[0]);
secp256k1_frost_partial_sig_save(partial_sig, &s);
secp256k1_frost_partial_sign_clear(&sk, k);
return 1;
}
int secp256k1_frost_partial_sig_verify(const secp256k1_context* ctx, const secp256k1_frost_partial_sig *partial_sig, const secp256k1_frost_pubnonce *pubnonce, const secp256k1_pubkey *pubshare, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) {
secp256k1_frost_session_internal session_i;
secp256k1_scalar e, s;
secp256k1_gej pkj;
secp256k1_ge nonce_pt[2];
secp256k1_gej rj;
secp256k1_gej tmp;
secp256k1_ge pkp;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(partial_sig != NULL);
ARG_CHECK(pubnonce != NULL);
ARG_CHECK(pubshare != NULL);
ARG_CHECK(session != NULL);
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
return 0;
}
/* Compute "effective" nonce rj = aggnonce[0] + b*aggnonce[1] */
/* TODO: use multiexp to compute -s*G + e*pubshare + aggnonce[0] + b*aggnonce[1] */
if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonce)) {
return 0;
}
secp256k1_gej_set_ge(&rj, &nonce_pt[1]);
secp256k1_ecmult(&rj, &rj, &session_i.noncecoef, NULL);
secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pt[0], NULL);
if (!secp256k1_pubkey_load(ctx, &pkp, pubshare)) {
return 0;
}
secp256k1_scalar_set_int(&e, 1);
if (tweak_cache != NULL) {
secp256k1_tweak_cache_internal cache_i;
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
return 0;
}
if (secp256k1_fe_is_odd(&cache_i.pk.y)
!= cache_i.parity_acc) {
secp256k1_scalar_negate(&e, &e);
}
}
secp256k1_scalar_mul(&e, &e, &session_i.challenge);
if (!secp256k1_frost_partial_sig_load(ctx, &s, partial_sig)) {
return 0;
}
/* Compute -s*G + e*pkj + rj (e already includes the lagrange coefficient l) */
secp256k1_scalar_negate(&s, &s);
secp256k1_gej_set_ge(&pkj, &pkp);
secp256k1_ecmult(&tmp, &pkj, &e, &s);
if (session_i.fin_nonce_parity) {
secp256k1_gej_neg(&rj, &rj);
}
secp256k1_gej_add_var(&tmp, &tmp, &rj, NULL);
return secp256k1_gej_is_infinity(&tmp);
}
int secp256k1_frost_partial_sig_agg(const secp256k1_context* ctx, unsigned char *sig64, const secp256k1_frost_session *session, const secp256k1_frost_partial_sig * const* partial_sigs, size_t n_sigs) {
size_t i;
secp256k1_frost_session_internal session_i;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(sig64 != NULL);
ARG_CHECK(session != NULL);
ARG_CHECK(partial_sigs != NULL);
ARG_CHECK(n_sigs > 0);
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
return 0;
}
for (i = 0; i < n_sigs; i++) {
secp256k1_scalar term;
if (!secp256k1_frost_partial_sig_load(ctx, &term, partial_sigs[i])) {
return 0;
}
secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &term);
}
secp256k1_scalar_get_b32(&sig64[32], &session_i.s_part);
memcpy(&sig64[0], session_i.fin_nonce, 32);
return 1;
}
#endif

View File

@ -0,0 +1,738 @@
/***********************************************************************
* Copyright (c) 2022, 2023 Jesse Posner *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
***********************************************************************/
#ifndef SECP256K1_MODULE_FROST_TESTS_IMPL_H
#define SECP256K1_MODULE_FROST_TESTS_IMPL_H
#include <stdlib.h>
#include <string.h>
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_extrakeys.h"
#include "../../../include/secp256k1_frost.h"
#include "session.h"
#include "keygen.h"
#include "../../scalar.h"
#include "../../scratch.h"
#include "../../field.h"
#include "../../group.h"
#include "../../hash.h"
#include "../../util.h"
/* Simple (non-adaptor, non-tweaked) 3-of-5 FROST aggregate, sign, verify
* test. */
void frost_simple_test(void) {
secp256k1_frost_pubnonce pubnonce[5];
const secp256k1_frost_pubnonce *pubnonce_ptr[5];
unsigned char msg[32];
secp256k1_xonly_pubkey pk;
unsigned char seed[32];
secp256k1_frost_share shares[5];
secp256k1_frost_secnonce secnonce[5];
secp256k1_pubkey pubshares[5];
secp256k1_frost_partial_sig partial_sig[5];
const secp256k1_frost_partial_sig *partial_sig_ptr[5];
unsigned char final_sig[64];
secp256k1_frost_session session;
int i;
size_t ids[5];
secp256k1_testrand256(seed);
for (i = 0; i < 5; i++) {
pubnonce_ptr[i] = &pubnonce[i];
partial_sig_ptr[i] = &partial_sig[i];
ids[i] = i + 1;
}
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 5) == 1);
secp256k1_testrand256(msg);
for (i = 0; i < 3; i++) {
unsigned char session_id[32];
secp256k1_testrand256(session_id);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id, &shares[i], NULL, NULL, NULL) == 1);
}
for (i = 0; i < 3; i++) {
CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, &pk, ids[i], ids, NULL, NULL) == 1);
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &shares[i], &session, NULL) == 1);
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], &pubshares[i], &session, NULL) == 1);
}
CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1);
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk) == 1);
}
void frost_pubnonce_summing_to_inf(secp256k1_frost_pubnonce *pubnonce) {
secp256k1_ge ge[2];
int i;
secp256k1_gej summed_nonces[2];
const secp256k1_frost_pubnonce *pubnonce_ptr[2];
ge[0] = secp256k1_ge_const_g;
ge[1] = secp256k1_ge_const_g;
for (i = 0; i < 2; i++) {
secp256k1_frost_pubnonce_save(&pubnonce[i], ge);
pubnonce_ptr[i] = &pubnonce[i];
secp256k1_ge_neg(&ge[0], &ge[0]);
secp256k1_ge_neg(&ge[1], &ge[1]);
}
secp256k1_frost_sum_nonces(CTX, summed_nonces, pubnonce_ptr, 2);
CHECK(secp256k1_gej_is_infinity(&summed_nonces[0]));
CHECK(secp256k1_gej_is_infinity(&summed_nonces[1]));
}
int frost_memcmp_and_randomize(unsigned char *value, const unsigned char *expected, size_t len) {
int ret;
size_t i;
ret = secp256k1_memcmp_var(value, expected, len);
for (i = 0; i < len; i++) {
value[i] = secp256k1_testrand_bits(8);
}
return ret;
}
void frost_api_tests(void) {
secp256k1_frost_partial_sig partial_sig[5];
const secp256k1_frost_partial_sig *partial_sig_ptr[5];
secp256k1_frost_partial_sig invalid_partial_sig;
const secp256k1_frost_partial_sig *invalid_partial_sig_ptr[5];
unsigned char final_sig[64];
unsigned char pre_sig[64];
unsigned char buf[32];
/* unsigned char sk[5][32]; */
unsigned char max64[64];
unsigned char zeros68[68] = { 0 };
unsigned char session_id[5][32];
unsigned char seed[32];
secp256k1_frost_secnonce secnonce[5];
secp256k1_frost_secnonce secnonce_tmp;
secp256k1_frost_secnonce invalid_secnonce;
secp256k1_frost_pubnonce pubnonce[5];
const secp256k1_frost_pubnonce *pubnonce_ptr[5];
unsigned char pubnonce_ser[66];
secp256k1_frost_pubnonce inf_pubnonce[5];
secp256k1_frost_pubnonce invalid_pubnonce;
const secp256k1_frost_pubnonce *invalid_pubnonce_ptr[5];
unsigned char msg[32];
secp256k1_xonly_pubkey pk;
secp256k1_pubkey full_pk;
secp256k1_frost_tweak_cache tweak_cache;
secp256k1_frost_tweak_cache invalid_tweak_cache;
secp256k1_frost_session session[5];
secp256k1_frost_session invalid_session;
secp256k1_xonly_pubkey invalid_pk;
unsigned char tweak[32];
int nonce_parity;
unsigned char sec_adaptor[32];
unsigned char sec_adaptor1[32];
secp256k1_pubkey adaptor;
secp256k1_pubkey invalid_vss_pk;
secp256k1_frost_share invalid_share;
secp256k1_frost_share shares[5];
secp256k1_pubkey pubshares[5];
int i;
size_t ids[5];
size_t invalid_ids[5];
/** setup **/
memset(max64, 0xff, sizeof(max64));
memset(&invalid_share, 0xff, sizeof(invalid_share));
/* Simulate structs being uninitialized by setting it to 0s. We don't want
* to produce undefined behavior by actually providing uninitialized
* structs. */
memset(&invalid_pk, 0, sizeof(invalid_pk));
memset(&invalid_secnonce, 0, sizeof(invalid_secnonce));
memset(&invalid_partial_sig, 0, sizeof(invalid_partial_sig));
memset(&invalid_pubnonce, 0, sizeof(invalid_pubnonce));
memset(&invalid_vss_pk, 0, sizeof(invalid_vss_pk));
memset(&invalid_tweak_cache, 0, sizeof(invalid_tweak_cache));
memset(&invalid_session, 0, sizeof(invalid_session));
frost_pubnonce_summing_to_inf(inf_pubnonce);
secp256k1_testrand256(sec_adaptor);
secp256k1_testrand256(msg);
secp256k1_testrand256(tweak);
secp256k1_testrand256(seed);
CHECK(secp256k1_ec_pubkey_create(CTX, &adaptor, sec_adaptor) == 1);
for (i = 0; i < 5; i++) {
pubnonce_ptr[i] = &pubnonce[i];
partial_sig_ptr[i] = &partial_sig[i];
invalid_partial_sig_ptr[i] = &partial_sig[i];
ids[i] = i + 1;
invalid_ids[i] = i + 1;
secp256k1_testrand256(session_id[i]);
}
invalid_pubnonce_ptr[0] = &invalid_pubnonce;
invalid_partial_sig_ptr[0] = &invalid_partial_sig;
invalid_ids[2] = 0;
/** main test body **/
/** Key generation **/
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 5) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, NULL, pubshares, &pk, seed, 3, 5));
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, NULL, &pk, seed, 3, 5));
for (i = 0; i < 5; i++) {
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
}
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, NULL, seed, 3, 5));
for (i = 0; i < 5; i++) {
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
}
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, NULL, 3, 5));
for (i = 0; i < 5; i++) {
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
}
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 0, 5));
for (i = 0; i < 5; i++) {
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
}
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 0));
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 2));
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 5) == 1);
/* pubkey_get */
CHECK(secp256k1_frost_pubkey_get(CTX, &full_pk, &pk) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, NULL, &pk));
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, &full_pk, NULL));
CHECK(secp256k1_memcmp_var(&full_pk, zeros68, sizeof(full_pk)) == 0);
/** Tweaking **/
/* pubkey_tweak */
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &pk) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, NULL, &pk));
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, NULL));
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &invalid_pk));
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &pk) == 1);
/* tweak_add */
{
int (*tweak_func[2]) (const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32);
tweak_func[0] = secp256k1_frost_pubkey_ec_tweak_add;
tweak_func[1] = secp256k1_frost_pubkey_xonly_tweak_add;
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &pk) == 1);
for (i = 0; i < 2; i++) {
secp256k1_pubkey tmp_output_pk;
secp256k1_frost_tweak_cache tmp_tweak_cache = tweak_cache;
CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1);
/* Reset tweak_cache */
tmp_tweak_cache = tweak_cache;
CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1);
tmp_tweak_cache = tweak_cache;
CHECK((*tweak_func[i])(CTX, NULL, &tmp_tweak_cache, tweak) == 1);
tmp_tweak_cache = tweak_cache;
CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, NULL, tweak));
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
tmp_tweak_cache = tweak_cache;
CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, NULL));
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
tmp_tweak_cache = tweak_cache;
CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, max64) == 0);
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
tmp_tweak_cache = tweak_cache;
/* Uninitialized tweak_cache */
CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &invalid_tweak_cache, tweak));
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
}
}
/** Session creation **/
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &pk, max64) == 1);
CHECK_ILLEGAL(STATIC_CTX, secp256k1_frost_nonce_gen(STATIC_CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &pk, max64));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, NULL, &pubnonce[0], session_id[0], &shares[0], msg, &pk, max64));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], NULL, session_id[0], &shares[0], msg, &pk, max64));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, &shares[0], msg, &pk, max64));
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
/* no seckey and session_id is 0 */
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], zeros68, NULL, msg, &pk, max64) == 0);
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
/* session_id 0 is fine when a seckey is provided */
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], zeros68, &shares[0], msg, &pk, max64) == 1);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, msg, &pk, max64) == 1);
/* invalid share */
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &invalid_share, msg, &pk, max64));
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], NULL, &pk, max64) == 1);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, NULL, max64) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &invalid_pk, max64));
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &pk, NULL) == 1);
/* Every in-argument except session_id can be NULL */
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, NULL, NULL, NULL) == 1);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[1], &pubnonce[1], session_id[1], &shares[1], NULL, NULL, NULL) == 1);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[2], &pubnonce[2], session_id[2], &shares[2], NULL, NULL, NULL) == 1);
/** Serialize and parse public nonces **/
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, NULL, &pubnonce[0]));
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, NULL));
CHECK(frost_memcmp_and_randomize(pubnonce_ser, zeros68, sizeof(pubnonce_ser)) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &invalid_pubnonce));
CHECK(frost_memcmp_and_randomize(pubnonce_ser, zeros68, sizeof(pubnonce_ser)) == 0);
CHECK(secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce[0]) == 1);
CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], pubnonce_ser) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_parse(CTX, NULL, pubnonce_ser));
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], NULL));
CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], zeros68) == 0);
CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], pubnonce_ser) == 1);
{
/* Check that serialize and parse results in the same value */
secp256k1_frost_pubnonce tmp;
CHECK(secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce[0]) == 1);
CHECK(secp256k1_frost_pubnonce_parse(CTX, &tmp, pubnonce_ser) == 1);
CHECK(secp256k1_memcmp_var(&tmp, &pubnonce[0], sizeof(tmp)) == 0);
}
/** Process nonces **/
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, NULL, pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], NULL, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 0, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], invalid_pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, NULL, &pk, ids[0], ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, NULL, ids[0], ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, 0, ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], invalid_ids, &tweak_cache, &adaptor));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], NULL, &tweak_cache, &adaptor));
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, NULL, &adaptor) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &invalid_tweak_cache, &adaptor));
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, NULL) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, (secp256k1_pubkey *)&invalid_pk));
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor) == 1);
CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, &pk, ids[1], ids, &tweak_cache, &adaptor) == 1);
CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, &pk, ids[2], ids, &tweak_cache, &adaptor) == 1);
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &tweak_cache) == 1);
/* The secnonce is set to 0 and subsequent signing attempts fail */
CHECK(secp256k1_memcmp_var(&secnonce_tmp, zeros68, sizeof(secnonce_tmp)) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &tweak_cache));
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, NULL, &secnonce_tmp, &shares[0], &session[0], &tweak_cache));
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], NULL, &shares[0], &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &invalid_secnonce, &shares[0], &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, NULL, &session[0], &tweak_cache));
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &invalid_share, &session[0], &tweak_cache));
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], NULL, &tweak_cache));
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &invalid_session, &tweak_cache));
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], NULL) == 1);
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &invalid_tweak_cache));
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], &shares[0], &session[0], &tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], &shares[1], &session[1], &tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], &shares[2], &session[2], &tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_serialize(CTX, buf, &partial_sig[0]) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_serialize(CTX, NULL, &partial_sig[0]));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_serialize(CTX, buf, NULL));
CHECK(secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], buf) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_parse(CTX, NULL, buf));
CHECK(secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], max64) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], NULL));
{
/* Check that serialize and parse results in the same value */
secp256k1_frost_partial_sig tmp;
CHECK(secp256k1_frost_partial_sig_serialize(CTX, buf, &partial_sig[0]) == 1);
CHECK(secp256k1_frost_partial_sig_parse(CTX, &tmp, buf) == 1);
CHECK(secp256k1_memcmp_var(&tmp, &partial_sig[0], sizeof(tmp)) == 0);
}
/** Partial signature verification */
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], &tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[0], &pubshares[0], &session[0], &tweak_cache) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, NULL, &pubnonce[0], &pubshares[0], &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &invalid_partial_sig, &pubnonce[0], &pubshares[0], &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], NULL, &pubshares[0], &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &invalid_pubnonce, &pubshares[0], &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], NULL, &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &invalid_vss_pk, &session[0], &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], NULL, &tweak_cache));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &invalid_session, &tweak_cache));
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], NULL) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], &invalid_tweak_cache));
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], &tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], &pubshares[1], &session[1], &tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], &pubshares[2], &session[2], &tweak_cache) == 1);
/** Signature aggregation and verification */
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 3) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, NULL, &session[0], partial_sig_ptr, 3));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, NULL, partial_sig_ptr, 3));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &invalid_session, partial_sig_ptr, 3));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], NULL, 3));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], invalid_partial_sig_ptr, 3));
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 0));
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 1) == 1);
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[1], partial_sig_ptr, 2) == 1);
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[2], partial_sig_ptr, 3) == 1);
/** Adaptor signature verification */
CHECK(secp256k1_frost_nonce_parity(CTX, &nonce_parity, &session[0]) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, NULL, &session[0]));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, &nonce_parity, NULL));
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, &nonce_parity, &invalid_session));
CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1);
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, NULL, pre_sig, sec_adaptor, 0));
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, NULL, sec_adaptor, 0));
CHECK(secp256k1_frost_adapt(CTX, final_sig, max64, sec_adaptor, 0) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, pre_sig, NULL, 0));
CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, max64, 0) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, 2));
/* sig and pre_sig argument point to the same location */
memcpy(final_sig, pre_sig, sizeof(final_sig));
CHECK(secp256k1_frost_adapt(CTX, final_sig, final_sig, sec_adaptor, nonce_parity) == 1);
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk) == 1);
CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1);
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk) == 1);
/** Secret adaptor can be extracted from signature */
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, nonce_parity) == 1);
CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) == 0);
/* wrong nonce parity */
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, !nonce_parity) == 1);
CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) != 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, NULL, final_sig, pre_sig, 0));
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, NULL, pre_sig, 0));
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, max64, pre_sig, 0) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, NULL, 0));
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, max64, 0) == 0);
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, 2));
}
void frost_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes) {
secp256k1_scalar k1[2], k2[2];
secp256k1_nonce_function_frost(k1, args[0], args[1], args[2], args[3], args[4]);
secp256k1_testrand_flip(args[n_flip], n_bytes);
secp256k1_nonce_function_frost(k2, args[0], args[1], args[2], args[3], args[4]);
CHECK(secp256k1_scalar_eq(&k1[0], &k2[0]) == 0);
CHECK(secp256k1_scalar_eq(&k1[1], &k2[1]) == 0);
}
void frost_nonce_test(void) {
unsigned char *args[5];
unsigned char session_id[32];
unsigned char sk[32];
unsigned char msg[32];
unsigned char agg_pk[32];
unsigned char extra_input[32];
int i, j;
secp256k1_scalar k[5][2];
secp256k1_testrand_bytes_test(session_id, sizeof(session_id));
secp256k1_testrand_bytes_test(sk, sizeof(sk));
secp256k1_testrand_bytes_test(msg, sizeof(msg));
secp256k1_testrand_bytes_test(agg_pk, sizeof(agg_pk));
secp256k1_testrand_bytes_test(extra_input, sizeof(extra_input));
/* Check that a bitflip in an argument results in different nonces. */
args[0] = session_id;
args[1] = msg;
args[2] = sk;
args[3] = agg_pk;
args[4] = extra_input;
for (i = 0; i < COUNT; i++) {
frost_nonce_bitflip(args, 0, sizeof(session_id));
frost_nonce_bitflip(args, 1, sizeof(msg));
frost_nonce_bitflip(args, 2, sizeof(sk));
frost_nonce_bitflip(args, 3, sizeof(agg_pk));
frost_nonce_bitflip(args, 4, sizeof(extra_input));
}
/* Check that if any argument is NULL, a different nonce is produced than if
* any other argument is NULL. */
memcpy(msg, session_id, sizeof(msg));
memcpy(sk, session_id, sizeof(sk));
memcpy(agg_pk, session_id, sizeof(agg_pk));
memcpy(extra_input, session_id, sizeof(extra_input));
secp256k1_nonce_function_frost(k[0], args[0], args[1], args[2], args[3], args[4]);
secp256k1_nonce_function_frost(k[1], args[0], NULL, args[2], args[3], args[4]);
secp256k1_nonce_function_frost(k[2], args[0], args[1], NULL, args[3], args[4]);
secp256k1_nonce_function_frost(k[3], args[0], args[1], args[2], NULL, args[4]);
secp256k1_nonce_function_frost(k[4], args[0], args[1], args[2], args[3], NULL);
for (i = 0; i < 4; i++) {
for (j = i+1; j < 5; j++) {
CHECK(secp256k1_scalar_eq(&k[i][0], &k[j][0]) == 0);
CHECK(secp256k1_scalar_eq(&k[i][1], &k[j][1]) == 0);
}
}
}
/* Attempts to create a signature for the aggregate public key using given secret
* keys and tweak_cache. */
void frost_tweak_test_helper(const secp256k1_xonly_pubkey* pk, const secp256k1_frost_share *sr0, const secp256k1_frost_share *sr1, const secp256k1_frost_share *sr2, secp256k1_frost_tweak_cache *tweak_cache, const size_t *ids, const secp256k1_pubkey *sr_pk0, const secp256k1_pubkey *sr_pk1, const secp256k1_pubkey *sr_pk2) {
unsigned char session_id[3][32];
unsigned char msg[32];
secp256k1_frost_secnonce secnonce[3];
secp256k1_frost_pubnonce pubnonce[3];
const secp256k1_frost_pubnonce *pubnonce_ptr[3];
secp256k1_frost_session session[5];
secp256k1_frost_partial_sig partial_sig[3];
const secp256k1_frost_partial_sig *partial_sig_ptr[3];
unsigned char final_sig[64];
int i;
for (i = 0; i < 3; i++) {
pubnonce_ptr[i] = &pubnonce[i];
partial_sig_ptr[i] = &partial_sig[i];
secp256k1_testrand256(session_id[i]);
}
secp256k1_testrand256(msg);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], sr0, NULL, NULL, NULL) == 1);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[1], &pubnonce[1], session_id[1], sr1, NULL, NULL, NULL) == 1);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[2], &pubnonce[2], session_id[2], sr2, NULL, NULL, NULL) == 1);
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, pk, ids[0], ids, tweak_cache, NULL) == 1);
CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, pk, ids[1], ids, tweak_cache, NULL) == 1);
CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, pk, ids[2], ids, tweak_cache, NULL) == 1);
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], sr0, &session[0], tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], sr1, &session[1], tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], sr2, &session[2], tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], sr_pk0, &session[0], tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], sr_pk1, &session[1], tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], sr_pk2, &session[2], tweak_cache) == 1);
CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session[0], partial_sig_ptr, 3) == 1);
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), pk) == 1);
}
/* Create aggregate public key P[0], tweak multiple times (using xonly and
* ordinary tweaking) and test signing. */
void frost_tweak_test(void) {
secp256k1_pubkey pubshares[5];
secp256k1_frost_tweak_cache tweak_cache;
enum { N_TWEAKS = 8 };
secp256k1_pubkey P[N_TWEAKS + 1];
secp256k1_xonly_pubkey P_xonly[N_TWEAKS + 1];
unsigned char seed[32];
secp256k1_frost_share shares[5];
int i;
size_t ids[5];
secp256k1_testrand256(seed);
/* Key Setup */
for (i = 0; i < 5; i++) {
ids[i] = i + 1;
}
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &P_xonly[0], seed, 3, 5) == 1);
frost_tweak_test_helper(&P_xonly[0], &shares[0], &shares[1], &shares[2], NULL, ids, &pubshares[0], &pubshares[1], &pubshares[2]);
CHECK(secp256k1_frost_pubkey_get(CTX, &P[0], &P_xonly[0]));
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &P_xonly[0]) == 1);
/* Compute Pi = f(Pj) + tweaki*G where where j = i-1 and try signing for */
/* that key. If xonly is set to true, the function f is normalizes the input */
/* point to have an even X-coordinate ("xonly-tweaking"). */
/* Otherwise, the function f is the identity function. */
for (i = 1; i <= N_TWEAKS; i++) {
unsigned char tweak[32];
int P_parity;
int xonly = secp256k1_testrand_bits(1);
secp256k1_testrand256(tweak);
if (xonly) {
CHECK(secp256k1_frost_pubkey_xonly_tweak_add(CTX, &P[i], &tweak_cache, tweak) == 1);
} else {
CHECK(secp256k1_frost_pubkey_ec_tweak_add(CTX, &P[i], &tweak_cache, tweak) == 1);
}
CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &P_xonly[i], &P_parity, &P[i]));
/* Check that frost_pubkey_tweak_add produces same result as */
/* xonly_pubkey_tweak_add or ec_pubkey_tweak_add. */
if (xonly) {
unsigned char P_serialized[32];
CHECK(secp256k1_xonly_pubkey_serialize(CTX, P_serialized, &P_xonly[i]));
CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, P_serialized, P_parity, &P_xonly[i-1], tweak) == 1);
} else {
secp256k1_pubkey tmp_key = P[i-1];
CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &tmp_key, tweak));
CHECK(secp256k1_memcmp_var(&tmp_key, &P[i], sizeof(tmp_key)) == 0);
}
/* Test signing for P[i] */
frost_tweak_test_helper(&P_xonly[i], &shares[0], &shares[1], &shares[2], &tweak_cache, ids, &pubshares[0], &pubshares[1], &pubshares[2]);
}
}
/* Performs a FROST DKG */
void frost_dkg_test_helper(secp256k1_frost_share *shares, secp256k1_xonly_pubkey *pk) {
unsigned char seed[32];
secp256k1_pubkey pubshares[5];
secp256k1_testrand256(seed);
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, pk, seed, 3, 5) == 1);
}
/* Signs a message with a FROST keypair */
int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_share *share, const secp256k1_xonly_pubkey *pk, const unsigned char *msg, const secp256k1_pubkey *adaptor) {
unsigned char session_id[3][32];
secp256k1_frost_secnonce secnonce[3];
secp256k1_frost_pubnonce pubnonce[3];
const secp256k1_frost_pubnonce *pubnonce_ptr[3];
secp256k1_frost_partial_sig partial_sig[5];
const secp256k1_frost_partial_sig *partial_sig_ptr[5];
secp256k1_frost_session session;
int i;
int nonce_parity;
secp256k1_frost_session_internal session_i;
size_t ids[5];
for (i = 0; i < 3; i++) {
pubnonce_ptr[i] = &pubnonce[i];
partial_sig_ptr[i] = &partial_sig[i];
ids[i] = i + 1;
}
for (i = 0; i < 3; i++) {
secp256k1_testrand256(session_id[i]);
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id[i], &share[i], NULL, NULL, NULL) == 1);
}
for (i = 0; i < 3; i++) {
CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, pk, i + 1, ids, NULL, adaptor) == 1);
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &share[i], &session, NULL) == 1);
}
CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1);
CHECK(secp256k1_frost_nonce_parity(CTX, &nonce_parity, &session));
secp256k1_frost_session_load(CTX, &session_i, &session);
return nonce_parity;
}
void frost_rand_scalar(secp256k1_scalar *scalar) {
unsigned char buf32[32];
secp256k1_testrand256(buf32);
secp256k1_scalar_set_b32(scalar, buf32, NULL);
}
void frost_multi_hop_lock_tests(void) {
secp256k1_frost_share share_a[5];
secp256k1_frost_share share_b[5];
secp256k1_xonly_pubkey agg_pk_a;
secp256k1_xonly_pubkey agg_pk_b;
unsigned char asig_ab[64];
unsigned char asig_bc[64];
unsigned char pop[32];
secp256k1_pubkey pubkey_pop;
unsigned char tx_ab[32];
unsigned char tx_bc[32];
unsigned char buf[32];
secp256k1_scalar t1, t2, tp;
secp256k1_pubkey l, r;
secp256k1_ge l_ge, r_ge;
secp256k1_scalar deckey;
unsigned char sig_ab[64];
unsigned char sig_bc[64];
int nonce_parity_ab;
int nonce_parity_bc;
/* Alice DKG */
frost_dkg_test_helper(share_a, &agg_pk_a);
/* Bob DKG */
frost_dkg_test_helper(share_b, &agg_pk_b);
/* Carol setup */
/* Proof of payment */
secp256k1_testrand256(pop);
CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey_pop, pop));
/* Alice setup */
secp256k1_testrand256(tx_ab);
frost_rand_scalar(&t1);
frost_rand_scalar(&t2);
secp256k1_scalar_add(&tp, &t1, &t2);
/* Left lock */
secp256k1_pubkey_load(CTX, &l_ge, &pubkey_pop);
CHECK(secp256k1_eckey_pubkey_tweak_add(&l_ge, &t1));
secp256k1_pubkey_save(&l, &l_ge);
/* Right lock */
secp256k1_pubkey_load(CTX, &r_ge, &pubkey_pop);
CHECK(secp256k1_eckey_pubkey_tweak_add(&r_ge, &tp));
secp256k1_pubkey_save(&r, &r_ge);
/* Encrypt Alice's signature with the left lock as the encryption key */
nonce_parity_ab = frost_sign_test_helper(asig_ab, share_a, &agg_pk_a, tx_ab, &l);
/* Bob setup */
CHECK(secp256k1_frost_verify_adaptor(CTX, asig_ab, tx_ab, &agg_pk_a, &l, nonce_parity_ab) == 1);
secp256k1_testrand256(tx_bc);
/* Encrypt Bob's signature with the right lock as the encryption key */
nonce_parity_bc = frost_sign_test_helper(asig_bc, share_b, &agg_pk_b, tx_bc, &r);
/* Carol decrypt */
CHECK(secp256k1_frost_verify_adaptor(CTX, asig_bc, tx_bc, &agg_pk_b, &r, nonce_parity_bc) == 1);
secp256k1_scalar_set_b32(&deckey, pop, NULL);
secp256k1_scalar_add(&deckey, &deckey, &tp);
secp256k1_scalar_get_b32(buf, &deckey);
CHECK(secp256k1_frost_adapt(CTX, sig_bc, asig_bc, buf, nonce_parity_bc));
CHECK(secp256k1_schnorrsig_verify(CTX, sig_bc, tx_bc, sizeof(tx_bc), &agg_pk_b) == 1);
/* Bob recover and decrypt */
CHECK(secp256k1_frost_extract_adaptor(CTX, buf, sig_bc, asig_bc, nonce_parity_bc));
secp256k1_scalar_set_b32(&deckey, buf, NULL);
secp256k1_scalar_negate(&t2, &t2);
secp256k1_scalar_add(&deckey, &deckey, &t2);
secp256k1_scalar_get_b32(buf, &deckey);
CHECK(secp256k1_frost_adapt(CTX, sig_ab, asig_ab, buf, nonce_parity_ab));
CHECK(secp256k1_schnorrsig_verify(CTX, sig_ab, tx_ab, sizeof(tx_ab), &agg_pk_a) == 1);
/* Alice recover and derive proof of payment */
CHECK(secp256k1_frost_extract_adaptor(CTX, buf, sig_ab, asig_ab, nonce_parity_ab));
secp256k1_scalar_set_b32(&deckey, buf, NULL);
secp256k1_scalar_negate(&t1, &t1);
secp256k1_scalar_add(&deckey, &deckey, &t1);
secp256k1_scalar_get_b32(buf, &deckey);
CHECK(secp256k1_memcmp_var(buf, pop, 32) == 0);
}
void run_frost_tests(void) {
int i;
for (i = 0; i < COUNT; i++) {
frost_simple_test();
}
frost_api_tests();
frost_nonce_test();
for (i = 0; i < COUNT; i++) {
/* Run multiple times to ensure that pk and nonce have different y
* parities */
frost_tweak_test();
}
for (i = 0; i < COUNT; i++) {
frost_multi_hop_lock_tests();
}
}
#endif

View File

@ -27,12 +27,6 @@ typedef struct {
int parity_acc; int parity_acc;
} secp256k1_keyagg_cache_internal; } secp256k1_keyagg_cache_internal;
/* point_save_ext and point_load_ext are identical to point_save and point_load
* except that they allow saving and loading the point at infinity */
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge);
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data);
static int secp256k1_keyagg_cache_load(const secp256k1_context* ctx, secp256k1_keyagg_cache_internal *cache_i, const secp256k1_musig_keyagg_cache *cache); static int secp256k1_keyagg_cache_load(const secp256k1_context* ctx, secp256k1_keyagg_cache_internal *cache_i, const secp256k1_musig_keyagg_cache *cache);
static void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_keyagg_cache_internal *cache_i, secp256k1_ge *pk); static void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_keyagg_cache_internal *cache_i, secp256k1_ge *pk);

View File

@ -17,23 +17,6 @@
#include "../../hash.h" #include "../../hash.h"
#include "../../util.h" #include "../../util.h"
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) {
if (secp256k1_ge_is_infinity(ge)) {
memset(data, 0, 64);
} else {
secp256k1_ge_to_bytes(data, ge);
}
}
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) {
unsigned char zeros[64] = { 0 };
if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) {
secp256k1_ge_set_infinity(ge);
} else {
secp256k1_ge_from_bytes(ge, data);
}
}
static const unsigned char secp256k1_musig_keyagg_cache_magic[4] = { 0xf4, 0xad, 0xbb, 0xdf }; static const unsigned char secp256k1_musig_keyagg_cache_magic[4] = { 0xf4, 0xad, 0xbb, 0xdf };
/* A keyagg cache consists of /* A keyagg cache consists of

View File

@ -889,6 +889,10 @@ static int secp256k1_ge_parse_ext(secp256k1_ge* ge, const unsigned char *in33) {
# include "modules/ecdsa_adaptor/main_impl.h" # include "modules/ecdsa_adaptor/main_impl.h"
#endif #endif
#ifdef ENABLE_MODULE_FROST
# include "modules/frost/main_impl.h"
#endif
#ifdef ENABLE_MODULE_MUSIG #ifdef ENABLE_MODULE_MUSIG
# include "modules/musig/main_impl.h" # include "modules/musig/main_impl.h"
#endif #endif

View File

@ -7502,6 +7502,10 @@ static void run_ecdsa_wycheproof(void) {
# include "modules/ecdsa_adaptor/tests_impl.h" # include "modules/ecdsa_adaptor/tests_impl.h"
#endif #endif
#ifdef ENABLE_MODULE_FROST
# include "modules/frost/tests_impl.h"
#endif
static void run_secp256k1_memczero_test(void) { static void run_secp256k1_memczero_test(void) {
unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf1[6] = {1, 2, 3, 4, 5, 6};
unsigned char buf2[sizeof(buf1)]; unsigned char buf2[sizeof(buf1)];
@ -7892,6 +7896,10 @@ int main(int argc, char **argv) {
run_ecdsa_adaptor_tests(); run_ecdsa_adaptor_tests();
#endif #endif
#ifdef ENABLE_MODULE_FROST
run_frost_tests();
#endif
/* util tests */ /* util tests */
run_secp256k1_memczero_test(); run_secp256k1_memczero_test();
run_secp256k1_byteorder_tests(); run_secp256k1_byteorder_tests();