diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9a4f44b54..be03d080a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,6 +41,7 @@ env:
ECDSAADAPTOR: 'no'
BPPP: 'no'
SCHNORRSIG_HALFAGG: 'no'
+ FROST: 'no'
### test options
SECP256K1_TEST_ITERS:
BENCH: 'yes'
@@ -79,14 +80,14 @@ jobs:
matrix:
configuration:
- 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_struct', 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: { 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: { 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: { 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', FROST: 'yes', CPPFLAGS: '-DVERIFY' }
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
@@ -158,6 +159,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CC: ${{ matrix.cc }}
steps:
@@ -211,6 +213,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CTIMETESTS: 'no'
steps:
@@ -271,6 +274,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CTIMETESTS: 'no'
steps:
@@ -325,6 +329,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CTIMETESTS: 'no'
strategy:
@@ -389,6 +394,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CTIMETESTS: 'no'
steps:
@@ -450,6 +456,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CTIMETESTS: 'no'
SECP256K1_TEST_ITERS: 2
@@ -510,6 +517,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CTIMETESTS: 'no'
CFLAGS: '-fsanitize=undefined,address -g'
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
@@ -583,6 +591,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CC: 'clang'
SECP256K1_TEST_ITERS: 32
ASM: 'no'
@@ -638,6 +647,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
CTIMETESTS: 'no'
strategy:
@@ -694,15 +704,15 @@ jobs:
fail-fast: false
matrix:
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', 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', 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', 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', 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' }
+ - { 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', 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', 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', 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', FROST: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
- BUILD: 'distcheck'
steps:
@@ -878,6 +888,7 @@ jobs:
ECDSAADAPTOR: 'yes'
BPPP: 'yes'
SCHNORRSIG_HALFAGG: 'yes'
+ FROST: 'yes'
steps:
- name: Checkout
diff --git a/.gitignore b/.gitignore
index b3ae618d4..3d20e46d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,6 +66,7 @@ libsecp256k1.pc
contrib/gh-pr-create.sh
musig_example
+frost_example
### CMake
/CMakeUserPresets.json
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2d9d9d2c9..00a6a4c64 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -69,9 +69,18 @@ option(SECP256K1_ENABLE_MODULE_MUSIG "Enable MuSig module." ON)
option(SECP256K1_ENABLE_MODULE_ECDSA_ADAPTOR "Enable ecdsa adaptor signatures module." ON)
option(SECP256K1_ENABLE_MODULE_ECDSA_S2C "Enable ECDSA sign-to-contract module." ON)
option(SECP256K1_ENABLE_MODULE_BPPP "Enable Bulletproofs++ module." ON)
+option(SECP256K1_ENABLE_MODULE_FROST "Enable FROST module." ON)
# Processing must be done in a topological sorting of the dependency graph
# (dependent module first).
+if(SECP256K1_ENABLE_MODULE_FROST)
+ if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG)
+ message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the frost module.")
+ endif()
+ set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON)
+ add_compile_definitions(ENABLE_MODULE_FROST=1)
+endif()
+
if(SECP256K1_ENABLE_MODULE_BPPP)
if(DEFINED SECP256K1_ENABLE_MODULE_GENERATOR AND NOT SECP256K1_ENABLE_MODULE_GENERATOR)
message(FATAL_ERROR "Module dependency error: You have disabled the generator module explicitly, but it is required by the bppp module.")
@@ -362,6 +371,7 @@ message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG
message(" ecdsa-s2c ........................... ${SECP256K1_ENABLE_MODULE_ECDSA_S2C}")
message(" ecdsa-adaptor ....................... ${SECP256K1_ENABLE_MODULE_ECDSA_ADAPTOR}")
message(" bppp ................................ ${SECP256K1_ENABLE_MODULE_BPPP}")
+message(" frost ............................... ${SECP256K1_ENABLE_MODULE_FROST}")
message("Parameters:")
message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}")
message(" ecmult gen precision bits ........... ${SECP256K1_ECMULT_GEN_PREC_BITS}")
diff --git a/Makefile.am b/Makefile.am
index 329f86ca8..3fe70c607 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -195,6 +195,17 @@ musig_example_LDFLAGS += -lbcrypt
endif
TESTS += musig_example
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
### Precomputed tables
@@ -320,3 +331,7 @@ endif
if ENABLE_MODULE_ECDSA_ADAPTOR
include src/modules/ecdsa_adaptor/Makefile.am.include
endif
+
+if ENABLE_MODULE_FROST
+include src/modules/frost/Makefile.am.include
+endif
diff --git a/README.md b/README.md
index 88bdb2ba4..f7b3b0468 100644
--- a/README.md
+++ b/README.md
@@ -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 Bulletproofs++ range proofs.
* Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md).
+* Experimental module for [FROST](src/modules/frost/frost.md).
Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable.
diff --git a/ci/ci.sh b/ci/ci.sh
index 47c4ae67c..e9f603d9d 100755
--- a/ci/ci.sh
+++ b/ci/ci.sh
@@ -15,7 +15,7 @@ print_environment() {
ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG SCHNORRSIG_HALFAGG ELLSWIFT \
ECDSA_S2C GENERATOR RANGEPROOF WHITELIST MUSIG ECDSAADAPTOR BPPP \
- SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
+ FROST SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
EXAMPLES \
HOST WRAPPER_CMD \
CC CFLAGS CPPFLAGS AR NM
@@ -83,6 +83,7 @@ esac
--enable-module-schnorrsig="$SCHNORRSIG" --enable-module-musig="$MUSIG" --enable-module-ecdsa-adaptor="$ECDSAADAPTOR" \
--enable-module-schnorrsig="$SCHNORRSIG" \
--enable-module-schnorrsig-halfagg="$SCHNORRSIG_HALFAGG" \
+ --enable-module-frost="$FROST" \
--enable-examples="$EXAMPLES" \
--enable-ctime-tests="$CTIMETESTS" \
--with-valgrind="$WITH_VALGRIND" \
diff --git a/configure.ac b/configure.ac
index 673d6fbc0..22ed081f0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -240,6 +240,11 @@ AC_ARG_ENABLE(external_default_callbacks,
AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
[SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
+AC_ARG_ENABLE(module_frost,
+ AS_HELP_STRING([--enable-module-frost],[enable FROST module (experimental)]),
+ [],
+ [SECP_SET_DEFAULT([enable_module_frost], [no], [yes])])
+
# Test-only override of the (autodetected by the C code) "widemul" setting.
# Legal values are:
# * int64 (for [u]int64_t),
@@ -544,6 +549,14 @@ if test x"$enable_module_ecdh" = x"yes"; then
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDH=1"
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 frost module.])
+ fi
+ SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_FROST=1"
+ enable_module_schnorrsig=yes
+fi
+
if test x"$enable_external_default_callbacks" = x"yes"; then
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DUSE_EXTERNAL_DEFAULT_CALLBACKS=1"
fi
@@ -591,6 +604,9 @@ if test x"$enable_experimental" = x"no"; then
if test x"$set_asm" = x"arm32"; then
AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.])
fi
+ if test x"$enable_module_frost" = x"yes"; then
+ AC_MSG_ERROR([FROST module is experimental. Use --enable-experimental to allow.])
+ fi
fi
###
@@ -620,6 +636,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_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_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_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
@@ -660,6 +677,7 @@ echo " module ecdsa-s2c = $enable_module_ecdsa_s2c"
echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor"
echo " module bppp = $enable_module_bppp"
echo " module schnorrsig-halfagg = $enable_module_schnorrsig_halfagg"
+echo " module frost = $enable_module_frost"
echo
echo " asm = $set_asm"
echo " ecmult window size = $set_ecmult_window"
diff --git a/contrib/frost-vectors.py b/contrib/frost-vectors.py
new file mode 100755
index 000000000..6a4cd7438
--- /dev/null
+++ b/contrib/frost-vectors.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+
+import sys
+import json
+import textwrap
+
+max_participants = 0
+
+if len(sys.argv) < 2:
+ print(
+ "This script converts BIP FROST test vectors in a given directory to a C file that can be used in the test framework."
+ )
+ print("Usage: %s
" % sys.argv[0])
+ sys.exit(1)
+
+
+def hexstr_to_intarray(str):
+ return ", ".join([f"0x{b:02X}" for b in bytes.fromhex(str)])
+
+
+def create_init(name):
+ return """
+static const struct frost_%s_vector frost_%s_vector = {
+""" % (
+ name,
+ name,
+ )
+
+
+def init_array(key):
+ return textwrap.indent("{ %s },\n" % hexstr_to_intarray(data[key]), 4 * " ")
+
+
+def init_arrays(key):
+ s = textwrap.indent("{\n", 4 * " ")
+ s += textwrap.indent(
+ ",\n".join(["{ %s }" % hexstr_to_intarray(x) for x in data[key]]), 8 * " "
+ )
+ s += textwrap.indent("\n},\n", 4 * " ")
+ return s
+
+
+def init_nested_arrays(array):
+ return "{ %s }" % ", ".join(["{ %s }" % hexstr_to_intarray(x) for x in array])
+
+
+def init_indices(array):
+ return " %d, { %s }" % (
+ len(array),
+ ", ".join(map(str, array)) if len(array) > 0 else "0",
+ )
+
+
+def init_is_xonly(case):
+ if len(case.get("tweak_indices", [])) > 0:
+ return ", ".join("1" if x else "0" for x in case["is_xonly"])
+ return "0"
+
+
+def init_optional_expected(case):
+ return hexstr_to_intarray(case["expected"]) if "expected" in case else "0"
+
+
+def init_cases(cases, f):
+ s = textwrap.indent("{\n", 4 * " ")
+ for (i, case) in enumerate(cases):
+ s += textwrap.indent("%s\n" % f(case), 8 * " ")
+ s += textwrap.indent("},\n", 4 * " ")
+ return s
+
+
+def finish_init():
+ return "};\n"
+
+
+s = (
+ """/**
+ * Automatically generated by %s.
+ *
+ * The test vectors for the FROST implementation.
+ */
+"""
+ % sys.argv[0]
+)
+
+s += """
+enum FROST_ERROR {
+ FROST_PUBKEY,
+ FROST_PUBSHARE,
+ FROST_TWEAK,
+ FROST_PUBNONCE,
+ FROST_AGGNONCE,
+ FROST_SECNONCE,
+ FROST_SIG,
+ FROST_SIG_VERIFY,
+ FROST_OTHER
+};
+"""
+
+# key gen vectors
+with open(sys.argv[1] + "/keygen_vectors.json") as f:
+ data = json.load(f)
+
+ num_valid_cases = len(data["valid_test_cases"])
+ num_pubshare_fail_cases = len(data["pubshare_correctness_fail_test_cases"])
+ num_group_pubkey_fail_cases = len(data["group_pubkey_correctness_fail_test_cases"])
+
+ all_cases = (
+ data["valid_test_cases"]
+ + data["pubshare_correctness_fail_test_cases"]
+ + data["group_pubkey_correctness_fail_test_cases"]
+ )
+ max_participants = max(
+ len(test_case["participant_identifiers"]) for test_case in all_cases
+ )
+
+ # Add structures for valid and error cases
+ s += """
+struct frost_key_gen_valid_test_case {
+ size_t max_participants;
+ size_t min_participants;
+ unsigned char group_public_key[33];
+ size_t participant_identifiers_len;
+ size_t participant_identifiers[%d];
+ unsigned char participant_pubshares[%d][33];
+ unsigned char participant_secshares[%d][32];
+};
+""" % (
+ max_participants,
+ max_participants,
+ max_participants,
+ )
+ s += """
+struct frost_key_gen_pubshare_fail_test_case {
+ size_t max_participants;
+ size_t min_participants;
+ unsigned char group_public_key[33];
+ size_t participant_identifiers_len;
+ size_t participant_identifiers[%d];
+ unsigned char participant_pubshares[%d][33];
+ unsigned char participant_secshares[%d][32];
+ enum FROST_ERROR error;
+};
+""" % (
+ max_participants,
+ max_participants,
+ max_participants,
+ )
+ s += """
+struct frost_key_gen_pubkey_fail_test_case {
+ size_t max_participants;
+ size_t min_participants;
+ unsigned char group_public_key[33];
+ size_t participant_identifiers_len;
+ size_t participant_identifiers[%d];
+ unsigned char participant_pubshares[%d][33];
+ unsigned char participant_secshares[%d][32];
+ enum FROST_ERROR error;
+};
+""" % (
+ max_participants,
+ max_participants,
+ max_participants,
+ )
+
+ # Add structure for entire vector
+
+ s += """
+struct frost_key_gen_vector {
+ struct frost_key_gen_valid_test_case valid_cases[%d];
+ struct frost_key_gen_pubshare_fail_test_case pubshare_fail_cases[%d];
+ struct frost_key_gen_pubkey_fail_test_case pubkey_fail_cases[%d];
+};
+""" % (
+ num_valid_cases,
+ num_pubshare_fail_cases,
+ num_group_pubkey_fail_cases,
+ )
+
+ s += create_init("key_gen")
+
+ # Add valid cases to the vector
+ s += init_cases(
+ data["valid_test_cases"],
+ lambda case: "{ %d, %d, { %s }, %s, %s, %s },"
+ % (
+ case["max_participants"],
+ case["min_participants"],
+ hexstr_to_intarray(case["group_public_key"]),
+ init_indices(case["participant_identifiers"]),
+ init_nested_arrays(case["participant_pubshares"]),
+ init_nested_arrays(case["participant_secshares"]),
+ ),
+ )
+
+ def comment_to_error(case):
+ comment = case["comment"]
+ if "public key" in comment.lower():
+ return "FROST_PUBKEY"
+ elif "pubshare" in comment.lower():
+ return "FROST_PUBSHARE"
+ else:
+ sys.exit("Unknown error")
+
+ for cases in ("pubshare_correctness_fail_test_cases", "group_pubkey_correctness_fail_test_cases"):
+ s += init_cases(
+ data[cases],
+ lambda case: "{ %d, %d, { %s }, %s, %s, %s, %s },"
+ % (
+ case["max_participants"],
+ case["min_participants"],
+ hexstr_to_intarray(case["group_public_key"]),
+ init_indices(case["participant_identifiers"]),
+ init_nested_arrays(case["participant_pubshares"]),
+ init_nested_arrays(case["participant_secshares"]),
+ comment_to_error(case),
+ ),
+ )
+ s += finish_init()
+s += "enum { FROST_VECTORS_MAX_PARTICIPANTS = %d };" % max_participants
+print(s)
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 607bb6777..8e1c97019 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -28,3 +28,7 @@ endif()
if(SECP256K1_ENABLE_MODULE_SCHNORRSIG)
add_example(schnorr)
endif()
+
+if(SECP256K1_ENABLE_MODULE_FROST)
+ add_example(frost)
+endif()
diff --git a/examples/frost.c b/examples/frost.c
new file mode 100644
index 000000000..d9b7be691
--- /dev/null
+++ b/examples/frost.c
@@ -0,0 +1,243 @@
+/***********************************************************************
+ * Copyright (c) 2021-2024 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
+#include
+#include
+
+#include
+#include
+#include
+
+#include "examples_util.h"
+
+struct signer_secrets {
+ secp256k1_frost_secshare share;
+ secp256k1_frost_secnonce secnonce;
+};
+
+struct signer {
+ secp256k1_pubkey pubshare;
+ secp256k1_frost_pubnonce pubnonce;
+ secp256k1_frost_session session;
+ secp256k1_frost_partial_sig partial_sig;
+};
+
+/* Threshold required in creating the aggregate signature */
+#define THRESHOLD 3
+
+
+/* Number of public keys involved in creating the aggregate signature */
+#define N_SIGNERS 5
+
+/* Create shares and coefficient commitments */
+static int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) {
+ int i;
+ secp256k1_frost_secshare shares[N_SIGNERS];
+ secp256k1_pubkey vss_commitment[THRESHOLD];
+ unsigned char seed[32];
+
+ if (!fill_random(seed, sizeof(seed))) {
+ return 0;
+ }
+
+ /* Generate shares for the participants */
+ if (!secp256k1_frost_shares_gen(ctx, shares, vss_commitment, seed, THRESHOLD, N_SIGNERS)) {
+ return 0;
+ }
+
+ /* Distribute shares and VSS commitment */
+ for (i = 0; i < N_SIGNERS; i++) {
+ signer_secrets[i].share = shares[i];
+ /* Each participant verifies their share. */
+ if (!secp256k1_frost_share_verify(ctx, THRESHOLD, i, &shares[i], vss_commitment)) {
+ return 0;
+ }
+ /* Each participant generates public verification shares that are
+ * used for verifying partial signatures. */
+ if (!secp256k1_frost_compute_pubshare(ctx, &signer[i].pubshare, THRESHOLD, i, vss_commitment)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Tweak the pubkey corresponding to the provided tweak cache, update the cache
+ * and return the tweaked aggregate pk. */
+static int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_frost_keygen_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..";
+
+ /* 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 */
+static int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char *msg32, unsigned char *sig64, const secp256k1_frost_keygen_cache *cache) {
+ int i;
+ int 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, cache, NULL)) {
+ return 0;
+ }
+ is_signer[i] = 0; /* Initialize is_signer */
+ }
+ /* Select a random subset of signers */
+ for (i = 0; i < THRESHOLD; i++) {
+ unsigned int 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;
+ ids[i] = signer_id;
+ }
+ /* 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, signer_id, 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;
+ size_t i;
+ struct signer_secrets signer_secrets[N_SIGNERS];
+ struct signer signers[N_SIGNERS];
+ const secp256k1_pubkey *pubshares_ptr[N_SIGNERS];
+ secp256k1_xonly_pubkey pk;
+ secp256k1_frost_keygen_cache keygen_cache = {0};
+ unsigned char msg[] = {'t', 'h', 'i', 's', '_', 'c', 'o', 'u', 'l', 'd', ' ', 'b', 'e', ' ', 't', 'h', 'e', '_', 'h', 'a', 's', 'h', '_', 'o', 'f', '_', 'a', '_', 'm', 's', 'g', '!'};
+ unsigned char sig[64];
+ size_t ids[5];
+
+ /* Create a context for signing and verification */
+ ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
+ for (i = 0; i < N_SIGNERS; i++) {
+ pubshares_ptr[i] = &signers[i].pubshare;
+ ids[i] = i;
+ }
+ printf("Creating shares.........");
+ if (!create_shares(ctx, signer_secrets, signers)) {
+ printf("FAILED\n");
+ return 1;
+ }
+ printf("ok\n");
+ printf("Generating public key...");
+ if (!secp256k1_frost_pubkey_gen(ctx, &keygen_cache, pubshares_ptr, N_SIGNERS, ids)) {
+ printf("FAILED\n");
+ return 1;
+ }
+ printf("ok\n");
+ printf("Tweaking................");
+ /* Optionally tweak the aggregate key */
+ if (!tweak(ctx, &pk, &keygen_cache)) {
+ printf("FAILED\n");
+ return 1;
+ }
+ printf("ok\n");
+ printf("Signing message.........");
+ if (!sign(ctx, signer_secrets, signers, msg, sig, &keygen_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;
+}
diff --git a/include/secp256k1_frost.h b/include/secp256k1_frost.h
new file mode 100644
index 000000000..79e65ef51
--- /dev/null
+++ b/include/secp256k1_frost.h
@@ -0,0 +1,650 @@
+#ifndef SECP256K1_FROST_H
+#define SECP256K1_FROST_H
+
+#include "secp256k1_extrakeys.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+
+/** 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.
+ *
+ * It is recommended to read the documentation in this include file carefully.
+ * Further notes on API usage can be found in src/modules/frost/frost.md
+ *
+ * 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 the FROST group public
+ * key and tweak state used for signing and verification.
+ *
+ * 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_keygen_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_secshare;
+
+/** 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.
+ *
+ * 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 SECP256K1_WARN_UNUSED_RESULT 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 SECP256K1_WARN_UNUSED_RESULT 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_secshare *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 SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_share_parse(
+ const secp256k1_context *ctx,
+ secp256k1_frost_secshare *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 participant.
+ *
+ * The trusted dealer must transmit shares over secure channels to each
+ * participant.
+ *
+ * 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 participants).
+ *
+ * Returns: 0 if the arguments are invalid, 1 otherwise
+ * Args: ctx: pointer to a context object
+ * Out: shares: pointer to the key shares
+ * vss_commitment: output array of the elements of the VSS commitment
+ * In: seed32: 32-byte random seed as explained above. Must be
+ * unique to this call to secp256k1_frost_shares_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_gen(
+ const secp256k1_context *ctx,
+ secp256k1_frost_secshare *shares,
+ secp256k1_pubkey *vss_commitment,
+ 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);
+
+/** Verifies a share received during a key generation session
+ *
+ * The signature is verified against the VSS commitment received with the
+ * share.
+ *
+ * Returns: 0 if the arguments are invalid or the share does not verify, 1
+ * otherwise
+ * Args ctx: pointer to a context object
+ * In: threshold: the minimum number of signers required to produce a
+ * signature
+ * id: the participant ID of the share recipient
+ * share: pointer to a key generation share
+ * vss_commitment: input array of the elements of the VSS commitment
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_share_verify(
+ const secp256k1_context *ctx,
+ size_t threshold,
+ const size_t id,
+ const secp256k1_frost_secshare *share,
+ const secp256k1_pubkey *vss_commitment
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
+
+/** Computes a public verification share used for verifying partial signatures
+ *
+ * Returns: 0 if the arguments are invalid, 1 otherwise
+ * Args: ctx: pointer to a context object
+ * Out: pubshare: pointer to a struct to store the public verification
+ * share
+ * In: threshold: the minimum number of signers required to produce a
+ * signature
+ * id: the participant ID of the participant whose partial
+ * signature will be verified with the pubshare
+ * vss_commitment: input array of the elements of the VSS commitment
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_compute_pubshare(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pubshare,
+ size_t threshold,
+ const size_t id,
+ const secp256k1_pubkey *vss_commitment
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
+
+/** Computes a group public key and uses it to initialize a keygen_cache
+ *
+ * Returns: 0 if the arguments are invalid, 1 otherwise
+ * Args: ctx: pointer to a context object
+ * Out: keygen_cache: pointer to a frost_keygen_cache struct that is required
+ * for signing (or observing the signing session and
+ * verifying partial signatures).
+ * In: pubshares: input array of pointers to the public verification
+ * shares of the participants ordered by the IDs of the
+ * participants
+ * n_pubshares: the total number of public verification shares
+ * ids: array of the participant IDs of the signers
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_gen(
+ const secp256k1_context *ctx,
+ secp256k1_frost_keygen_cache *keygen_cache,
+ const secp256k1_pubkey * const *pubshares,
+ size_t n_pubshares,
+ const size_t *ids
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
+
+/** Obtain the group public key from a keygen_cache.
+ *
+ * This is only useful if you need the non-xonly public key, in particular for
+ * plain (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: pk: the FROST group public key.
+ * In: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by
+ * `frost_pubkey_gen`
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get(
+ const secp256k1_context *ctx,
+ secp256k1_pubkey *pk,
+ const secp256k1_frost_keygen_cache *keygen_cache
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
+
+/** Apply ordinary "EC" tweaking to a public key in a given keygen_cache by
+ * adding the generator multiplied with `tweak32` to it. This is useful for
+ * deriving child keys from a group 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_pubkey_gen(..., keygen_cache, ...)
+ * secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, keygen_cache, tweak32)
+ * secp256k1_ec_pubkey_serialize(..., buf, output_pk)
+ * secp256k1_frost_pubkey_get(..., ec_pk, keygen_cache)
+ * secp256k1_ec_pubkey_tweak_add(..., ec_pk, tweak32)
+ * secp256k1_ec_pubkey_serialize(..., buf2, ec_pk)
+ *
+ * This function is required if you want to _sign_ for a tweaked group 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: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by
+ * `frost_pubkey_gen`
+ * 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_keygen_cache *keygen_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 keygen_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_pubkey_gen(..., keygen_cache, ..., ..., ...)
+ * secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, keygen_cache, tweak32)
+ * secp256k1_xonly_pubkey_serialize(..., buf, output_pk)
+ * secp256k1_frost_pubkey_get(..., pk, keygen_cache)
+ * secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., pk, tweak32)
+ *
+ * This function is required if you want to _sign_ for a tweaked group 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: keygen_cache: pointer to a `frost_keygen_cache` struct initialized by
+ * `frost_pubkey_gen`
+ * 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_keygen_cache *keygen_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 group 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 share!
+ * 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)
+ * keygen_cache: pointer to the keygen_cache that was used to create the group
+ * (and potentially tweaked) public key if already known
+ * (can be NULL)
+ * extra_input32: an optional 32-byte array that is input to the nonce
+ * derivation function (can be NULL)
+ */
+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT 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_secshare *agg_share,
+ const unsigned char *msg32,
+ const secp256k1_frost_keygen_cache *keygen_cache,
+ 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
+ * my_id: the ID of the participant who will use the session for
+ * signing
+ * ids: array of the participant IDs of the signers
+ * keygen_cache: pointer to frost_keygen_cache struct
+ * 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 size_t my_id,
+ const size_t *ids,
+ const secp256k1_frost_keygen_cache *keygen_cache,
+ const secp256k1_pubkey *adaptor
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7) 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 share!
+ *
+ * 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
+ * keygen_cache: pointer to frost_keygen_cache struct
+ */
+SECP256K1_API int secp256k1_frost_partial_sign(
+ const secp256k1_context *ctx,
+ secp256k1_frost_partial_sig *partial_sig,
+ secp256k1_frost_secnonce *secnonce,
+ const secp256k1_frost_secshare *agg_share,
+ const secp256k1_frost_session *session,
+ const secp256k1_frost_keygen_cache *keygen_cache
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
+
+/** 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 `keygen_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_compute_pubshare` for the signer's ID.
+ * 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_compute_pubshare`
+ * session: pointer to the session that was created with
+ * `frost_nonce_process`
+ * keygen_cache: pointer to frost_keygen_cache struct
+ */
+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_keygen_cache *keygen_cache
+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
+
+/** 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 SECP256K1_WARN_UNUSED_RESULT 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
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 27e902045..773796be6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -120,6 +120,9 @@ if(SECP256K1_INSTALL)
"${PROJECT_SOURCE_DIR}/include/secp256k1.h"
"${PROJECT_SOURCE_DIR}/include/secp256k1_preallocated.h"
)
+ if(SECP256K1_ENABLE_MODULE_FROST)
+ list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_frost.h")
+ endif()
if(SECP256K1_ENABLE_MODULE_BPPP)
list(APPEND ${PROJECT_NAME}_headers "${PROJECT_SOURCE_DIR}/include/secp256k1_bppp.h")
endif()
diff --git a/src/ctime_tests.c b/src/ctime_tests.c
index 407d2cc6a..5e3ab60e8 100644
--- a/src/ctime_tests.c
+++ b/src/ctime_tests.c
@@ -47,6 +47,10 @@
#include "../include/secp256k1_musig.h"
#endif
+#ifdef ENABLE_MODULE_FROST
+#include "../include/secp256k1_frost.h"
+#endif
+
static void run_tests(secp256k1_context *ctx, unsigned char *key);
int main(void) {
@@ -349,4 +353,87 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
CHECK(ret == 1);
}
#endif
+
+#ifdef ENABLE_MODULE_FROST
+ {
+ unsigned char session_id[32];
+ unsigned char seed[32];
+ secp256k1_frost_secnonce secnonce[2];
+ secp256k1_frost_pubnonce pubnonce[2];
+ const secp256k1_frost_pubnonce *pubnonce_ptr[2];
+ secp256k1_frost_keygen_cache cache = {0};
+ 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_secshare shares[2];
+ secp256k1_pubkey vss_commitment[2];
+ secp256k1_pubkey pubshare[2];
+ const secp256k1_pubkey *pubshares_ptr[2];
+ size_t ids[2] = {0, 1};
+
+ pubnonce_ptr[0] = &pubnonce[0];
+ pubnonce_ptr[1] = &pubnonce[1];
+ SECP256K1_CHECKMEM_DEFINE(key, 32);
+ memcpy(seed, key, 32);
+ seed[0] = seed[0] + 1;
+ memcpy(extra_input, key, sizeof(extra_input));
+ extra_input[0] = extra_input[0] + 2;
+ memcpy(sec_adaptor, key, sizeof(sec_adaptor));
+ sec_adaptor[0] = extra_input[0] + 3;
+ memcpy(session_id, key, sizeof(session_id));
+ session_id[0] = session_id[0] + 5;
+ partial_sig_ptr[0] = &partial_sig;
+ pubshares_ptr[0] = &pubshare[0];
+ pubshares_ptr[1] = &pubshare[1];
+
+ /* shares_gen */
+ SECP256K1_CHECKMEM_UNDEFINE(seed, 32);
+ ret = secp256k1_frost_shares_gen(ctx, shares, vss_commitment, seed, 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]));
+ /* pubkey_gen */
+ SECP256K1_CHECKMEM_DEFINE(&vss_commitment[0], sizeof(secp256k1_pubkey));
+ SECP256K1_CHECKMEM_DEFINE(&vss_commitment[1], sizeof(secp256k1_pubkey));
+ CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[0], 2, 0, vss_commitment));
+ CHECK(secp256k1_frost_compute_pubshare(ctx, &pubshare[1], 2, 1, vss_commitment));
+ CHECK(secp256k1_frost_pubkey_gen(ctx, &cache, pubshares_ptr, 2, ids));
+ /* 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));
+ ret = secp256k1_frost_nonce_gen(ctx, &secnonce[0], &pubnonce[0], session_id, &shares[0], msg, &cache, 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, &cache, extra_input);
+ SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
+ CHECK(ret == 1);
+ /* partial_sign */
+ /* Make sure that previous tests don't undefine msg. It's not used as a secret here. */
+ SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg));
+ CHECK(secp256k1_frost_nonce_process(ctx, &session, pubnonce_ptr, 2, msg, 0, ids, &cache, &adaptor) == 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
}
diff --git a/src/group.h b/src/group.h
index 81b159c81..70968c1c2 100644
--- a/src/group.h
+++ b/src/group.h
@@ -86,7 +86,11 @@ static void secp256k1_ge_set_gej(secp256k1_ge *r, secp256k1_gej *a);
/** Set a group element equal to another which is given in jacobian coordinates. */
static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a);
-/** Set a batch of group elements equal to the inputs given in jacobian coordinates */
+/** Set group elements r[0:len] (affine) equal to group elements a[0:len] (jacobian).
+ * None of the group elements in a[0:len] may be infinity. Constant time. */
+static void secp256k1_ge_set_all_gej(secp256k1_ge *r, const secp256k1_gej *a, size_t len);
+
+/** Set group elements r[0:len] (affine) equal to group elements a[0:len] (jacobian). */
static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len);
/** Bring a batch of inputs to the same global z "denominator", based on ratios between
@@ -185,12 +189,20 @@ static void secp256k1_gej_rescale(secp256k1_gej *r, const secp256k1_fe *b);
/** Convert a group element that is not infinity to a 64-byte array. The output
* array is platform-dependent. */
-static void secp256k1_ge_to_bytes(unsigned char *buf, secp256k1_ge *a);
+static void secp256k1_ge_to_bytes(unsigned char *buf, const secp256k1_ge *a);
/** Convert a 64-byte array into group element. This function assumes that the
* provided buffer correctly encodes a group element. */
static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf);
+/** Convert a group element (that is allowed to be infinity) to a 64-byte
+ * array. The output array is platform-dependent. */
+static void secp256k1_ge_to_bytes_ext(unsigned char *data, const secp256k1_ge *ge);
+
+/** Convert a 64-byte array into a group element. This function assumes that the
+ * provided buffer is the output of secp256k1_ge_to_bytes_ext. */
+static void secp256k1_ge_from_bytes_ext(secp256k1_ge *ge, const unsigned char *data);
+
/** Determine if a point (which is assumed to be on the curve) is in the correct (sub)group of the curve.
*
* In normal mode, the used group is secp256k1, which has cofactor=1 meaning that every point on the curve is in the
diff --git a/src/group_impl.h b/src/group_impl.h
index f27b7d994..b572e7c8d 100644
--- a/src/group_impl.h
+++ b/src/group_impl.h
@@ -195,6 +195,52 @@ static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a) {
SECP256K1_GE_VERIFY(r);
}
+static void secp256k1_ge_set_all_gej(secp256k1_ge *r, const secp256k1_gej *a, size_t len) {
+ secp256k1_fe u;
+ size_t i;
+ size_t last_i = SIZE_MAX;
+#ifdef VERIFY
+ for (i = 0; i < len; i++) {
+ SECP256K1_GEJ_VERIFY(&a[i]);
+ VERIFY_CHECK(!secp256k1_gej_is_infinity(&a[i]));
+ }
+#endif
+
+ for (i = 0; i < len; i++) {
+ /* Use destination's x coordinates as scratch space */
+ if (last_i == SIZE_MAX) {
+ r[i].x = a[i].z;
+ } else {
+ secp256k1_fe_mul(&r[i].x, &r[last_i].x, &a[i].z);
+ }
+ last_i = i;
+ }
+ if (last_i == SIZE_MAX) {
+ return;
+ }
+ secp256k1_fe_inv(&u, &r[last_i].x);
+
+ i = last_i;
+ while (i > 0) {
+ i--;
+ secp256k1_fe_mul(&r[last_i].x, &r[i].x, &u);
+ secp256k1_fe_mul(&u, &u, &a[last_i].z);
+ last_i = i;
+ }
+ VERIFY_CHECK(!a[last_i].infinity);
+ r[last_i].x = u;
+
+ for (i = 0; i < len; i++) {
+ secp256k1_ge_set_gej_zinv(&r[i], &a[i], &r[i].x);
+ }
+
+#ifdef VERIFY
+ for (i = 0; i < len; i++) {
+ SECP256K1_GE_VERIFY(&r[i]);
+ }
+#endif
+}
+
static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len) {
secp256k1_fe u;
size_t i;
@@ -965,7 +1011,7 @@ static int secp256k1_ge_x_frac_on_curve_var(const secp256k1_fe *xn, const secp25
return secp256k1_fe_is_square_var(&r);
}
-static void secp256k1_ge_to_bytes(unsigned char *buf, secp256k1_ge *a) {
+static void secp256k1_ge_to_bytes(unsigned char *buf, const secp256k1_ge *a) {
secp256k1_ge_storage s;
/* We require that the secp256k1_ge_storage type is exactly 64 bytes.
@@ -985,4 +1031,21 @@ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf) {
secp256k1_ge_from_storage(r, &s);
}
+static void secp256k1_ge_to_bytes_ext(unsigned char *data, const secp256k1_ge *ge) {
+ if (secp256k1_ge_is_infinity(ge)) {
+ memset(data, 0, 64);
+ } else {
+ secp256k1_ge_to_bytes(data, ge);
+ }
+}
+
+static void secp256k1_ge_from_bytes_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);
+ }
+}
+
#endif /* SECP256K1_GROUP_IMPL_H */
diff --git a/src/modules/frost/Makefile.am.include b/src/modules/frost/Makefile.am.include
new file mode 100644
index 000000000..b21aa078f
--- /dev/null
+++ b/src/modules/frost/Makefile.am.include
@@ -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
diff --git a/src/modules/frost/adaptor_impl.h b/src/modules/frost/adaptor_impl.h
new file mode 100644
index 000000000..28b21f709
--- /dev/null
+++ b/src/modules/frost/adaptor_impl.h
@@ -0,0 +1,168 @@
+/***********************************************************************
+ * Copyright (c) 2022-2024 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
+
+#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
diff --git a/src/modules/frost/frost.md b/src/modules/frost/frost.md
new file mode 100644
index 000000000..662e0abfd
--- /dev/null
+++ b/src/modules/frost/frost.md
@@ -0,0 +1,94 @@
+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_shares_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 `ids33` 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. The trusted dealer generate a VSS commitment and shares with
+ `secp256k1_frost_shares_gen`. The VSS commitment must be broadcast to all
+ participants. Assign each participant a share, and an ID according to the
+ index of the share returned from `secp256k1_frost_shares_gen`, and
+ distribute the shares to the participants using a secure channel.
+2. After receiving a share and VSS commitment from the dealer, call
+ `secp256k1_frost_share_verify` to verify the share.
+3. Compute the public verification shares for each participant by calling
+ `secp256k1_frost_compute_pubshare` with the ID of the participant.
+ This share is required by `secp256k1_frost_partial_sig_verify` to verify
+ partial signatures generated by `secp256k1_frost_partial_sign`, and public
+ shares are required by `secp256k1_frost_pubkey_gen` to generate the group
+ public key.
+4. Generate the group key by passing the public shares of all participants to
+ `secp256k1_frost_pubkey_gen`, which will initialize a key generation
+ context. The context can be passed to `secp256k1_frost_pubkey_get` to obtain
+ the group public key.
+
+# 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. Initialize the key generation context with `secp256k1_frost_pubkey_gen`.
+2. 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.
+3. Generate a pair of secret and public nonce with `secp256k1_frost_nonce_gen`
+ and send the public nonce to the other signers.
+4. Process the aggregate nonce with `secp256k1_frost_nonce_process`.
+5. Create a partial signature with `secp256k1_frost_partial_sign`.
+6. Verify the partial signatures (optional in some scenarios) with
+ `secp256k1_frost_partial_sig_verify`.
+7. 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 3).
+
+# 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 3 and 5.
diff --git a/src/modules/frost/keygen.h b/src/modules/frost/keygen.h
new file mode 100644
index 000000000..c2fc8f55f
--- /dev/null
+++ b/src/modules/frost/keygen.h
@@ -0,0 +1,33 @@
+/**********************************************************************
+ * Copyright (c) 2021-2024 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;
+ /* tweak is identical to value tacc[v] in the specification. */
+ secp256k1_scalar tweak;
+ /* parity_acc corresponds to gacc[v] in the spec. If gacc[v] is -1,
+ * parity_acc is 1. Otherwise, parity_acc is 0. */
+ int parity_acc;
+} secp256k1_keygen_cache_internal;
+
+static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_keygen_cache_internal *cache_i, const secp256k1_frost_keygen_cache *cache);
+
+static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_secshare* share);
+
+static void secp256k1_frost_get_scalar_index(secp256k1_scalar *idx, const size_t id);
+
+static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const size_t *ids, size_t n_participants, const size_t my_id);
+
+#endif
diff --git a/src/modules/frost/keygen_impl.h b/src/modules/frost/keygen_impl.h
new file mode 100644
index 000000000..06ff885c3
--- /dev/null
+++ b/src/modules/frost/keygen_impl.h
@@ -0,0 +1,422 @@
+/**********************************************************************
+ * Copyright (c) 2021-2024 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
+
+#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_keygen_cache_magic[4] = { 0x40, 0x25, 0x2e, 0x41 };
+
+/* A keygen 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_keygen_cache_save(secp256k1_frost_keygen_cache *cache, secp256k1_keygen_cache_internal *cache_i) {
+ unsigned char *ptr = cache->data;
+ memcpy(ptr, secp256k1_frost_keygen_cache_magic, 4);
+ ptr += 4;
+ secp256k1_ge_to_bytes(ptr, &cache_i->pk);
+ ptr += 64;
+ *ptr = cache_i->parity_acc;
+ ptr += 1;
+ secp256k1_scalar_get_b32(ptr, &cache_i->tweak);
+}
+
+static int secp256k1_keygen_cache_load(const secp256k1_context* ctx, secp256k1_keygen_cache_internal *cache_i, const secp256k1_frost_keygen_cache *cache) {
+ const unsigned char *ptr = cache->data;
+ ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_keygen_cache_magic, 4) == 0);
+ ptr += 4;
+ secp256k1_ge_from_bytes(&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 void secp256k1_frost_get_scalar_index(secp256k1_scalar *idx, const size_t id) {
+ secp256k1_scalar_set_int(idx, id + 1);
+}
+
+static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 };
+
+static void secp256k1_frost_share_save(secp256k1_frost_secshare* 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_secshare* 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_secshare* 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_secshare* 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;
+}
+
+static void secp256k1_frost_derive_coeff(secp256k1_scalar *coeff, const unsigned char *polygen32, size_t i) {
+ secp256k1_sha256 sha;
+ unsigned char buf[32];
+
+ secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/coeffgen", sizeof("FROST/coeffgen") - 1);
+ secp256k1_sha256_write(&sha, polygen32, 32);
+ secp256k1_write_be64(&buf[0], i);
+ secp256k1_sha256_write(&sha, buf, 8);
+ secp256k1_sha256_finalize(&sha, buf);
+ secp256k1_scalar_set_b32(coeff, buf, NULL);
+}
+
+static void secp256k1_frost_vss_gen(const secp256k1_context *ctx, secp256k1_pubkey *vss_commitment, const unsigned char *polygen32, size_t threshold) {
+ secp256k1_gej rj;
+ secp256k1_ge rp;
+ size_t i;
+
+ /* Compute commitment to each coefficient */
+ for (i = 0; i < threshold; i++) {
+ secp256k1_scalar coeff_i;
+
+ secp256k1_frost_derive_coeff(&coeff_i, polygen32, i);
+ secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &coeff_i);
+ secp256k1_ge_set_gej(&rp, &rj);
+ secp256k1_pubkey_save(&vss_commitment[threshold - i - 1], &rp);
+ }
+}
+
+static int secp256k1_frost_share_gen(secp256k1_frost_secshare *share, const unsigned char *polygen32, size_t threshold, const size_t id) {
+ secp256k1_scalar idx;
+ secp256k1_scalar share_i;
+ size_t i;
+ int ret = 1;
+
+ /* Derive share */
+ /* See RFC 9591, appendix C.1 */
+ secp256k1_scalar_set_int(&share_i, 0);
+ for (i = 0; i < threshold; i++) {
+ secp256k1_scalar coeff_i;
+
+ secp256k1_frost_derive_coeff(&coeff_i, polygen32, i);
+ /* Horner's method to evaluate polynomial to derive shares */
+ secp256k1_scalar_add(&share_i, &share_i, &coeff_i);
+ if (i < threshold - 1) {
+ secp256k1_frost_get_scalar_index(&idx, id);
+ secp256k1_scalar_mul(&share_i, &share_i, &idx);
+ }
+ }
+ secp256k1_frost_share_save(share, &share_i);
+
+ return ret;
+}
+
+int secp256k1_frost_shares_gen(const secp256k1_context *ctx, secp256k1_frost_secshare *shares, secp256k1_pubkey *vss_commitment, const unsigned char *seed32, size_t threshold, size_t n_participants) {
+ secp256k1_sha256 sha;
+ unsigned char polygen[32];
+ size_t i;
+ int ret = 1;
+
+ 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(vss_commitment != NULL);
+ ARG_CHECK(seed32 != NULL);
+ ARG_CHECK(threshold > 1);
+ ARG_CHECK(n_participants >= threshold);
+
+ /* Commit to all inputs */
+ secp256k1_sha256_initialize(&sha);
+ 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);
+
+ secp256k1_frost_vss_gen(ctx, vss_commitment, polygen, threshold);
+
+ for (i = 0; i < n_participants; i++) {
+ ret &= secp256k1_frost_share_gen(&shares[i], polygen, threshold, i);
+ }
+
+ return ret;
+}
+
+typedef struct {
+ const secp256k1_context *ctx;
+ secp256k1_scalar idx;
+ secp256k1_scalar idxn;
+ const secp256k1_pubkey *vss_commitment;
+} secp256k1_frost_evaluate_vss_ecmult_data;
+
+typedef struct {
+ const secp256k1_context *ctx;
+ const secp256k1_pubkey * const* pubshares;
+ const size_t *ids;
+ size_t n_pubshares;
+} secp256k1_frost_interpolate_pubkey_ecmult_data;
+
+static int secp256k1_frost_evaluate_vss_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) {
+ secp256k1_frost_evaluate_vss_ecmult_data *ctx = (secp256k1_frost_evaluate_vss_ecmult_data *) data;
+ if (!secp256k1_pubkey_load(ctx->ctx, pt, &ctx->vss_commitment[idx])) {
+ return 0;
+ }
+ *sc = ctx->idxn;
+ secp256k1_scalar_mul(&ctx->idxn, &ctx->idxn, &ctx->idx);
+
+ return 1;
+}
+
+static int secp256k1_frost_interpolate_pubkey_ecmult_callback(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data) {
+ secp256k1_frost_interpolate_pubkey_ecmult_data *ctx = (secp256k1_frost_interpolate_pubkey_ecmult_data *) data;
+ secp256k1_scalar l;
+
+ if (!secp256k1_pubkey_load(ctx->ctx, pt, ctx->pubshares[idx])) {
+ return 0;
+ }
+
+ if (!secp256k1_frost_lagrange_coefficient(&l, ctx->ids, ctx->n_pubshares, ctx->ids[idx])) {
+ return 0;
+ }
+
+ *sc = l;
+
+ return 1;
+}
+
+static int secp256k1_frost_evaluate_vss(const secp256k1_context* ctx, secp256k1_gej *share, size_t threshold, const size_t id, const secp256k1_pubkey *vss_commitment) {
+ secp256k1_frost_evaluate_vss_ecmult_data evaluate_vss_ecmult_data;
+
+ ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
+
+ /* Use an EC multi-multiplication to verify the following equation:
+ * 0 = - share_i*G + idx^0*vss_commitment[0]
+ * + ...
+ * + idx^(threshold - 1)*vss_commitment[threshold - 1]*/
+
+ VERIFY_CHECK(ctx != NULL);
+ ARG_CHECK(share != NULL);
+ ARG_CHECK(vss_commitment != NULL);
+ ARG_CHECK(threshold > 1);
+
+ evaluate_vss_ecmult_data.ctx = ctx;
+ evaluate_vss_ecmult_data.vss_commitment = vss_commitment;
+
+ /* Evaluate the public polynomial at the idx */
+ secp256k1_frost_get_scalar_index(&evaluate_vss_ecmult_data.idx, id);
+
+ /* Initialize idx power accumulator to 1 (idx^0) */
+ secp256k1_scalar_set_int(&evaluate_vss_ecmult_data.idxn, 1);
+ /* TODO: add scratch */
+ if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, share, NULL, secp256k1_frost_evaluate_vss_ecmult_callback, (void *) &evaluate_vss_ecmult_data, threshold)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* See RFC 9591, appendix C.2 */
+int secp256k1_frost_share_verify(const secp256k1_context* ctx, size_t threshold, const size_t id, const secp256k1_frost_secshare *share, const secp256k1_pubkey *vss_commitment) {
+ secp256k1_scalar share_i;
+ secp256k1_scalar share_neg;
+ secp256k1_gej tmpj, snj;
+ secp256k1_ge sng;
+
+ VERIFY_CHECK(ctx != NULL);
+ ARG_CHECK(share != NULL);
+ ARG_CHECK(vss_commitment != NULL);
+ ARG_CHECK(threshold > 1);
+
+ if (!secp256k1_frost_share_load(ctx, &share_i, share)) {
+ return 0;
+ }
+
+ if (!secp256k1_frost_evaluate_vss(ctx, &tmpj, threshold, id, vss_commitment)) {
+ return 0;
+ }
+
+ secp256k1_scalar_negate(&share_neg, &share_i);
+ secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &snj, &share_neg);
+ secp256k1_ge_set_gej(&sng, &snj);
+ secp256k1_gej_add_ge(&tmpj, &tmpj, &sng);
+ return secp256k1_gej_is_infinity(&tmpj);
+}
+
+/* See RFC 9591, appendix C.2 */
+int secp256k1_frost_compute_pubshare(const secp256k1_context* ctx, secp256k1_pubkey *pubshare, size_t threshold, const size_t id, const secp256k1_pubkey *vss_commitment) {
+ secp256k1_gej pkj;
+ secp256k1_ge tmp;
+
+ VERIFY_CHECK(ctx != NULL);
+ ARG_CHECK(pubshare != NULL);
+ memset(pubshare, 0, sizeof(*pubshare));
+ ARG_CHECK(vss_commitment != NULL);
+ ARG_CHECK(threshold > 1);
+
+ if (!secp256k1_frost_evaluate_vss(ctx, &pkj, threshold, id, vss_commitment)) {
+ return 0;
+ }
+ secp256k1_ge_set_gej(&tmp, &pkj);
+ secp256k1_pubkey_save(pubshare, &tmp);
+
+ return 1;
+}
+
+int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *agg_pk, const secp256k1_frost_keygen_cache *keygen_cache) {
+ secp256k1_keygen_cache_internal cache_i;
+ VERIFY_CHECK(ctx != NULL);
+ ARG_CHECK(agg_pk != NULL);
+ memset(agg_pk, 0, sizeof(*agg_pk));
+ ARG_CHECK(keygen_cache != NULL);
+
+ if(!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
+ return 0;
+ }
+ secp256k1_pubkey_save(agg_pk, &cache_i.pk);
+ return 1;
+}
+
+int secp256k1_frost_pubkey_gen(const secp256k1_context* ctx, secp256k1_frost_keygen_cache *cache, const secp256k1_pubkey * const *pubshares, size_t n_pubshares, const size_t *ids) {
+ secp256k1_gej pkj;
+ secp256k1_frost_interpolate_pubkey_ecmult_data interpolate_pubkey_ecmult_data;
+ secp256k1_keygen_cache_internal cache_i = { 0 };
+
+ VERIFY_CHECK(ctx != NULL);
+ ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
+ ARG_CHECK(cache != NULL);
+ ARG_CHECK(pubshares != NULL);
+ ARG_CHECK(ids != NULL);
+ ARG_CHECK(n_pubshares > 1);
+
+ interpolate_pubkey_ecmult_data.ctx = ctx;
+ interpolate_pubkey_ecmult_data.pubshares = pubshares;
+ interpolate_pubkey_ecmult_data.ids = ids;
+ interpolate_pubkey_ecmult_data.n_pubshares = n_pubshares;
+
+ /* TODO: add scratch */
+ if (!secp256k1_ecmult_multi_var(&ctx->error_callback, NULL, &pkj, NULL, secp256k1_frost_interpolate_pubkey_ecmult_callback, (void *) &interpolate_pubkey_ecmult_data, n_pubshares)) {
+ return 0;
+ }
+ secp256k1_ge_set_gej(&cache_i.pk, &pkj);
+ secp256k1_keygen_cache_save(cache, &cache_i);
+
+ return 1;
+}
+
+static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32, int xonly) {
+ secp256k1_keygen_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(keygen_cache != NULL);
+ ARG_CHECK(tweak32 != NULL);
+
+ if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_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_keygen_cache_save(keygen_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_keygen_cache *keygen_cache, const unsigned char *tweak32) {
+ return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, keygen_cache, tweak32, 0);
+}
+
+int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32) {
+ return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, keygen_cache, tweak32, 1);
+}
+
+/* TODO: change to void */
+static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const size_t *ids, size_t n_participants, const size_t my_id) {
+ size_t i;
+ secp256k1_scalar num = secp256k1_scalar_one;
+ secp256k1_scalar den = secp256k1_scalar_one;
+ secp256k1_scalar party_idx;
+
+ secp256k1_frost_get_scalar_index(&party_idx, my_id);
+ for (i = 0; i < n_participants; i++) {
+ secp256k1_scalar mul;
+
+ secp256k1_frost_get_scalar_index(&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;
+}
+
+#endif
diff --git a/src/modules/frost/main_impl.h b/src/modules/frost/main_impl.h
new file mode 100644
index 000000000..92824c03a
--- /dev/null
+++ b/src/modules/frost/main_impl.h
@@ -0,0 +1,14 @@
+/**********************************************************************
+ * Copyright (c) 2021-2024 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
diff --git a/src/modules/frost/session.h b/src/modules/frost/session.h
new file mode 100644
index 000000000..82bd915a4
--- /dev/null
+++ b/src/modules/frost/session.h
@@ -0,0 +1,25 @@
+/**********************************************************************
+ * Copyright (c) 2021-2024 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
diff --git a/src/modules/frost/session_impl.h b/src/modules/frost/session_impl.h
new file mode 100644
index 000000000..d70ec79ee
--- /dev/null
+++ b/src/modules/frost/session_impl.h
@@ -0,0 +1,683 @@
+/**********************************************************************
+ * Copyright (c) 2021-2024 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
+
+#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, const 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, const secp256k1_frost_secnonce *secnonce) {
+ int is_zero;
+ ARG_CHECK(secp256k1_memcmp_var(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4) == 0);
+
+ /* We make very sure that the nonce isn't invalidated by checking the values
+ * in addition to the magic. */
+ is_zero = secp256k1_is_zero_array(&secnonce->data[4], 2 * 32);
+ secp256k1_declassify(ctx, &is_zero, sizeof(is_zero));
+ ARG_CHECK(!is_zero);
+
+ secp256k1_scalar_set_b32(&k[0], &secnonce->data[4], NULL);
+ secp256k1_scalar_set_b32(&k[1], &secnonce->data[36], NULL);
+
+ 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, const secp256k1_ge* ges) {
+ int i;
+ memcpy(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4);
+ for (i = 0; i < 2; i++) {
+ secp256k1_ge_to_bytes(nonce->data + 4+64*i, &ges[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* ges, 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_ge_from_bytes(&ges[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_parse(const secp256k1_context* ctx, secp256k1_frost_pubnonce* nonce, const unsigned char *in66) {
+ secp256k1_ge ges[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(&ges[i], &in66[33*i], 33)) {
+ return 0;
+ }
+ if (!secp256k1_ge_is_in_correct_subgroup(&ges[i])) {
+ return 0;
+ }
+ }
+ /* The group elements can not be infinity because they were just parsed */
+ secp256k1_frost_pubnonce_save(nonce, ges);
+ return 1;
+}
+
+int secp256k1_frost_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_frost_pubnonce* nonce) {
+ secp256k1_ge ges[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, ges, nonce)) {
+ return 0;
+ }
+ for (i = 0; i < 2; i++) {
+ int ret;
+ size_t size = 33;
+ ret = secp256k1_eckey_pubkey_serialize(&ges[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_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);
+
+ /* Ensure that using the signature will fail if parsing fails (and the user
+ * doesn't check the return value). */
+ memset(sig, 0, sizeof(*sig));
+
+ secp256k1_scalar_set_b32(&tmp, in32, &overflow);
+ if (overflow) {
+ return 0;
+ }
+ secp256k1_frost_partial_sig_save(sig, &tmp);
+ 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);
+ ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_frost_partial_sig_magic, 4) == 0);
+ memcpy(out32, &sig->data[4], 32);
+ return 1;
+}
+
+/* Write optional inputs into the hash */
+static void secp256k1_nonce_function_frost_helper(secp256k1_sha256 *sha, unsigned int prefix_size, const unsigned char *data32) {
+ /* The spec requires length prefix to be 4 bytes for `extra_in`, 1 byte
+ * otherwise */
+ VERIFY_CHECK(prefix_size == 4 || prefix_size == 1);
+ if (prefix_size == 4) {
+ /* Four byte big-endian value, pad first three bytes with 0 */
+ unsigned char zero[3] = {0};
+ secp256k1_sha256_write(sha, zero, 3);
+ }
+ if (data32 != NULL) {
+ unsigned char len = 32;
+ secp256k1_sha256_write(sha, &len, 1);
+ secp256k1_sha256_write(sha, data32, 32);
+ } else {
+ unsigned char len = 0;
+ secp256k1_sha256_write(sha, &len, 1);
+ }
+}
+
+/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
+ * SHA256 to SHA256("FROST/aux")||SHA256("FROST/aux"). */
+static void secp256k1_nonce_function_frost_sha256_tagged_aux(secp256k1_sha256 *sha) {
+ secp256k1_sha256_initialize(sha);
+ sha->s[0] = 0x3f307d0ful;
+ sha->s[1] = 0xaadb37feul;
+ sha->s[2] = 0xee046f28ul;
+ sha->s[3] = 0x5b291e9cul;
+ sha->s[4] = 0x04f1a312ul;
+ sha->s[5] = 0xe998b71bul;
+ sha->s[6] = 0x44518abful;
+ sha->s[7] = 0xf57558d9ul;
+ sha->bytes = 64;
+}
+
+/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
+ * SHA256 to SHA256("FROST/nonce")||SHA256("FROST/nonce"). */
+static void secp256k1_nonce_function_frost_sha256_tagged(secp256k1_sha256 *sha) {
+ secp256k1_sha256_initialize(sha);
+ sha->s[0] = 0x56d260d6ul;
+ sha->s[1] = 0x9bbae97cul;
+ sha->s[2] = 0xa5ce116cul;
+ sha->s[3] = 0x19f32eeful;
+ sha->s[4] = 0xf995de98ul;
+ sha->s[5] = 0x7f6f8d1aul;
+ sha->s[6] = 0x79ba95aeul;
+ sha->s[7] = 0x1fe66de5ul;
+ sha->bytes = 64;
+}
+
+
+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 rand[32];
+ unsigned char i;
+
+ if (key32 != NULL) {
+ secp256k1_nonce_function_frost_sha256_tagged_aux(&sha);
+ secp256k1_sha256_write(&sha, session_id, 32);
+ secp256k1_sha256_finalize(&sha, rand);
+ for (i = 0; i < 32; i++) {
+ rand[i] ^= key32[i];
+ }
+ } else {
+ memcpy(rand, session_id, sizeof(rand));
+ }
+
+ secp256k1_nonce_function_frost_sha256_tagged(&sha);
+ secp256k1_sha256_write(&sha, rand, sizeof(rand));
+ secp256k1_nonce_function_frost_helper(&sha, 1, pk32);
+ secp256k1_nonce_function_frost_helper(&sha, 1, msg32);
+ secp256k1_nonce_function_frost_helper(&sha, 4, extra_input32);
+
+ for (i = 0; i < 2; i++) {
+ unsigned char buf[32];
+ secp256k1_sha256 sha_tmp = sha;
+ secp256k1_sha256_write(&sha_tmp, &i, 1);
+ secp256k1_sha256_finalize(&sha_tmp, buf);
+ secp256k1_scalar_set_b32(&k[i], buf, NULL);
+
+ /* Attempt to erase secret data */
+ memset(buf, 0, sizeof(buf));
+ memset(&sha_tmp, 0, sizeof(sha_tmp));
+ }
+ memset(rand, 0, sizeof(rand));
+ memset(&sha, 0, sizeof(sha));
+}
+
+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_secshare *share, const unsigned char *msg32, const secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *extra_input32) {
+ secp256k1_scalar k[2];
+ secp256k1_ge nonce_pts[2];
+ secp256k1_gej nonce_ptj[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. */
+ ret &= !secp256k1_is_zero_array(session_id32, 32);
+ }
+
+ /* Check that the share is valid to be able to sign for it later. */
+ if (share != NULL) {
+ 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 (keygen_cache != NULL) {
+ secp256k1_keygen_cache_internal cache_i;
+ if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
+ return 0;
+ }
+ /* The loaded point cache_i.pk can not be the point at infinity. */
+ secp256k1_fe_get_b32(pk_ser, &cache_i.pk.x);
+ 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_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj[i], &k[i]);
+ secp256k1_scalar_clear(&k[i]);
+ }
+
+ /* Batch convert to two public ges */
+ secp256k1_ge_set_all_gej(nonce_pts, nonce_ptj, 2);
+ for (i = 0; i < 2; i++) {
+ secp256k1_gej_clear(&nonce_ptj[i]);
+ }
+
+ for (i = 0; i < 2; i++) {
+ secp256k1_declassify(ctx, &nonce_pts[i], sizeof(nonce_pts[i]));
+ }
+ /* nonce_pts won't be infinity because k != 0 with overwhelming probability */
+ secp256k1_frost_pubnonce_save(pubnonce, nonce_pts);
+ 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_pts[2];
+ if (!secp256k1_frost_pubnonce_load(ctx, nonce_pts, pubnonces[i])) {
+ return 0;
+ }
+ for (j = 0; j < 2; j++) {
+ secp256k1_gej_add_ge_var(&summed_nonces[j], &summed_nonces[j], &nonce_pts[j], NULL);
+ }
+ }
+ return 1;
+}
+
+/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
+ * SHA256 to SHA256("FROST/noncecoef")||SHA256("FROST/noncecoef"). */
+static void secp256k1_frost_compute_noncehash_sha256_tagged(secp256k1_sha256 *sha) {
+ secp256k1_sha256_initialize(sha);
+ sha->s[0] = 0xa123a1caul;
+ sha->s[1] = 0x7dea94e1ul;
+ sha->s[2] = 0x40b0fbf7ul;
+ sha->s[3] = 0x14c389e4ul;
+ sha->s[4] = 0xbd6daae4ul;
+ sha->s[5] = 0xab02db8ful;
+ sha->s[6] = 0x073b4036ul;
+ sha->s[7] = 0x47788b04ul;
+ sha->bytes = 64;
+}
+
+/* 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_frost_compute_noncehash_sha256_tagged(&sha);
+ /* TODO: sort by index */
+ for (i = 0; i < n_pubnonces; i++) {
+ secp256k1_scalar idx;
+
+ secp256k1_frost_get_scalar_index(&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_gej_set_infinity(&fin_nonce_ptj);
+ 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)) {
+ fin_nonce_pt = secp256k1_ge_const_g;
+ }
+ /* fin_nonce_pt is not the point at infinity */
+ 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;
+}
+
+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 size_t my_id, const size_t *ids, const secp256k1_frost_keygen_cache *keygen_cache, const secp256k1_pubkey *adaptor) {
+ secp256k1_keygen_cache_internal cache_i;
+ secp256k1_gej aggnonce_ptj[2];
+ unsigned char fin_nonce[32];
+ secp256k1_frost_session_internal session_i = { 0 };
+ unsigned char pk32[32];
+ 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(keygen_cache != NULL);
+ ARG_CHECK(n_pubnonces > 1);
+
+ if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
+ return 0;
+ }
+ secp256k1_fe_get_b32(pk32, &cache_i.pk.x);
+
+ if (!secp256k1_frost_sum_nonces(ctx, aggnonce_ptj, pubnonces, n_pubnonces)) {
+ return 0;
+ }
+ /* 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 (!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;
+}
+
+static 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_secshare *share, const secp256k1_frost_session *session, const secp256k1_frost_keygen_cache *keygen_cache) {
+ secp256k1_scalar sk;
+ secp256k1_scalar k[2];
+ secp256k1_scalar s;
+ secp256k1_keygen_cache_internal cache_i;
+ 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(keygen_cache != NULL);
+ ARG_CHECK(session != NULL);
+
+ if (!secp256k1_frost_share_load(ctx, &sk, share)) {
+ secp256k1_frost_partial_sign_clear(&sk, k);
+ return 0;
+ }
+ if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
+ secp256k1_frost_partial_sign_clear(&sk, k);
+ return 0;
+ }
+
+ /* Negate sk if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc.
+ * This corresponds to the line "Let d = g⋅gacc⋅d' mod n" in the
+ * specification. */
+ if ((secp256k1_fe_is_odd(&cache_i.pk.y)
+ != cache_i.parity_acc)) {
+ secp256k1_scalar_negate(&sk, &sk);
+ }
+
+ if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
+ secp256k1_frost_partial_sign_clear(&sk, k);
+ return 0;
+ }
+
+ 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_keygen_cache *keygen_cache) {
+ secp256k1_keygen_cache_internal cache_i;
+ secp256k1_frost_session_internal session_i;
+ secp256k1_scalar e, s;
+ secp256k1_gej pkj;
+ secp256k1_ge nonce_pts[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(keygen_cache != 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_pts, pubnonce)) {
+ return 0;
+ }
+ secp256k1_gej_set_ge(&rj, &nonce_pts[1]);
+ secp256k1_ecmult(&rj, &rj, &session_i.noncecoef, NULL);
+ secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pts[0], NULL);
+
+ if (!secp256k1_pubkey_load(ctx, &pkp, pubshare)) {
+ return 0;
+ }
+ if (!secp256k1_keygen_cache_load(ctx, &cache_i, keygen_cache)) {
+ return 0;
+ }
+
+ secp256k1_scalar_set_int(&e, 1);
+ /* Negate e if secp256k1_fe_is_odd(&cache_i.pk.y)) XOR cache_i.parity_acc.
+ * This corresponds to the line "Let g' = g⋅gacc mod n" and the multiplication "g'⋅e"
+ * in the specification. */
+ 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
diff --git a/src/modules/frost/tests_impl.h b/src/modules/frost/tests_impl.h
new file mode 100644
index 000000000..6c1c31bfe
--- /dev/null
+++ b/src/modules/frost/tests_impl.h
@@ -0,0 +1,820 @@
+/***********************************************************************
+ * Copyright (c) 2022-2024 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
+#include
+
+#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 generate, sign, verify
+ * test. */
+static void frost_simple_test(void) {
+ secp256k1_frost_pubnonce pubnonce[5];
+ const secp256k1_frost_pubnonce *pubnonce_ptr[5];
+ unsigned char msg[32];
+ secp256k1_pubkey vss_commitment[3];
+ secp256k1_xonly_pubkey pk_xonly;
+ secp256k1_pubkey pk;
+ unsigned char buf[32];
+ secp256k1_frost_secshare shares[5];
+ secp256k1_frost_secnonce secnonce[5];
+ secp256k1_pubkey pubshare[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];
+ const secp256k1_pubkey *pubshare_ptr[5];
+ secp256k1_frost_keygen_cache cache;
+
+ for (i = 0; i < 5; i++) {
+ pubnonce_ptr[i] = &pubnonce[i];
+ partial_sig_ptr[i] = &partial_sig[i];
+ ids[i] = i;
+ pubshare_ptr[i] = &pubshare[i];
+ }
+ secp256k1_testrand256(buf);
+ CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, buf, 3, 5) == 1);
+ for (i = 0; i < 5; i++) {
+ CHECK(secp256k1_frost_share_verify(CTX, 3, i, &shares[i], vss_commitment) == 1);
+ CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, i, vss_commitment) == 1);
+ }
+ CHECK(secp256k1_frost_pubkey_gen(CTX, &cache, pubshare_ptr, 5, ids) == 1);
+
+ secp256k1_testrand256(msg);
+ for (i = 0; i < 3; i++) {
+ secp256k1_testrand256(buf);
+ CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], buf, &shares[i], NULL, NULL, NULL) == 1);
+ }
+ for (i = 0; i < 3; i++) {
+ CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, i, ids, &cache, NULL) == 1);
+ CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &shares[i], &session, &cache) == 1);
+ CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], &pubshare[i], &session, &cache) == 1);
+ }
+ CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1);
+ CHECK(secp256k1_frost_pubkey_get(CTX, &pk, &cache) == 1);
+ CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_xonly, NULL, &pk) == 1);
+ CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk_xonly) == 1);
+}
+
+static void frost_pubnonce_summing_to_inf(secp256k1_frost_pubnonce *pubnonce) {
+ secp256k1_ge ge[2];
+ int i;
+ secp256k1_gej summed_pubnonces[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_pubnonces, pubnonce_ptr, 2);
+ CHECK(secp256k1_gej_is_infinity(&summed_pubnonces[0]));
+ CHECK(secp256k1_gej_is_infinity(&summed_pubnonces[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;
+}
+
+static 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 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;
+ unsigned char msg[32];
+ secp256k1_pubkey pk;
+ secp256k1_xonly_pubkey pk_xonly;
+ secp256k1_frost_keygen_cache keygen_cache;
+ secp256k1_frost_keygen_cache invalid_keygen_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 vss_commitment[3];
+ const secp256k1_frost_pubnonce *invalid_pubnonce_ptr[5];
+ secp256k1_pubkey invalid_vss_commitment[3];
+ secp256k1_pubkey invalid_pubshare;
+ secp256k1_frost_secshare shares[5];
+ secp256k1_frost_secshare invalid_share;
+ secp256k1_pubkey pubshare[5];
+ int i;
+ size_t ids[5];
+ const secp256k1_pubkey *pubshare_ptr[5];
+
+ /** setup **/
+ memset(max64, 0xff, sizeof(max64));
+ /* Simulate structs being uninitialized by setting it to 0s. We don't want
+ * to produce undefined behavior by actually providing uninitialized
+ * structs. */
+ memset(&invalid_share, 0, sizeof(invalid_share));
+ 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_pubshare, 0, sizeof(invalid_pubshare));
+ memset(&invalid_keygen_cache, 0, sizeof(invalid_keygen_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);
+ CHECK(secp256k1_ec_pubkey_create(CTX, &adaptor, sec_adaptor) == 1);
+ secp256k1_testrand256(seed);
+ 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;
+ pubshare_ptr[i] = &pubshare[i];
+ secp256k1_testrand256(session_id[i]);
+ }
+
+ invalid_partial_sig_ptr[0] = &invalid_partial_sig;
+
+ /** main test body **/
+
+ /** Key generation **/
+ CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5) == 1);
+ for (i = 0; i < 3; i++) {
+ invalid_vss_commitment[i] = vss_commitment[i];
+ }
+ invalid_vss_commitment[0] = invalid_pubshare;
+ CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, NULL, vss_commitment, seed, 3, 5));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, 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_gen(CTX, shares, vss_commitment, 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_gen(CTX, shares, vss_commitment, 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_gen(CTX, shares, vss_commitment, seed, 3, 0));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 2));
+ for (i = 0; i < 2; i++) {
+ CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
+ }
+
+ CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5) == 1);
+
+ /* Share verification */
+ CHECK(secp256k1_frost_share_verify(CTX, 3, 0, &shares[0], vss_commitment) == 1);
+ CHECK(secp256k1_frost_share_verify(CTX, 3, 1, &shares[0], vss_commitment) == 0);
+ CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, 0, NULL, vss_commitment));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, 0, &invalid_share, vss_commitment));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, 0, &shares[0], NULL));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 3, 0, &shares[0], invalid_vss_commitment));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_share_verify(CTX, 0, 4, &shares[0], vss_commitment));
+ CHECK(secp256k1_frost_share_verify(CTX, 3, 5, &shares[0], vss_commitment) == 0);
+
+ /* Compute public verification share */
+ CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, 0, vss_commitment) == 1);
+ CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, NULL, 3, 0, vss_commitment));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, 0, NULL));
+ CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0);
+ CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 3, 0, invalid_vss_commitment));
+ CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0);
+ CHECK_ILLEGAL(CTX, secp256k1_frost_compute_pubshare(CTX, &pubshare[0], 0, 0, vss_commitment));
+ CHECK(frost_memcmp_and_randomize(pubshare[0].data, zeros68, sizeof(pubshare[0].data)) == 0);
+
+ for (i = 0; i < 5; i++) {
+ CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, i, vss_commitment) == 1);
+ pubshare_ptr[i] = &pubshare[i];
+ }
+
+ /* pubkey_gen */
+ CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, NULL, pubshare_ptr, 5, ids));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, &keygen_cache, NULL, 5, ids));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 0, ids));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, NULL));
+ CHECK(secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, ids) == 1);
+
+ /* pubkey_get */
+ CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, NULL, &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, &pk, NULL));
+ CHECK(secp256k1_memcmp_var(&pk, zeros68, sizeof(pk)) == 0);
+ CHECK(secp256k1_frost_pubkey_get(CTX, &pk, &keygen_cache) == 1);
+
+ /* tweak_add */
+ {
+ int (*tweak_func[2]) (const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_keygen_cache *keygen_cache, const unsigned char *tweak32);
+ tweak_func[0] = secp256k1_frost_pubkey_ec_tweak_add;
+ tweak_func[1] = secp256k1_frost_pubkey_xonly_tweak_add;
+
+ for (i = 0; i < 2; i++) {
+ secp256k1_pubkey tmp_output_pk;
+ secp256k1_frost_keygen_cache tmp_keygen_cache = keygen_cache;
+ CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_keygen_cache, tweak) == 1);
+ tmp_keygen_cache = keygen_cache;
+ CHECK((*tweak_func[i])(CTX, NULL, &tmp_keygen_cache, tweak) == 1);
+ tmp_keygen_cache = keygen_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_keygen_cache = keygen_cache;
+ CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &tmp_keygen_cache, NULL));
+ CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
+ tmp_keygen_cache = keygen_cache;
+ CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_keygen_cache, max64) == 0);
+ CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
+ tmp_keygen_cache = keygen_cache;
+ /* Uninitialized keygen_cache */
+ CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &invalid_keygen_cache, tweak));
+ CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
+ }
+ }
+
+ /** Session creation **/
+ CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_xonly, NULL, &pk) == 1);
+
+ for (i = 0; i < 3; i++) {
+ CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id[i], &shares[i], NULL, NULL, NULL) == 1);
+ pubnonce_ptr[i] = &pubnonce[i];
+ }
+
+ invalid_pubnonce_ptr[0] = &invalid_pubnonce;
+ invalid_pubnonce_ptr[1] = &pubnonce[1];
+ invalid_pubnonce_ptr[2] = &pubnonce[2];
+
+ for (i = 0; i < 3; i++) {
+ CHECK(secp256k1_frost_nonce_process(CTX, &session[i], pubnonce_ptr, 3, msg, ids[i], ids, &keygen_cache, &adaptor) == 1);
+ }
+
+ {
+ secp256k1_frost_session tmp_sess;
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, NULL, pubnonce_ptr, 3, msg, ids[0], ids, &keygen_cache, &adaptor));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &tmp_sess, NULL, 3, msg, ids[0], ids, &keygen_cache, &adaptor));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &tmp_sess, pubnonce_ptr, 0, msg, ids[0], ids, &keygen_cache, &adaptor));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &tmp_sess, invalid_pubnonce_ptr, 3, msg, ids[0], ids, &keygen_cache, &adaptor));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &tmp_sess, pubnonce_ptr, 3, NULL, ids[0], ids, &keygen_cache, &adaptor));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &tmp_sess, pubnonce_ptr, 3, msg, ids[0], NULL, &keygen_cache, &adaptor));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &tmp_sess, pubnonce_ptr, 3, msg, ids[0], ids, NULL, &adaptor));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &tmp_sess, pubnonce_ptr, 3, msg, ids[0], ids, &invalid_keygen_cache, &adaptor));
+ CHECK(secp256k1_frost_nonce_process(CTX, &tmp_sess, pubnonce_ptr, 3, msg, ids[0], ids, &keygen_cache, 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);
+ }
+
+ {
+ secp256k1_frost_secnonce scratch_secnonce;
+ secp256k1_frost_pubnonce scratch_pubnonce;
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, NULL, &scratch_pubnonce, session_id[0], &shares[0], NULL, NULL, NULL));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &scratch_secnonce, NULL, session_id[0], &shares[0], NULL, NULL, NULL));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &scratch_secnonce, &scratch_pubnonce, NULL, &shares[0], NULL, NULL, NULL));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &scratch_secnonce, &scratch_pubnonce, session_id[0], &invalid_share, NULL, NULL, NULL));
+ }
+
+ memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
+ CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &keygen_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], &keygen_cache));
+ memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, NULL, &secnonce_tmp, &shares[0], &session[0], &keygen_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], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &invalid_secnonce, &shares[0], &session[0], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, NULL, &session[0], &keygen_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], &keygen_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, &keygen_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, &keygen_cache));
+ 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], NULL));
+ 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_keygen_cache));
+ memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
+
+ CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], &shares[0], &session[0], &keygen_cache) == 1);
+ CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], &shares[1], &session[1], &keygen_cache) == 1);
+ CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], &shares[2], &session[2], &keygen_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_ILLEGAL(CTX, secp256k1_frost_partial_sig_serialize(CTX, buf, &invalid_partial_sig));
+ CHECK(secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], buf) == 1);
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_parse(CTX, NULL, buf));
+ {
+ secp256k1_frost_partial_sig tmp;
+ CHECK(secp256k1_frost_partial_sig_parse(CTX, &tmp, max64) == 0);
+ CHECK(secp256k1_memcmp_var(&tmp, zeros68, sizeof(partial_sig[0])) == 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], &pubshare[0], &session[0], &keygen_cache) == 1);
+ CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[0], &pubshare[0], &session[0], &keygen_cache) == 0);
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, NULL, &pubnonce[0], &pubshare[0], &session[0], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &invalid_partial_sig, &pubnonce[0], &pubshare[0], &session[0], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], NULL, &pubshare[0], &session[0], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &invalid_pubnonce, &pubshare[0], &session[0], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], NULL, &session[0], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &invalid_pubshare, &session[0], &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], NULL, &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &invalid_session, &keygen_cache));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], NULL));
+ CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &invalid_keygen_cache));
+
+ CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshare[0], &session[0], &keygen_cache) == 1);
+ CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], &pubshare[1], &session[1], &keygen_cache) == 1);
+ CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], &pubshare[2], &session[2], &keygen_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_xonly) == 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_xonly) == 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);
+}
+
+static 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 < 5; i++) {
+ CHECK(!secp256k1_scalar_eq(&k[i][0], &k[i][1]));
+ for (j = i+1; j < 5; j++) {
+ CHECK(!secp256k1_scalar_eq(&k[i][0], &k[j][0]));
+ CHECK(!secp256k1_scalar_eq(&k[i][1], &k[j][1]));
+ }
+ }
+}
+
+static void frost_sha256_tag_test_internal(secp256k1_sha256 *sha_tagged, unsigned char *tag, size_t taglen) {
+ secp256k1_sha256 sha;
+ secp256k1_sha256_initialize_tagged(&sha, tag, taglen);
+ test_sha256_eq(&sha, sha_tagged);
+}
+
+/* Checks that the initialized tagged hashes have the expected
+ * state. */
+static void frost_sha256_tag_test(void) {
+ secp256k1_sha256 sha;
+ {
+ unsigned char tag[] = "FROST/aux";
+ secp256k1_nonce_function_frost_sha256_tagged_aux(&sha);
+ frost_sha256_tag_test_internal(&sha, (unsigned char*)tag, sizeof(tag) - 1);
+ }
+ {
+ unsigned char tag[] = "FROST/nonce";
+ secp256k1_nonce_function_frost_sha256_tagged(&sha);
+ frost_sha256_tag_test_internal(&sha, (unsigned char*)tag, sizeof(tag) - 1);
+ }
+ {
+ unsigned char tag[] = "FROST/noncecoef";
+ secp256k1_frost_compute_noncehash_sha256_tagged(&sha);
+ frost_sha256_tag_test_internal(&sha, (unsigned char*)tag, sizeof(tag) - 1);
+ }
+}
+
+/* Attempts to create a signature for the group public key using given secret
+ * shares and keygen_cache. */
+static void frost_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const secp256k1_frost_secshare *sr0, const secp256k1_frost_secshare *sr1, const secp256k1_frost_secshare *sr2, secp256k1_frost_keygen_cache *keygen_cache, const secp256k1_pubkey *sr_pk0, const secp256k1_pubkey *sr_pk1, const secp256k1_pubkey *sr_pk2) {
+ enum {N = 3};
+ size_t ids[N] = {0, 1, 2};
+ unsigned char session_id[N][32];
+ unsigned char msg[32];
+ secp256k1_frost_secnonce secnonce[N];
+ secp256k1_frost_pubnonce pubnonce[N];
+ const secp256k1_frost_pubnonce *pubnonce_ptr[N];
+ secp256k1_frost_session session[N];
+ secp256k1_frost_partial_sig partial_sig[N];
+ const secp256k1_frost_partial_sig *partial_sig_ptr[N];
+ unsigned char final_sig[64];
+ int i;
+ const secp256k1_frost_secshare *shares[N];
+ const secp256k1_pubkey *sr_pks[N];
+ shares[0] = sr0; shares[1] = sr1; shares[2] = sr2;
+ sr_pks[0] = sr_pk0; sr_pks[1] = sr_pk1; sr_pks[2] = sr_pk2;
+
+ for (i = 0; i < N; i++) {
+ secp256k1_testrand256(session_id[i]);
+ }
+ secp256k1_testrand256(msg);
+
+ for (i = 0; i < N; i++) {
+ CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id[i], shares[i], NULL, NULL, NULL) == 1);
+ }
+ for (i = 0; i < N; i++) {
+ pubnonce_ptr[i] = &pubnonce[i];
+ partial_sig_ptr[i] = &partial_sig[i];
+ }
+ for (i = 0; i < N; i++) {
+ CHECK(secp256k1_frost_nonce_process(CTX, &session[i], pubnonce_ptr, N, msg, ids[i], ids, keygen_cache, NULL) == 1);
+ }
+ for (i = 0; i < N; i++) {
+ CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], shares[i], &session[i], keygen_cache) == 1);
+ }
+ for (i = 0; i < N; i++) {
+ CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], sr_pks[i], &session[i], keygen_cache) == 1);
+ }
+ CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session[0], partial_sig_ptr, N) == 1);
+ CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), agg_pk) == 1);
+}
+
+/* Create group public key P[0], tweak multiple times (using xonly and
+ * ordinary tweaking) and test signing. */
+static void frost_tweak_test(void) {
+ secp256k1_pubkey pubshare[5];
+ secp256k1_frost_keygen_cache keygen_cache;
+ enum { N_TWEAKS = 8 };
+ secp256k1_pubkey P[N_TWEAKS + 1];
+ secp256k1_xonly_pubkey P_xonly[N_TWEAKS + 1];
+ unsigned char seed[32];
+ secp256k1_pubkey vss_commitment[3];
+ secp256k1_frost_secshare shares[5];
+ int i;
+ const secp256k1_pubkey *pubshare_ptr[5];
+ size_t ids[5];
+
+ /* Key Setup */
+ for (i = 0; i < 5; i++) {
+ pubshare_ptr[i] = &pubshare[i];
+ ids[i] = (size_t)i;
+ }
+ secp256k1_testrand256(seed);
+ CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5) == 1);
+ for (i = 0; i < 5; i++) {
+ CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, (size_t)i, vss_commitment) == 1);
+ pubshare_ptr[i] = &pubshare[i];
+ }
+ CHECK(secp256k1_frost_pubkey_gen(CTX, &keygen_cache, pubshare_ptr, 5, ids) == 1);
+ CHECK(secp256k1_frost_pubkey_get(CTX, &P[0], &keygen_cache) == 1);
+
+ /* Compute P0 and test signing for it */
+ CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &P_xonly[0], NULL, &P[0]));
+ frost_tweak_test_helper(&P_xonly[0], &shares[0], &shares[1], &shares[2], &keygen_cache, &pubshare[0], &pubshare[1], &pubshare[2]);
+
+ /* 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], &keygen_cache, tweak) == 1);
+ } else {
+ CHECK(secp256k1_frost_pubkey_ec_tweak_add(CTX, &P[i], &keygen_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], &keygen_cache, &pubshare[0], &pubshare[1], &pubshare[2]);
+ }
+}
+
+/* Performs a FROST DKG */
+void frost_dkg_test_helper(secp256k1_frost_keygen_cache *keygen_cache, secp256k1_frost_secshare *shares) {
+ secp256k1_pubkey vss_commitment[3];
+ unsigned char seed[32];
+ int i;
+ secp256k1_pubkey pubshare[5];
+ const secp256k1_pubkey *pubshare_ptr[5];
+ size_t ids[5];
+ secp256k1_testrand256(seed);
+ for (i = 0; i < 5; i++) {
+ pubshare_ptr[i] = &pubshare[i];
+ ids[i] = (size_t)i;
+ }
+ CHECK(secp256k1_frost_shares_gen(CTX, shares, vss_commitment, seed, 3, 5) == 1);
+ for (i = 0; i < 5; i++) {
+ CHECK(secp256k1_frost_compute_pubshare(CTX, &pubshare[i], 3, (size_t)i, vss_commitment) == 1);
+ }
+ CHECK(secp256k1_frost_pubkey_gen(CTX, keygen_cache, pubshare_ptr, 5, ids) == 1);
+}
+
+/* Signs a message with a FROST keypair */
+int frost_sign_test_helper(unsigned char *pre_sig, const secp256k1_frost_secshare *shares, const unsigned char *msg, const secp256k1_pubkey *adaptor, secp256k1_frost_keygen_cache *keygen_cache) {
+ 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[3];
+ const secp256k1_frost_partial_sig *partial_sig_ptr[3];
+ secp256k1_frost_session session[3];
+ size_t ids[3];
+ int i;
+ int nonce_parity;
+ secp256k1_frost_session_internal session_i;
+
+ for (i = 0; i < 3; i++) {
+ pubnonce_ptr[i] = &pubnonce[i];
+ partial_sig_ptr[i] = &partial_sig[i];
+ ids[i] = (size_t)i;
+ }
+
+ for (i = 0; i < 3; i++) {
+ secp256k1_testrand256(session_id[i]);
+ CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id[i], &shares[i], NULL, NULL, NULL) == 1);
+ }
+ for (i = 0; i < 3; i++) {
+ CHECK(secp256k1_frost_nonce_process(CTX, &session[i], pubnonce_ptr, 3, msg, ids[i], ids, keygen_cache, adaptor) == 1);
+ }
+ for (i = 0; i < 3; i++) {
+ CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &shares[i], &session[i], keygen_cache) == 1);
+ }
+ CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 3) == 1);
+
+ CHECK(secp256k1_frost_nonce_parity(CTX, &nonce_parity, &session[0]) == 1);
+
+ CHECK(secp256k1_frost_session_load(CTX, &session_i, &session[0]) == 1);
+
+ 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_secshare shares_a[5];
+ secp256k1_frost_secshare shares_b[5];
+ secp256k1_xonly_pubkey pk_a;
+ secp256k1_xonly_pubkey pk_b;
+ secp256k1_pubkey tmp;
+ 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;
+ secp256k1_frost_keygen_cache cache_a;
+ secp256k1_frost_keygen_cache cache_b;
+
+ /* Alice DKG */
+ frost_dkg_test_helper(&cache_a, shares_a);
+ CHECK(secp256k1_frost_pubkey_get(CTX, &tmp, &cache_a) == 1);
+ CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_a, NULL, &tmp) == 1);
+
+ /* Bob DKG */
+ frost_dkg_test_helper(&cache_b, shares_b);
+ CHECK(secp256k1_frost_pubkey_get(CTX, &tmp, &cache_b) == 1);
+ CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &pk_b, NULL, &tmp) == 1);
+
+ /* 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, shares_a, tx_ab, &l, &cache_a);
+
+ /* Bob setup */
+ CHECK(secp256k1_frost_verify_adaptor(CTX, asig_ab, tx_ab, &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, shares_b, tx_bc, &r, &cache_b);
+
+ /* Carol decrypt */
+ CHECK(secp256k1_frost_verify_adaptor(CTX, asig_bc, tx_bc, &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), &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), &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();
+ }
+
+ frost_sha256_tag_test();
+}
+
+#endif
diff --git a/src/modules/frost/vectors.h b/src/modules/frost/vectors.h
new file mode 100644
index 000000000..91ede2039
--- /dev/null
+++ b/src/modules/frost/vectors.h
@@ -0,0 +1,69 @@
+/**
+ * Automatically generated by ./contrib/frost-vectors.py.
+ *
+ * The test vectors for the FROST implementation.
+ */
+
+enum FROST_ERROR {
+ FROST_PUBKEY,
+ FROST_PUBSHARE,
+ FROST_TWEAK,
+ FROST_PUBNONCE,
+ FROST_AGGNONCE,
+ FROST_SECNONCE,
+ FROST_SIG,
+ FROST_SIG_VERIFY,
+ FROST_OTHER
+};
+
+struct frost_key_gen_valid_test_case {
+ size_t max_participants;
+ size_t min_participants;
+ unsigned char group_public_key[33];
+ size_t participant_identifiers_len;
+ size_t participant_identifiers[5];
+ unsigned char participant_pubshares[5][33];
+ unsigned char participant_secshares[5][32];
+};
+
+struct frost_key_gen_pubshare_fail_test_case {
+ size_t max_participants;
+ size_t min_participants;
+ unsigned char group_public_key[33];
+ size_t participant_identifiers_len;
+ size_t participant_identifiers[5];
+ unsigned char participant_pubshares[5][33];
+ unsigned char participant_secshares[5][32];
+ enum FROST_ERROR error;
+};
+
+struct frost_key_gen_pubkey_fail_test_case {
+ size_t max_participants;
+ size_t min_participants;
+ unsigned char group_public_key[33];
+ size_t participant_identifiers_len;
+ size_t participant_identifiers[5];
+ unsigned char participant_pubshares[5][33];
+ unsigned char participant_secshares[5][32];
+ enum FROST_ERROR error;
+};
+
+struct frost_key_gen_vector {
+ struct frost_key_gen_valid_test_case valid_cases[2];
+ struct frost_key_gen_pubshare_fail_test_case pubshare_fail_cases[1];
+ struct frost_key_gen_pubkey_fail_test_case pubkey_fail_cases[1];
+};
+
+static const struct frost_key_gen_vector frost_key_gen_vector = {
+ {
+ { 3, 2, { 0x02, 0xF3, 0x7C, 0x34, 0xB6, 0x6C, 0xED, 0x1F, 0xB5, 0x1C, 0x34, 0xA9, 0x0B, 0xDA, 0xE0, 0x06, 0x90, 0x1F, 0x10, 0x62, 0x5C, 0xC0, 0x6C, 0x4F, 0x64, 0x66, 0x3B, 0x0E, 0xAE, 0x87, 0xD8, 0x7B, 0x4F }, 3, { 1, 2, 3 }, { { 0x02, 0x6B, 0xAE, 0xE4, 0xBF, 0x7D, 0x4B, 0x9C, 0x45, 0x67, 0xDF, 0xFF, 0x6F, 0x3C, 0x2C, 0x76, 0xDF, 0x5C, 0x08, 0x2E, 0x93, 0x20, 0xCD, 0x81, 0x87, 0xD6, 0xAB, 0x59, 0x65, 0xBC, 0x5A, 0x11, 0x9A }, { 0x03, 0xDA, 0xCC, 0x94, 0x63, 0xE5, 0x18, 0x6F, 0x3C, 0x81, 0xAE, 0x1B, 0x31, 0x4F, 0x7B, 0x09, 0x00, 0x1A, 0x22, 0xB2, 0x8B, 0xB5, 0x6A, 0xD0, 0xAB, 0xD3, 0xF3, 0x76, 0x81, 0x8F, 0x96, 0x04, 0xAB }, { 0x03, 0x14, 0x04, 0x71, 0x0E, 0x93, 0x80, 0x32, 0xDB, 0x0D, 0x4F, 0x6A, 0x4C, 0xD2, 0x0A, 0xE3, 0x73, 0x84, 0xBE, 0x98, 0xBA, 0x9F, 0xE0, 0x5B, 0x42, 0xD1, 0x39, 0x36, 0x12, 0x02, 0xB3, 0x91, 0xE6 } }, { { 0x08, 0xF8, 0x9F, 0xFE, 0x80, 0xAC, 0x94, 0xDC, 0xB9, 0x20, 0xC2, 0x6F, 0x3F, 0x46, 0x14, 0x0B, 0xFC, 0x7F, 0x95, 0xB4, 0x93, 0xF8, 0x31, 0x0F, 0x5F, 0xC1, 0xEA, 0x2B, 0x01, 0xF4, 0x25, 0x4C }, { 0x04, 0xF0, 0xFE, 0xAC, 0x2E, 0xDC, 0xED, 0xC6, 0xCE, 0x12, 0x53, 0xB7, 0xFA, 0xB8, 0xC8, 0x6B, 0x85, 0x6A, 0x79, 0x7F, 0x44, 0xD8, 0x3D, 0x82, 0xA3, 0x85, 0x55, 0x4E, 0x6E, 0x40, 0x19, 0x84 }, { 0x00, 0xE9, 0x5D, 0x59, 0xDD, 0x0D, 0x46, 0xB0, 0xE3, 0x03, 0xE5, 0x00, 0xB6, 0x2B, 0x7C, 0xCB, 0x0E, 0x55, 0x5D, 0x49, 0xF5, 0xB8, 0x49, 0xF5, 0xE7, 0x48, 0xC0, 0x71, 0xDA, 0x8C, 0x0D, 0xBC } } },
+ { 5, 3, { 0x03, 0x79, 0x40, 0xB3, 0xED, 0x1F, 0xDC, 0x36, 0x02, 0x52, 0xA6, 0xF4, 0x80, 0x58, 0xC7, 0xB9, 0x42, 0x76, 0xDF, 0xB6, 0xAA, 0x2B, 0x7D, 0x51, 0x70, 0x6F, 0xB4, 0x83, 0x26, 0xB1, 0x9E, 0x7A, 0xE1 }, 5, { 1, 2, 3, 4, 5 }, { { 0x02, 0xBB, 0x66, 0x43, 0x7F, 0xCA, 0xA0, 0x12, 0x92, 0xBF, 0xB4, 0xBB, 0x6F, 0x19, 0xD6, 0x78, 0x18, 0xFE, 0x69, 0x32, 0x15, 0xC3, 0x6C, 0x46, 0x63, 0x85, 0x7F, 0x1D, 0xC8, 0xAB, 0x8B, 0xF4, 0xFA }, { 0x02, 0xC3, 0x25, 0x00, 0x13, 0xC8, 0x6A, 0xA9, 0xC3, 0x01, 0x1C, 0xD4, 0x0B, 0x26, 0x58, 0xCB, 0xC5, 0xB9, 0x50, 0xDD, 0x21, 0xFF, 0xAA, 0x4E, 0xDE, 0x1B, 0xB6, 0x6E, 0x18, 0xA0, 0x63, 0xCE, 0xD5 }, { 0x03, 0x25, 0x9D, 0x70, 0x68, 0x33, 0x50, 0x12, 0xC0, 0x8C, 0x5D, 0x80, 0xE1, 0x81, 0x96, 0x9E, 0xD7, 0xFF, 0xA0, 0x8F, 0x79, 0x73, 0xE3, 0xED, 0x9C, 0x8C, 0x0B, 0xFF, 0x3E, 0xC0, 0x3C, 0x22, 0x3E }, { 0x02, 0xA2, 0x29, 0x71, 0x75, 0x02, 0x42, 0xF6, 0xDA, 0x35, 0xB8, 0xDB, 0x0D, 0xFE, 0x74, 0xF3, 0x8A, 0x32, 0x27, 0x11, 0x8B, 0x29, 0x6A, 0xDD, 0x2C, 0x65, 0xE3, 0x24, 0xE2, 0xB7, 0xEB, 0x20, 0xAD }, { 0x03, 0x54, 0x12, 0x93, 0x53, 0x5B, 0xB6, 0x62, 0xF8, 0x29, 0x4C, 0x4B, 0xEB, 0x7E, 0xA2, 0x5F, 0x55, 0xFE, 0xAE, 0x86, 0xC6, 0xBA, 0xE0, 0xCE, 0xBD, 0x74, 0x1E, 0xAA, 0xA2, 0x86, 0x39, 0xA6, 0xE6 } }, { { 0x81, 0xD0, 0xD4, 0x0C, 0xDF, 0x04, 0x45, 0x88, 0x16, 0x7A, 0x98, 0x7C, 0x14, 0x55, 0x29, 0x54, 0xDB, 0x18, 0x7A, 0xC5, 0xAD, 0x3B, 0x1C, 0xA4, 0x0D, 0x7B, 0x03, 0xDC, 0xA3, 0x2A, 0xFD, 0xFB }, { 0x10, 0x13, 0x04, 0x12, 0xFD, 0xB9, 0xA1, 0x0F, 0x7D, 0xF8, 0x62, 0xCE, 0x87, 0x63, 0x31, 0x1B, 0x7D, 0x1B, 0x7A, 0xAC, 0xF2, 0x11, 0xED, 0x32, 0x27, 0x2F, 0x0D, 0xAC, 0x49, 0xDF, 0x67, 0x43 }, { 0x13, 0x62, 0xA1, 0x4A, 0xE0, 0x72, 0x43, 0xC9, 0x3C, 0x24, 0xE7, 0xEE, 0xA3, 0xFB, 0x8C, 0x61, 0x93, 0x38, 0xC2, 0x49, 0x25, 0xF8, 0xE5, 0xE4, 0x88, 0xDA, 0xE1, 0xD3, 0xDE, 0x7B, 0x22, 0x36 }, { 0x8B, 0xBF, 0xAB, 0xB4, 0x87, 0x2E, 0x2D, 0xB5, 0x51, 0x00, 0x27, 0xDC, 0x6A, 0x1E, 0x3B, 0x27, 0x1D, 0x70, 0x51, 0x9A, 0x48, 0xF0, 0x06, 0xBB, 0x32, 0x7E, 0x80, 0x53, 0x60, 0xFE, 0x2E, 0xD4 }, { 0x79, 0x2A, 0x23, 0x4F, 0xF1, 0xED, 0x5E, 0xD3, 0xBC, 0x8A, 0x22, 0x97, 0xD9, 0xCB, 0x3D, 0x6D, 0x61, 0x13, 0x4B, 0xB9, 0xAB, 0xAE, 0xAF, 0x7A, 0x64, 0x47, 0x8A, 0x9E, 0x01, 0x32, 0x4B, 0xDC } } },
+ },
+ {
+ { 2, 2, { 0x02, 0x56, 0xC9, 0x2C, 0xA1, 0x8A, 0xD1, 0x8E, 0x5E, 0x14, 0x07, 0x5E, 0x4C, 0xDA, 0x4C, 0x94, 0x71, 0xE1, 0xF6, 0x9E, 0xFF, 0x06, 0xDA, 0x31, 0xB9, 0xDB, 0x8C, 0x43, 0x16, 0x97, 0x45, 0x7C, 0x96 }, 2, { 1, 2 }, { { 0x02, 0xEF, 0x27, 0x11, 0x68, 0x68, 0xEE, 0xC7, 0x2F, 0x1A, 0xEF, 0x13, 0xF0, 0x38, 0x3A, 0x83, 0x47, 0x9D, 0xB7, 0xDF, 0xBD, 0xE5, 0x5B, 0x56, 0x8A, 0xDC, 0x0A, 0xBC, 0x28, 0xB0, 0xC8, 0x2A, 0xEB }, { 0x03, 0x81, 0xEE, 0x46, 0xDB, 0x95, 0x82, 0xB6, 0xAA, 0x84, 0xAB, 0x1F, 0x39, 0xCA, 0xAD, 0x93, 0x08, 0x99, 0xB4, 0x4A, 0xCC, 0xB7, 0x5E, 0xDF, 0xFB, 0xB2, 0x9C, 0xDB, 0x8E, 0x21, 0x36, 0xF2, 0xA7 } }, { { 0x19, 0x03, 0x09, 0x72, 0x97, 0xA1, 0xE0, 0xFD, 0x75, 0xFC, 0xBC, 0xDB, 0x66, 0xDC, 0x21, 0xC6, 0x5A, 0xCE, 0xC5, 0x27, 0x10, 0x05, 0x66, 0x45, 0x9F, 0x1B, 0xBF, 0x2F, 0xA7, 0x38, 0x8D, 0x53 }, { 0xB9, 0xB2, 0xCD, 0x71, 0xF1, 0xC0, 0x9B, 0x8D, 0x6F, 0x67, 0x5D, 0x05, 0xCD, 0xF1, 0x39, 0x6B, 0x28, 0xFF, 0x62, 0x6C, 0xD8, 0xC6, 0x9B, 0x9D, 0xF4, 0xD3, 0xB6, 0xBD, 0xCB, 0x57, 0xEF, 0xF2 } }, FROST_PUBSHARE },
+ },
+ {
+ { 3, 3, { 0x03, 0x54, 0xF1, 0xE6, 0x7A, 0xAF, 0xFB, 0x49, 0x65, 0x4A, 0xF3, 0xEE, 0x5B, 0x0C, 0x68, 0xD8, 0xCF, 0x24, 0x46, 0x8D, 0x01, 0x44, 0x53, 0xF1, 0xF1, 0x3B, 0x52, 0x21, 0x51, 0x2A, 0x0B, 0xCE, 0x78 }, 3, { 1, 2, 3 }, { { 0x03, 0x7A, 0x01, 0xFF, 0x27, 0x05, 0xD6, 0x79, 0xCD, 0xC3, 0x4E, 0x04, 0x36, 0x6C, 0xC3, 0xBA, 0x95, 0xBD, 0x9E, 0x88, 0x3A, 0xC7, 0xE3, 0x3B, 0x64, 0x0D, 0x74, 0x4B, 0xE6, 0xBC, 0xC2, 0xD1, 0x40 }, { 0x03, 0x9E, 0x2C, 0x0A, 0xE4, 0x4E, 0xA1, 0x20, 0x36, 0x06, 0xD0, 0x4B, 0x71, 0x16, 0x67, 0xC0, 0x7D, 0x16, 0x95, 0xAD, 0xC3, 0x6F, 0xBF, 0x07, 0xDD, 0x37, 0xB7, 0xEC, 0xA8, 0x54, 0x90, 0x26, 0x2C }, { 0x02, 0x7C, 0x78, 0x26, 0x38, 0xAD, 0x6A, 0x8A, 0x95, 0xDE, 0xDF, 0x6C, 0xBA, 0x94, 0x0E, 0x89, 0xE8, 0x27, 0xEC, 0x5C, 0x4F, 0xCF, 0x69, 0x3E, 0xAB, 0x7D, 0x70, 0x92, 0x7C, 0x3C, 0xA5, 0x9F, 0xDB } }, { { 0xA3, 0x23, 0x6A, 0x9D, 0x6E, 0xF2, 0x52, 0xA5, 0xC5, 0x9F, 0x17, 0xB5, 0x44, 0xEC, 0xE3, 0x94, 0x87, 0xFF, 0xD8, 0x0F, 0x15, 0x8E, 0xB9, 0x3F, 0x8A, 0xA4, 0xAF, 0x70, 0x7B, 0xFA, 0x55, 0x11 }, { 0x7F, 0xA1, 0xBE, 0x8B, 0xCC, 0x29, 0x55, 0x5E, 0xFA, 0xAC, 0x4B, 0x19, 0xD4, 0x7E, 0x26, 0x46, 0x7E, 0x05, 0x6B, 0x9D, 0xE2, 0xF6, 0xE0, 0xB7, 0xB8, 0x44, 0x94, 0x0F, 0xD4, 0x3D, 0x10, 0x47 }, { 0x8B, 0xAC, 0xD7, 0x27, 0xEA, 0x7C, 0x21, 0x56, 0xF4, 0x76, 0xBF, 0xC8, 0xEF, 0x5B, 0x33, 0x2F, 0xE0, 0x66, 0x34, 0x64, 0xAC, 0x3F, 0x11, 0x7C, 0x0B, 0x69, 0xD6, 0x46, 0x0A, 0x4A, 0xD2, 0x5D } }, FROST_PUBKEY },
+ },
+};
+enum { FROST_VECTORS_MAX_PARTICIPANTS = 5 };
diff --git a/src/modules/musig/keyagg.h b/src/modules/musig/keyagg.h
index 620522feb..1497659d4 100644
--- a/src/modules/musig/keyagg.h
+++ b/src/modules/musig/keyagg.h
@@ -27,12 +27,6 @@ typedef struct {
int parity_acc;
} 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 void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_keyagg_cache_internal *cache_i, secp256k1_ge *pk);
diff --git a/src/modules/musig/keyagg_impl.h b/src/modules/musig/keyagg_impl.h
index aff955420..1c221d77b 100644
--- a/src/modules/musig/keyagg_impl.h
+++ b/src/modules/musig/keyagg_impl.h
@@ -17,23 +17,6 @@
#include "../../hash.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 };
/* A keyagg cache consists of
@@ -52,7 +35,7 @@ static void secp256k1_keyagg_cache_save(secp256k1_musig_keyagg_cache *cache, sec
ptr += 4;
secp256k1_ge_to_bytes(ptr, &cache_i->pk);
ptr += 64;
- secp256k1_point_save_ext(ptr, &cache_i->second_pk);
+ secp256k1_ge_to_bytes_ext(ptr, &cache_i->second_pk);
ptr += 64;
memcpy(ptr, cache_i->pk_hash, 32);
ptr += 32;
@@ -67,7 +50,7 @@ static int secp256k1_keyagg_cache_load(const secp256k1_context* ctx, secp256k1_k
ptr += 4;
secp256k1_ge_from_bytes(&cache_i->pk, ptr);
ptr += 64;
- secp256k1_point_load_ext(&cache_i->second_pk, ptr);
+ secp256k1_ge_from_bytes_ext(&cache_i->second_pk, ptr);
ptr += 64;
memcpy(cache_i->pk_hash, ptr, 32);
ptr += 32;
diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h
index ff87f2fd3..cab3376f9 100644
--- a/src/modules/musig/session_impl.h
+++ b/src/modules/musig/session_impl.h
@@ -84,7 +84,7 @@ static void secp256k1_musig_aggnonce_save(secp256k1_musig_aggnonce* nonce, secp2
int i;
memcpy(&nonce->data[0], secp256k1_musig_aggnonce_magic, 4);
for (i = 0; i < 2; i++) {
- secp256k1_point_save_ext(&nonce->data[4 + 64*i], &ge[i]);
+ secp256k1_ge_to_bytes_ext(&nonce->data[4 + 64*i], &ge[i]);
}
}
@@ -93,7 +93,7 @@ static int secp256k1_musig_aggnonce_load(const secp256k1_context* ctx, secp256k1
ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_musig_aggnonce_magic, 4) == 0);
for (i = 0; i < 2; i++) {
- secp256k1_point_load_ext(&ge[i], &nonce->data[4 + 64*i]);
+ secp256k1_ge_from_bytes_ext(&ge[i], &nonce->data[4 + 64*i]);
}
return 1;
}
diff --git a/src/secp256k1.c b/src/secp256k1.c
index 4c5782693..783b59cc9 100644
--- a/src/secp256k1.c
+++ b/src/secp256k1.c
@@ -908,3 +908,7 @@ static int secp256k1_ge_parse_ext(secp256k1_ge* ge, const unsigned char *in33) {
#ifdef ENABLE_MODULE_SURJECTIONPROOF
# include "modules/surjection/main_impl.h"
#endif
+
+#ifdef ENABLE_MODULE_FROST
+# include "modules/frost/main_impl.h"
+#endif
diff --git a/src/tests.c b/src/tests.c
index 7c2f30e35..16ad519e5 100644
--- a/src/tests.c
+++ b/src/tests.c
@@ -3912,14 +3912,27 @@ static void test_ge(void) {
/* Test batch gej -> ge conversion without known z ratios. */
{
+ secp256k1_ge *ge_set_all_var = (secp256k1_ge *)checked_malloc(&CTX->error_callback, (4 * runs + 1) * sizeof(secp256k1_ge));
secp256k1_ge *ge_set_all = (secp256k1_ge *)checked_malloc(&CTX->error_callback, (4 * runs + 1) * sizeof(secp256k1_ge));
- secp256k1_ge_set_all_gej_var(ge_set_all, gej, 4 * runs + 1);
+ secp256k1_ge_set_all_gej_var(&ge_set_all_var[0], &gej[0], 4 * runs + 1);
for (i = 0; i < 4 * runs + 1; i++) {
+ secp256k1_fe s;
+ random_fe_non_zero(&s);
+ secp256k1_gej_rescale(&gej[i], &s);
+ CHECK(secp256k1_gej_eq_ge_var(&gej[i], &ge_set_all_var[i]));
+ }
+
+ /* Skip infinity at &gej[0]. */
+ secp256k1_ge_set_all_gej(&ge_set_all[1], &gej[1], 4 * runs);
+ for (i = 1; i < 4 * runs + 1; i++) {
secp256k1_fe s;
random_fe_non_zero(&s);
secp256k1_gej_rescale(&gej[i], &s);
CHECK(secp256k1_gej_eq_ge_var(&gej[i], &ge_set_all[i]));
+ CHECK(secp256k1_ge_eq_var(&ge_set_all_var[i], &ge_set_all[i]));
}
+
+ free(ge_set_all_var);
free(ge_set_all);
}
@@ -4075,13 +4088,27 @@ static void test_add_neg_y_diff_x(void) {
static void test_ge_bytes(void) {
int i;
- for (i = 0; i < COUNT; i++) {
+ for (i = 0; i < COUNT + 1; i++) {
unsigned char buf[64];
secp256k1_ge p, q;
- random_group_element_test(&p);
- secp256k1_ge_to_bytes(buf, &p);
- secp256k1_ge_from_bytes(&q, buf);
+ if (i == 0) {
+ secp256k1_ge_set_infinity(&p);
+ } else {
+ random_group_element_test(&p);
+ }
+
+ if (!secp256k1_ge_is_infinity(&p)) {
+ secp256k1_ge_to_bytes(buf, &p);
+
+ secp256k1_ge_from_bytes(&q, buf);
+ CHECK(secp256k1_ge_eq_var(&p, &q));
+
+ secp256k1_ge_from_bytes_ext(&q, buf);
+ CHECK(secp256k1_ge_eq_var(&p, &q));
+ }
+ secp256k1_ge_to_bytes_ext(buf, &p);
+ secp256k1_ge_from_bytes_ext(&q, buf);
CHECK(secp256k1_ge_eq_var(&p, &q));
}
}
@@ -7502,6 +7529,10 @@ static void run_ecdsa_wycheproof(void) {
# include "modules/ecdsa_adaptor/tests_impl.h"
#endif
+#ifdef ENABLE_MODULE_FROST
+# include "modules/frost/tests_impl.h"
+#endif
+
static void run_secp256k1_memczero_test(void) {
unsigned char buf1[6] = {1, 2, 3, 4, 5, 6};
unsigned char buf2[sizeof(buf1)];
@@ -7517,6 +7548,18 @@ static void run_secp256k1_memczero_test(void) {
CHECK(secp256k1_memcmp_var(buf1, buf2, sizeof(buf1)) == 0);
}
+
+static void run_secp256k1_is_zero_array_test(void) {
+ unsigned char buf1[3] = {0, 1};
+ unsigned char buf2[3] = {1, 0};
+
+ CHECK(secp256k1_is_zero_array(buf1, 0) == 1);
+ CHECK(secp256k1_is_zero_array(buf1, 1) == 1);
+ CHECK(secp256k1_is_zero_array(buf1, 2) == 0);
+ CHECK(secp256k1_is_zero_array(buf2, 1) == 0);
+ CHECK(secp256k1_is_zero_array(buf2, 2) == 0);
+}
+
static void run_secp256k1_byteorder_tests(void) {
{
const uint32_t x = 0xFF03AB45;
@@ -7892,8 +7935,13 @@ int main(int argc, char **argv) {
run_ecdsa_adaptor_tests();
#endif
+#ifdef ENABLE_MODULE_FROST
+ run_frost_tests();
+#endif
+
/* util tests */
run_secp256k1_memczero_test();
+ run_secp256k1_is_zero_array_test();
run_secp256k1_byteorder_tests();
run_cmov_tests();
diff --git a/src/util.h b/src/util.h
index 10ea51605..5c132eed0 100644
--- a/src/util.h
+++ b/src/util.h
@@ -262,6 +262,22 @@ static SECP256K1_INLINE int secp256k1_memcmp_var(const void *s1, const void *s2,
return 0;
}
+/* Return 1 if all elements of array s are 0 and otherwise return 0.
+ * Constant-time. */
+static SECP256K1_INLINE int secp256k1_is_zero_array(const unsigned char *s, size_t len) {
+ unsigned char acc = 0;
+ int ret;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ acc |= s[i];
+ }
+ ret = (acc == 0);
+ /* acc may contain secret values. Try to explicitly clear it. */
+ acc = 0;
+ return ret;
+}
+
/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized and non-negative.*/
static SECP256K1_INLINE void secp256k1_int_cmov(int *r, const int *a, int flag) {
unsigned int mask0, mask1, r_masked, a_masked;