Compare commits
11 Commits
frost
...
frost-trus
Author | SHA1 | Date | |
---|---|---|---|
|
536b0458ad | ||
|
d3ef472559 | ||
|
e94367c83e | ||
|
1b9567289b | ||
|
fb34b29d7f | ||
|
9170dd337c | ||
|
ea059393f0 | ||
|
5368c81a3c | ||
|
9b852191de | ||
|
8969cee21c | ||
|
6f47d2eb22 |
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
@ -41,6 +41,7 @@ env:
|
|||||||
ECDSAADAPTOR: 'no'
|
ECDSAADAPTOR: 'no'
|
||||||
BPPP: 'no'
|
BPPP: 'no'
|
||||||
SCHNORRSIG_HALFAGG: 'no'
|
SCHNORRSIG_HALFAGG: 'no'
|
||||||
|
FROST: 'no'
|
||||||
### test options
|
### test options
|
||||||
SECP256K1_TEST_ITERS:
|
SECP256K1_TEST_ITERS:
|
||||||
BENCH: 'yes'
|
BENCH: 'yes'
|
||||||
@ -79,14 +80,14 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
configuration:
|
configuration:
|
||||||
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
|
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
|
||||||
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'}
|
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
|
||||||
- env_vars: { WIDEMUL: 'int128' }
|
- env_vars: { WIDEMUL: 'int128' }
|
||||||
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
|
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
|
||||||
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
|
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes' }
|
||||||
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'}
|
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
|
||||||
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
|
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
|
||||||
- env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes'}
|
- env_vars: { RECOVERY: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
|
||||||
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CPPFLAGS: '-DVERIFY' }
|
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CPPFLAGS: '-DVERIFY' }
|
||||||
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
|
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
|
||||||
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
|
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
|
||||||
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
|
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
|
||||||
@ -158,6 +159,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CC: ${{ matrix.cc }}
|
CC: ${{ matrix.cc }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -211,6 +213,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'no'
|
CTIMETESTS: 'no'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -271,6 +274,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'no'
|
CTIMETESTS: 'no'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -325,6 +329,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'no'
|
CTIMETESTS: 'no'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -389,6 +394,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'no'
|
CTIMETESTS: 'no'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -450,6 +456,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'no'
|
CTIMETESTS: 'no'
|
||||||
SECP256K1_TEST_ITERS: 2
|
SECP256K1_TEST_ITERS: 2
|
||||||
|
|
||||||
@ -510,6 +517,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'no'
|
CTIMETESTS: 'no'
|
||||||
CFLAGS: '-fsanitize=undefined,address -g'
|
CFLAGS: '-fsanitize=undefined,address -g'
|
||||||
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
|
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
|
||||||
@ -576,6 +584,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'yes'
|
CTIMETESTS: 'yes'
|
||||||
CC: 'clang'
|
CC: 'clang'
|
||||||
SECP256K1_TEST_ITERS: 32
|
SECP256K1_TEST_ITERS: 32
|
||||||
@ -632,6 +641,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
CTIMETESTS: 'no'
|
CTIMETESTS: 'no'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -688,15 +698,15 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
env_vars:
|
env_vars:
|
||||||
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' }
|
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
|
||||||
- { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 }
|
- { WIDEMUL: 'int128_struct', ECMULTGENPRECISION: 2, ECMULTWINDOW: 4 }
|
||||||
- { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' }
|
- { WIDEMUL: 'int128', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
|
||||||
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
|
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
|
||||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' }
|
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes' }
|
||||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc' }
|
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CC: 'gcc' }
|
||||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
|
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
|
||||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
|
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
|
||||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
|
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', SCHNORRSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', MUSIG: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
|
||||||
- BUILD: 'distcheck'
|
- BUILD: 'distcheck'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -816,6 +826,7 @@ jobs:
|
|||||||
ECDSAADAPTOR: 'yes'
|
ECDSAADAPTOR: 'yes'
|
||||||
BPPP: 'yes'
|
BPPP: 'yes'
|
||||||
SCHNORRSIG_HALFAGG: 'yes'
|
SCHNORRSIG_HALFAGG: 'yes'
|
||||||
|
FROST: 'yes'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -66,6 +66,7 @@ libsecp256k1.pc
|
|||||||
contrib/gh-pr-create.sh
|
contrib/gh-pr-create.sh
|
||||||
|
|
||||||
musig_example
|
musig_example
|
||||||
|
frost_example
|
||||||
|
|
||||||
### CMake
|
### CMake
|
||||||
/CMakeUserPresets.json
|
/CMakeUserPresets.json
|
||||||
|
15
Makefile.am
15
Makefile.am
@ -195,6 +195,17 @@ musig_example_LDFLAGS += -lbcrypt
|
|||||||
endif
|
endif
|
||||||
TESTS += musig_example
|
TESTS += musig_example
|
||||||
endif
|
endif
|
||||||
|
if ENABLE_MODULE_FROST
|
||||||
|
noinst_PROGRAMS += frost_example
|
||||||
|
frost_example_SOURCES = examples/frost.c
|
||||||
|
frost_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||||
|
frost_example_LDADD = libsecp256k1.la
|
||||||
|
frost_example_LDFLAGS = -static
|
||||||
|
if BUILD_WINDOWS
|
||||||
|
frost_example_LDFLAGS += -lbcrypt
|
||||||
|
endif
|
||||||
|
TESTS += frost_example
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### Precomputed tables
|
### Precomputed tables
|
||||||
@ -320,3 +331,7 @@ endif
|
|||||||
if ENABLE_MODULE_ECDSA_ADAPTOR
|
if ENABLE_MODULE_ECDSA_ADAPTOR
|
||||||
include src/modules/ecdsa_adaptor/Makefile.am.include
|
include src/modules/ecdsa_adaptor/Makefile.am.include
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if ENABLE_MODULE_FROST
|
||||||
|
include src/modules/frost/Makefile.am.include
|
||||||
|
endif
|
||||||
|
@ -12,6 +12,7 @@ Added features:
|
|||||||
* Experimental module for Confidential Assets (Pedersen commitments, range proofs, and [surjection proofs](src/modules/surjection/surjection.md)).
|
* Experimental module for Confidential Assets (Pedersen commitments, range proofs, and [surjection proofs](src/modules/surjection/surjection.md)).
|
||||||
* Experimental module for Bulletproofs++ range proofs.
|
* Experimental module for Bulletproofs++ range proofs.
|
||||||
* Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md).
|
* Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md).
|
||||||
|
* Experimental module for FROST.
|
||||||
|
|
||||||
Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable.
|
Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable.
|
||||||
|
|
||||||
|
3
ci/ci.sh
3
ci/ci.sh
@ -14,7 +14,7 @@ print_environment() {
|
|||||||
for var in WERROR_CFLAGS MAKEFLAGS BUILD \
|
for var in WERROR_CFLAGS MAKEFLAGS BUILD \
|
||||||
ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
|
ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
|
||||||
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG SCHNORRSIG_HALFAGG ELLSWIFT \
|
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG SCHNORRSIG_HALFAGG ELLSWIFT \
|
||||||
ECDSA_S2C GENERATOR RANGEPROOF WHITELIST MUSIG ECDSAADAPTOR BPPP \
|
ECDSA_S2C GENERATOR RANGEPROOF WHITELIST MUSIG ECDSAADAPTOR BPPP FROST \
|
||||||
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
|
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
|
||||||
EXAMPLES \
|
EXAMPLES \
|
||||||
HOST WRAPPER_CMD \
|
HOST WRAPPER_CMD \
|
||||||
@ -83,6 +83,7 @@ esac
|
|||||||
--enable-module-schnorrsig="$SCHNORRSIG" --enable-module-musig="$MUSIG" --enable-module-ecdsa-adaptor="$ECDSAADAPTOR" \
|
--enable-module-schnorrsig="$SCHNORRSIG" --enable-module-musig="$MUSIG" --enable-module-ecdsa-adaptor="$ECDSAADAPTOR" \
|
||||||
--enable-module-schnorrsig="$SCHNORRSIG" \
|
--enable-module-schnorrsig="$SCHNORRSIG" \
|
||||||
--enable-module-schnorrsig-halfagg="$SCHNORRSIG_HALFAGG" \
|
--enable-module-schnorrsig-halfagg="$SCHNORRSIG_HALFAGG" \
|
||||||
|
--enable-module-frost="$FROST" \
|
||||||
--enable-examples="$EXAMPLES" \
|
--enable-examples="$EXAMPLES" \
|
||||||
--enable-ctime-tests="$CTIMETESTS" \
|
--enable-ctime-tests="$CTIMETESTS" \
|
||||||
--with-valgrind="$WITH_VALGRIND" \
|
--with-valgrind="$WITH_VALGRIND" \
|
||||||
|
18
configure.ac
18
configure.ac
@ -236,6 +236,11 @@ AC_ARG_ENABLE(module_ecdsa-adaptor,
|
|||||||
[],
|
[],
|
||||||
[SECP_SET_DEFAULT([enable_module_ecdsa_adaptor], [no], [yes])])
|
[SECP_SET_DEFAULT([enable_module_ecdsa_adaptor], [no], [yes])])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(module_frost,
|
||||||
|
AS_HELP_STRING([--enable-module-frost],[enable FROST module [default=no]]),
|
||||||
|
[],
|
||||||
|
[SECP_SET_DEFAULT([enable_module_frost], [no], [yes])])
|
||||||
|
|
||||||
AC_ARG_ENABLE(external_default_callbacks,
|
AC_ARG_ENABLE(external_default_callbacks,
|
||||||
AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
|
AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [],
|
||||||
[SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
|
[SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])])
|
||||||
@ -470,6 +475,14 @@ if test x"$enable_module_ecdsa_adaptor" = x"yes"; then
|
|||||||
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDSA_ADAPTOR=1"
|
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ECDSA_ADAPTOR=1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if test x"$enable_module_frost" = x"yes"; then
|
||||||
|
if test x"$enable_module_schnorrsig" = x"no"; then
|
||||||
|
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
|
||||||
|
fi
|
||||||
|
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_FROST=1"
|
||||||
|
enable_module_schnorrsig=yes
|
||||||
|
fi
|
||||||
|
|
||||||
if test x"$enable_module_musig" = x"yes"; then
|
if test x"$enable_module_musig" = x"yes"; then
|
||||||
if test x"$enable_module_schnorrsig" = x"no"; then
|
if test x"$enable_module_schnorrsig" = x"no"; then
|
||||||
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
|
AC_MSG_ERROR([Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.])
|
||||||
@ -579,6 +592,9 @@ else
|
|||||||
if test x"$enable_module_generator" = x"yes"; then
|
if test x"$enable_module_generator" = x"yes"; then
|
||||||
AC_MSG_ERROR([NUMS generator module is experimental. Use --enable-experimental to allow.])
|
AC_MSG_ERROR([NUMS generator module is experimental. Use --enable-experimental to allow.])
|
||||||
fi
|
fi
|
||||||
|
if test x"$enable_module_frost" = x"yes"; then
|
||||||
|
AC_MSG_ERROR([FROST module is experimental. Use --enable-experimental to allow.])
|
||||||
|
fi
|
||||||
if test x"$set_asm" = x"arm32"; then
|
if test x"$set_asm" = x"arm32"; then
|
||||||
AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.])
|
AC_MSG_ERROR([ARM32 assembly is experimental. Use --enable-experimental to allow.])
|
||||||
fi
|
fi
|
||||||
@ -611,6 +627,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDSA_S2C], [test x"$enable_module_ecdsa_s2c" = x"
|
|||||||
AM_CONDITIONAL([ENABLE_MODULE_ECDSA_ADAPTOR], [test x"$enable_module_ecdsa_adaptor" = x"yes"])
|
AM_CONDITIONAL([ENABLE_MODULE_ECDSA_ADAPTOR], [test x"$enable_module_ecdsa_adaptor" = x"yes"])
|
||||||
AM_CONDITIONAL([ENABLE_MODULE_BPPP], [test x"$enable_module_bppp" = x"yes"])
|
AM_CONDITIONAL([ENABLE_MODULE_BPPP], [test x"$enable_module_bppp" = x"yes"])
|
||||||
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG_HALFAGG], [test x"$enable_module_schnorrsig_halfagg" = x"yes"])
|
AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG_HALFAGG], [test x"$enable_module_schnorrsig_halfagg" = x"yes"])
|
||||||
|
AM_CONDITIONAL([ENABLE_MODULE_FROST], [test x"$enable_module_frost" = x"yes"])
|
||||||
AM_CONDITIONAL([USE_REDUCED_SURJECTION_PROOF_SIZE], [test x"$use_reduced_surjection_proof_size" = x"yes"])
|
AM_CONDITIONAL([USE_REDUCED_SURJECTION_PROOF_SIZE], [test x"$use_reduced_surjection_proof_size" = x"yes"])
|
||||||
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
|
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"])
|
||||||
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
|
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"])
|
||||||
@ -649,6 +666,7 @@ echo " module whitelist = $enable_module_whitelist"
|
|||||||
echo " module musig = $enable_module_musig"
|
echo " module musig = $enable_module_musig"
|
||||||
echo " module ecdsa-s2c = $enable_module_ecdsa_s2c"
|
echo " module ecdsa-s2c = $enable_module_ecdsa_s2c"
|
||||||
echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor"
|
echo " module ecdsa-adaptor = $enable_module_ecdsa_adaptor"
|
||||||
|
echo " module frost = $enable_module_frost"
|
||||||
echo " module bppp = $enable_module_bppp"
|
echo " module bppp = $enable_module_bppp"
|
||||||
echo " module schnorrsig-halfagg = $enable_module_schnorrsig_halfagg"
|
echo " module schnorrsig-halfagg = $enable_module_schnorrsig_halfagg"
|
||||||
echo
|
echo
|
||||||
|
224
examples/frost.c
Normal file
224
examples/frost.c
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
/***********************************************************************
|
||||||
|
* Copyright (c) 2021-2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
***********************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file demonstrates how to use the FROST module to create a threshold
|
||||||
|
* signature. Additionally, see the documentation in include/secp256k1_frost.h.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <secp256k1.h>
|
||||||
|
#include <secp256k1_schnorrsig.h>
|
||||||
|
#include <secp256k1_frost.h>
|
||||||
|
|
||||||
|
#include "examples_util.h"
|
||||||
|
/* Number of public keys involved in creating the aggregate signature */
|
||||||
|
#define N_SIGNERS 5
|
||||||
|
|
||||||
|
/* Threshold required in creating the aggregate signature */
|
||||||
|
#define THRESHOLD 3
|
||||||
|
|
||||||
|
struct signer_secrets {
|
||||||
|
secp256k1_frost_share share;
|
||||||
|
secp256k1_frost_secnonce secnonce;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct signer {
|
||||||
|
secp256k1_pubkey pubshare;
|
||||||
|
secp256k1_frost_pubnonce pubnonce;
|
||||||
|
secp256k1_frost_session session;
|
||||||
|
secp256k1_frost_partial_sig partial_sig;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Create shares and coefficient commitments */
|
||||||
|
int create_shares(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signers, secp256k1_xonly_pubkey *pk) {
|
||||||
|
int i;
|
||||||
|
secp256k1_frost_share shares[N_SIGNERS];
|
||||||
|
secp256k1_pubkey pubshares[N_SIGNERS];
|
||||||
|
unsigned char seed[32];
|
||||||
|
|
||||||
|
if (!fill_random(seed, sizeof(seed))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secp256k1_frost_shares_trusted_gen(ctx, shares, pubshares, pk, seed, THRESHOLD, N_SIGNERS)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < N_SIGNERS; i++) {
|
||||||
|
signer_secrets[i].share = shares[i];
|
||||||
|
signers[i].pubshare = pubshares[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tweak the pubkey corresponding to the provided tweak cache, update the cache
|
||||||
|
* and return the tweaked aggregate pk. */
|
||||||
|
int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *pk, secp256k1_frost_tweak_cache *cache) {
|
||||||
|
secp256k1_pubkey output_pk;
|
||||||
|
unsigned char ordinary_tweak[32] = "this could be a BIP32 tweak....";
|
||||||
|
unsigned char xonly_tweak[32] = "this could be a taproot tweak..";
|
||||||
|
|
||||||
|
if (!secp256k1_frost_pubkey_tweak(ctx, cache, pk)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ordinary tweaking which, for example, allows deriving multiple child
|
||||||
|
* public keys from a single aggregate key using BIP32 */
|
||||||
|
if (!secp256k1_frost_pubkey_ec_tweak_add(ctx, NULL, cache, ordinary_tweak)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* If one is not interested in signing, the same output_pk can be obtained
|
||||||
|
* by calling `secp256k1_frost_pubkey_get` right after key aggregation to
|
||||||
|
* get the full pubkey and then call `secp256k1_ec_pubkey_tweak_add`. */
|
||||||
|
|
||||||
|
/* Xonly tweaking which, for example, allows creating taproot commitments */
|
||||||
|
if (!secp256k1_frost_pubkey_xonly_tweak_add(ctx, &output_pk, cache, xonly_tweak)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Note that if we wouldn't care about signing, we can arrive at the same
|
||||||
|
* output_pk by providing the untweaked public key to
|
||||||
|
* `secp256k1_xonly_pubkey_tweak_add` (after converting it to an xonly pubkey
|
||||||
|
* if necessary with `secp256k1_xonly_pubkey_from_pubkey`). */
|
||||||
|
|
||||||
|
/* Now we convert the output_pk to an xonly pubkey to allow to later verify
|
||||||
|
* the Schnorr signature against it. For this purpose we can ignore the
|
||||||
|
* `pk_parity` output argument; we would need it if we would have to open
|
||||||
|
* the taproot commitment. */
|
||||||
|
if (!secp256k1_xonly_pubkey_from_pubkey(ctx, pk, NULL, &output_pk)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sign a message hash with the given threshold and aggregate shares and store
|
||||||
|
* the result in sig */
|
||||||
|
int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char* msg32, secp256k1_xonly_pubkey *pk, unsigned char *sig64, const secp256k1_frost_tweak_cache *cache) {
|
||||||
|
int i;
|
||||||
|
size_t signer_id = 0;
|
||||||
|
int signers[THRESHOLD];
|
||||||
|
int is_signer[N_SIGNERS];
|
||||||
|
const secp256k1_frost_pubnonce *pubnonces[THRESHOLD];
|
||||||
|
size_t ids[THRESHOLD];
|
||||||
|
const secp256k1_frost_partial_sig *partial_sigs[THRESHOLD];
|
||||||
|
|
||||||
|
for (i = 0; i < N_SIGNERS; i++) {
|
||||||
|
unsigned char session_id[32];
|
||||||
|
|
||||||
|
/* Create random session ID. It is absolutely necessary that the session ID
|
||||||
|
* is unique for every call of secp256k1_frost_nonce_gen. Otherwise
|
||||||
|
* it's trivial for an attacker to extract the secret key! */
|
||||||
|
if (!fill_random(session_id, sizeof(session_id))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Initialize session and create secret nonce for signing and public
|
||||||
|
* nonce to send to the other signers. */
|
||||||
|
if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_id, &signer_secrets[i].share, msg32, pk, NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
is_signer[i] = 0; /* Initialize is_signer */
|
||||||
|
}
|
||||||
|
/* Select a random subset of signers */
|
||||||
|
for (i = 0; i < THRESHOLD; i++) {
|
||||||
|
size_t subset_seed;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (!fill_random((unsigned char*)&subset_seed, sizeof(subset_seed))) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
signer_id = subset_seed % N_SIGNERS;
|
||||||
|
/* Check if signer has already been assigned */
|
||||||
|
if (!is_signer[signer_id]) {
|
||||||
|
is_signer[signer_id] = 1;
|
||||||
|
signers[i] = signer_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Mark signer as assigned */
|
||||||
|
pubnonces[i] = &signer[signer_id].pubnonce;
|
||||||
|
/* pubkeys[i] = &signer[signer_id].pubkey; */
|
||||||
|
ids[i] = signer_id + 1;
|
||||||
|
}
|
||||||
|
/* Signing communication round 1: Exchange nonces */
|
||||||
|
for (i = 0; i < THRESHOLD; i++) {
|
||||||
|
signer_id = signers[i];
|
||||||
|
if (!secp256k1_frost_nonce_process(ctx, &signer[signer_id].session, pubnonces, THRESHOLD, msg32, pk, signer_id + 1, ids, cache, NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* partial_sign will clear the secnonce by setting it to 0. That's because
|
||||||
|
* you must _never_ reuse the secnonce (or use the same session_id to
|
||||||
|
* create a secnonce). If you do, you effectively reuse the nonce and
|
||||||
|
* leak the secret key. */
|
||||||
|
if (!secp256k1_frost_partial_sign(ctx, &signer[signer_id].partial_sig, &signer_secrets[signer_id].secnonce, &signer_secrets[signer_id].share, &signer[signer_id].session, cache)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
partial_sigs[i] = &signer[signer_id].partial_sig;
|
||||||
|
}
|
||||||
|
/* Communication round 2: A production system would exchange
|
||||||
|
* partial signatures here before moving on. */
|
||||||
|
for (i = 0; i < THRESHOLD; i++) {
|
||||||
|
signer_id = signers[i];
|
||||||
|
/* To check whether signing was successful, it suffices to either verify
|
||||||
|
* the aggregate signature with the aggregate public key using
|
||||||
|
* secp256k1_schnorrsig_verify, or verify all partial signatures of all
|
||||||
|
* signers individually. Verifying the aggregate signature is cheaper but
|
||||||
|
* verifying the individual partial signatures has the advantage that it
|
||||||
|
* can be used to determine which of the partial signatures are invalid
|
||||||
|
* (if any), i.e., which of the partial signatures cause the aggregate
|
||||||
|
* signature to be invalid and thus the protocol run to fail. It's also
|
||||||
|
* fine to first verify the aggregate sig, and only verify the individual
|
||||||
|
* sigs if it does not work.
|
||||||
|
*/
|
||||||
|
if (!secp256k1_frost_partial_sig_verify(ctx, &signer[signer_id].partial_sig, &signer[signer_id].pubnonce, &signer[signer_id].pubshare, &signer[signer_id].session, cache)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return secp256k1_frost_partial_sig_agg(ctx, sig64, &signer[signer_id].session, partial_sigs, THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
secp256k1_context* ctx;
|
||||||
|
struct signer_secrets signer_secrets[N_SIGNERS];
|
||||||
|
struct signer signers[N_SIGNERS];
|
||||||
|
secp256k1_xonly_pubkey pk;
|
||||||
|
secp256k1_frost_tweak_cache cache;
|
||||||
|
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!";
|
||||||
|
unsigned char sig[64];
|
||||||
|
|
||||||
|
/* Create a context for signing and verification */
|
||||||
|
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||||
|
printf("Creating shares.........");
|
||||||
|
if (!create_shares(ctx, signer_secrets, signers, &pk)) {
|
||||||
|
printf("FAILED\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("ok\n");
|
||||||
|
printf("Tweaking................");
|
||||||
|
/* Optionally tweak the key */
|
||||||
|
if (!tweak(ctx, &pk, &cache)) {
|
||||||
|
printf("FAILED\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("ok\n");
|
||||||
|
printf("Signing message.........");
|
||||||
|
if (!sign(ctx, signer_secrets, signers, msg, &pk, sig, &cache)) {
|
||||||
|
printf("FAILED\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("ok\n");
|
||||||
|
printf("Verifying signature.....");
|
||||||
|
if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &pk)) {
|
||||||
|
printf("FAILED\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("ok\n");
|
||||||
|
secp256k1_context_destroy(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
606
include/secp256k1_frost.h
Normal file
606
include/secp256k1_frost.h
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
#ifndef SECP256K1_FROST_H
|
||||||
|
#define SECP256K1_FROST_H
|
||||||
|
|
||||||
|
#include "secp256k1_extrakeys.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** This code is currently a work in progress. It's not secure nor stable. IT
|
||||||
|
* IS EXTREMELY DANGEROUS AND RECKLESS TO USE THIS MODULE IN PRODUCTION!
|
||||||
|
|
||||||
|
* This module implements a variant of Flexible Round-Optimized Schnorr
|
||||||
|
* Threshold Signatures (FROST) by Chelsea Komlo and Ian Goldberg
|
||||||
|
* (https://crysp.uwaterloo.ca/software/frost/). Signatures are compatible with
|
||||||
|
* BIP-340 ("Schnorr"). There's an example C source file in the module's
|
||||||
|
* directory (examples/frost.c) that demonstrates how it can be used.
|
||||||
|
*
|
||||||
|
* The module also supports BIP-341 ("Taproot") and BIP-32 ("ordinary") public
|
||||||
|
* key tweaking, and adaptor signatures.
|
||||||
|
*
|
||||||
|
* Following the convention used in the MuSig module, the API uses the singular
|
||||||
|
* term "nonce" to refer to the two "nonces" used by the FROST scheme.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Opaque data structures
|
||||||
|
*
|
||||||
|
* The exact representation of data inside is implementation defined and not
|
||||||
|
* guaranteed to be portable between different platforms or versions. If you
|
||||||
|
* need to convert to a format suitable for storage, transmission, or
|
||||||
|
* comparison, use the corresponding serialization and parsing functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Opaque data structure that caches information about key tweaking.
|
||||||
|
*
|
||||||
|
* Guaranteed to be 101 bytes in size. It can be safely copied/moved. No
|
||||||
|
* serialization and parsing functions.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned char data[101];
|
||||||
|
} secp256k1_frost_tweak_cache;
|
||||||
|
|
||||||
|
/** Opaque data structure that holds a signer's _secret_ share.
|
||||||
|
*
|
||||||
|
* Guaranteed to be 36 bytes in size. Serialized and parsed with
|
||||||
|
* `frost_share_serialize` and `frost_share_parse`.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned char data[36];
|
||||||
|
} secp256k1_frost_share;
|
||||||
|
|
||||||
|
/** Opaque data structure that holds a signer's _secret_ nonce.
|
||||||
|
*
|
||||||
|
* Guaranteed to be 68 bytes in size.
|
||||||
|
*
|
||||||
|
* WARNING: This structure MUST NOT be copied or read or written to directly.
|
||||||
|
* A signer who is online throughout the whole process and can keep this
|
||||||
|
* structure in memory can use the provided API functions for a safe standard
|
||||||
|
* workflow. See
|
||||||
|
* https://blockstream.com/2019/02/18/musig-a-new-multisignature-standard/ for
|
||||||
|
* more details about the risks associated with serializing or deserializing
|
||||||
|
* this structure.
|
||||||
|
*
|
||||||
|
* We repeat, copying this data structure can result in nonce reuse which will
|
||||||
|
* leak the secret signing key.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned char data[68];
|
||||||
|
} secp256k1_frost_secnonce;
|
||||||
|
|
||||||
|
/** Opaque data structure that holds a signer's public nonce.
|
||||||
|
*
|
||||||
|
* Guaranteed to be 132 bytes in size. It can be safely copied/moved.
|
||||||
|
* Serialized and parsed with `frost_pubnonce_serialize` and
|
||||||
|
* `frost_pubnonce_parse`.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned char data[132];
|
||||||
|
} secp256k1_frost_pubnonce;
|
||||||
|
|
||||||
|
/** Opaque data structure that holds a FROST session.
|
||||||
|
*
|
||||||
|
* This structure is not required to be kept secret for the signing protocol
|
||||||
|
* to be secure. Guaranteed to be 133 bytes in size. It can be safely
|
||||||
|
* copied/moved. No serialization and parsing functions.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned char data[133];
|
||||||
|
} secp256k1_frost_session;
|
||||||
|
|
||||||
|
/** Opaque data structure that holds a partial FROST signature.
|
||||||
|
*
|
||||||
|
* Guaranteed to be 36 bytes in size. Serialized and parsed with
|
||||||
|
* `frost_partial_sig_serialize` and `frost_partial_sig_parse`.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned char data[36];
|
||||||
|
} secp256k1_frost_partial_sig;
|
||||||
|
|
||||||
|
/** Parse a signer's public nonce.
|
||||||
|
*
|
||||||
|
* Returns: 1 when the nonce could be parsed, 0 otherwise.
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: nonce: pointer to a nonce object
|
||||||
|
* In: in66: pointer to the 66-byte nonce to be parsed
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_pubnonce_parse(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_pubnonce *nonce,
|
||||||
|
const unsigned char *in66
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Serialize a signer's public nonce
|
||||||
|
*
|
||||||
|
* Returns: 1 when the nonce could be serialized, 0 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: out66: pointer to a 66-byte array to store the serialized nonce
|
||||||
|
* In: nonce: pointer to the nonce
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_pubnonce_serialize(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
unsigned char *out66,
|
||||||
|
const secp256k1_frost_pubnonce *nonce
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Serialize a FROST partial signature
|
||||||
|
*
|
||||||
|
* Returns: 1 when the signature could be serialized, 0 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: out32: pointer to a 32-byte array to store the serialized signature
|
||||||
|
* In: sig: pointer to the signature
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_partial_sig_serialize(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
unsigned char *out32,
|
||||||
|
const secp256k1_frost_partial_sig *sig
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Parse a FROST partial signature.
|
||||||
|
*
|
||||||
|
* Returns: 1 when the signature could be parsed, 0 otherwise.
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: sig: pointer to a signature object
|
||||||
|
* In: in32: pointer to the 32-byte signature to be parsed
|
||||||
|
*
|
||||||
|
* After the call, sig will always be initialized. If parsing failed or the
|
||||||
|
* encoded numbers are out of range, signature verification with it is
|
||||||
|
* guaranteed to fail for every message and public key.
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_partial_sig_parse(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_partial_sig *sig,
|
||||||
|
const unsigned char *in32
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Serialize a FROST share
|
||||||
|
*
|
||||||
|
* Returns: 1 when the share could be serialized, 0 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: out32: pointer to a 32-byte array to store the serialized share
|
||||||
|
* In: share: pointer to the share
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_share_serialize(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
unsigned char *out32,
|
||||||
|
const secp256k1_frost_share *share
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Parse a FROST share.
|
||||||
|
*
|
||||||
|
* Returns: 1 when the share could be parsed, 0 otherwise.
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: share: pointer to a share object
|
||||||
|
* In: in32: pointer to the 32-byte share to be parsed
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_share_parse(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_share *share,
|
||||||
|
const unsigned char *in32
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Creates key shares
|
||||||
|
*
|
||||||
|
* To generate a key, a trusted dealer generates a share for each other
|
||||||
|
* participant.
|
||||||
|
*
|
||||||
|
* The trusted dealer must transmit shares over secure channels to
|
||||||
|
* participants.
|
||||||
|
*
|
||||||
|
* Each call to this function must have a UNIQUE and uniformly RANDOM seed32
|
||||||
|
* that must that must NOT BE REUSED in subsequent calls to this function and
|
||||||
|
* must be KEPT SECRET (even from other participants).
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: shares: pointer to the key shares
|
||||||
|
* pubshares: pointer to the public verification shares
|
||||||
|
* pk: pointer to the x-only public key
|
||||||
|
* In: seed32: a 32-byte random seed as explained above. Must be
|
||||||
|
* unique to this call to
|
||||||
|
* secp256k1_frost_shares_trusted_gen and must be
|
||||||
|
* uniformly random.
|
||||||
|
* threshold: the minimum number of signers required to produce a
|
||||||
|
* signature
|
||||||
|
* n_participants: the total number of participants
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_shares_trusted_gen(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_share *shares,
|
||||||
|
secp256k1_pubkey *pubshares,
|
||||||
|
secp256k1_xonly_pubkey *pk,
|
||||||
|
const unsigned char *seed32,
|
||||||
|
size_t threshold,
|
||||||
|
size_t n_participants
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||||
|
|
||||||
|
/** Obtain the aggregate public key from a FROST x-only aggregate public key.
|
||||||
|
*
|
||||||
|
* This is only useful if you need the non-xonly public key, in particular for
|
||||||
|
* ordinary (non-xonly) tweaking or batch-verifying multiple key aggregations
|
||||||
|
* (not implemented).
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: ec_agg_pk: the FROST-aggregated public key.
|
||||||
|
* In: xonly_agg_pk: the aggregated x-only public key that is the output of
|
||||||
|
* `secp256k1_frost_shares_gen`
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_get(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_pubkey *ec_agg_pk,
|
||||||
|
const secp256k1_xonly_pubkey *xonly_agg_pk
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Initializes a tweak cache used for applying tweaks to a FROST key
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: tweak_cache: pointer to a frost_tweak_cache struct that is required
|
||||||
|
* for key tweaking
|
||||||
|
* In: agg_pk: the aggregated x-only public key that is the output of
|
||||||
|
* `secp256k1_frost_shares_gen`
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_tweak(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_tweak_cache *tweak_cache,
|
||||||
|
const secp256k1_xonly_pubkey *agg_pk
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Apply ordinary "EC" tweaking to a public key in a given tweak_cache by
|
||||||
|
* adding the generator multiplied with `tweak32` to it. This is useful for
|
||||||
|
* deriving child keys from an aggregate public key via BIP32.
|
||||||
|
*
|
||||||
|
* The tweaking method is the same as `secp256k1_ec_pubkey_tweak_add`. So after
|
||||||
|
* the following pseudocode buf and buf2 have identical contents (absent
|
||||||
|
* earlier failures).
|
||||||
|
*
|
||||||
|
* secp256k1_frost_shares_gen(..., xonly_agg_pk, ...)
|
||||||
|
* secp256k1_frost_pubkey_tweak(..., tweak_cache, xonly_agg_pk)
|
||||||
|
* secp256k1_frost_pubkey_ec_tweak_add(..., output_pk, tweak_cache, tweak32)
|
||||||
|
* secp256k1_ec_pubkey_serialize(..., buf, output_pk)
|
||||||
|
* secp256k1_frost_pubkey_get(..., ec_agg_pk, xonly_agg_pk)
|
||||||
|
* secp256k1_ec_pubkey_tweak_add(..., ec_agg_pk, tweak32)
|
||||||
|
* secp256k1_ec_pubkey_serialize(..., buf2, ec_agg_pk)
|
||||||
|
*
|
||||||
|
* This function is required if you want to _sign_ for a tweaked aggregate key.
|
||||||
|
* On the other hand, if you are only computing a public key, but not intending
|
||||||
|
* to create a signature for it, you can just use
|
||||||
|
* `secp256k1_ec_pubkey_tweak_add`.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid or the resulting public key would be
|
||||||
|
* invalid (only when the tweak is the negation of the corresponding
|
||||||
|
* secret key). 1 otherwise.
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: output_pubkey: pointer to a public key to store the result. Will be set
|
||||||
|
* to an invalid value if this function returns 0. If you
|
||||||
|
* do not need it, this arg can be NULL.
|
||||||
|
* In/Out: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by
|
||||||
|
* `frost_pubkey_tweak`
|
||||||
|
* In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid
|
||||||
|
* according to `secp256k1_ec_seckey_verify`, this function
|
||||||
|
* returns 0. For uniformly random 32-byte arrays the
|
||||||
|
* chance of being invalid is negligible (around 1 in
|
||||||
|
* 2^128).
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_add(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_pubkey *output_pubkey,
|
||||||
|
secp256k1_frost_tweak_cache *tweak_cache,
|
||||||
|
const unsigned char *tweak32
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||||
|
|
||||||
|
/** Apply x-only tweaking to a public key in a given tweak_cache by adding the
|
||||||
|
* generator multiplied with `tweak32` to it. This is useful for creating
|
||||||
|
* Taproot outputs.
|
||||||
|
*
|
||||||
|
* The tweaking method is the same as `secp256k1_xonly_pubkey_tweak_add`. So in
|
||||||
|
* the following pseudocode xonly_pubkey_tweak_add_check (absent earlier
|
||||||
|
* failures) returns 1.
|
||||||
|
*
|
||||||
|
* secp256k1_frost_shares_gen(..., agg_pk, ...)
|
||||||
|
* secp256k1_frost_pubkey_tweak(..., tweak_cache, agg_pk)
|
||||||
|
* secp256k1_frost_pubkey_xonly_tweak_add(..., output_pk, tweak_cache, tweak32)
|
||||||
|
* secp256k1_xonly_pubkey_serialize(..., buf, output_pk)
|
||||||
|
* secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32)
|
||||||
|
*
|
||||||
|
* This function is required if you want to _sign_ for a tweaked aggregate key.
|
||||||
|
* On the other hand, if you are only computing a public key, but not intending
|
||||||
|
* to create a signature for it, you can just use
|
||||||
|
* `secp256k1_xonly_pubkey_tweak_add`.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid or the resulting public key would be
|
||||||
|
* invalid (only when the tweak is the negation of the corresponding
|
||||||
|
* secret key). 1 otherwise.
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: output_pubkey: pointer to a public key to store the result. Will be set
|
||||||
|
* to an invalid value if this function returns 0. If you
|
||||||
|
* do not need it, this arg can be NULL.
|
||||||
|
* In/Out: tweak_cache: pointer to a `frost_tweak_cache` struct initialized by
|
||||||
|
* `frost_pubkey_tweak`
|
||||||
|
* In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid
|
||||||
|
* according to secp256k1_ec_seckey_verify, this function
|
||||||
|
* returns 0. For uniformly random 32-byte arrays the
|
||||||
|
* chance of being invalid is negligible (around 1 in
|
||||||
|
* 2^128).
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_tweak_add(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_pubkey *output_pubkey,
|
||||||
|
secp256k1_frost_tweak_cache *tweak_cache,
|
||||||
|
const unsigned char *tweak32
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||||
|
|
||||||
|
/** Starts a signing session by generating a nonce
|
||||||
|
*
|
||||||
|
* This function outputs a secret nonce that will be required for signing and a
|
||||||
|
* corresponding public nonce that is intended to be sent to other signers.
|
||||||
|
*
|
||||||
|
* FROST, like MuSig, differs from regular Schnorr signing in that
|
||||||
|
* implementers _must_ take special care to not reuse a nonce. This can be
|
||||||
|
* ensured by following these rules:
|
||||||
|
*
|
||||||
|
* 1. Each call to this function must have a UNIQUE session_id32 that must NOT BE
|
||||||
|
* REUSED in subsequent calls to this function.
|
||||||
|
* If you do not provide a seckey, session_id32 _must_ be UNIFORMLY RANDOM
|
||||||
|
* AND KEPT SECRET (even from other signers). If you do provide a seckey,
|
||||||
|
* session_id32 can instead be a counter (that must never repeat!). However,
|
||||||
|
* it is recommended to always choose session_id32 uniformly at random.
|
||||||
|
* 2. If you already know the seckey, message or aggregate public key, they
|
||||||
|
* can be optionally provided to derive the nonce and increase
|
||||||
|
* misuse-resistance. The extra_input32 argument can be used to provide
|
||||||
|
* additional data that does not repeat in normal scenarios, such as the
|
||||||
|
* current time.
|
||||||
|
* 3. Avoid copying (or serializing) the secnonce. This reduces the possibility
|
||||||
|
* that it is used more than once for signing.
|
||||||
|
*
|
||||||
|
* Remember that nonce reuse will leak the secret key!
|
||||||
|
* Note that using the same agg_share for multiple FROST sessions is fine.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid and 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||||
|
* Out: secnonce: pointer to a structure to store the secret nonce
|
||||||
|
* pubnonce: pointer to a structure to store the public nonce
|
||||||
|
* In: session_id32: a 32-byte session_id32 as explained above. Must be
|
||||||
|
* unique to this call to secp256k1_frost_nonce_gen and
|
||||||
|
* must be uniformly random unless you really know what you
|
||||||
|
* are doing.
|
||||||
|
* agg_share: the aggregated share that will later be used for
|
||||||
|
* signing, if already known (can be NULL)
|
||||||
|
* msg32: the 32-byte message that will later be signed, if
|
||||||
|
* already known (can be NULL)
|
||||||
|
* agg_pk: the FROST-aggregated public key (can be NULL)
|
||||||
|
* extra_input32: an optional 32-byte array that is input to the nonce
|
||||||
|
* derivation function (can be NULL)
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_nonce_gen(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_secnonce *secnonce,
|
||||||
|
secp256k1_frost_pubnonce *pubnonce,
|
||||||
|
const unsigned char *session_id32,
|
||||||
|
const secp256k1_frost_share *agg_share,
|
||||||
|
const unsigned char *msg32,
|
||||||
|
const secp256k1_xonly_pubkey *agg_pk,
|
||||||
|
const unsigned char *extra_input32
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||||
|
|
||||||
|
/** Takes the public nonces of all signers and computes a session that is
|
||||||
|
* required for signing and verification of partial signatures. The participant
|
||||||
|
* IDs can be sorted before combining, but the corresponding pubnonces must be
|
||||||
|
* resorted as well. All signers must use the same sorting of pubnonces,
|
||||||
|
* otherwise signing will fail.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid or if some signer sent invalid
|
||||||
|
* pubnonces, 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: session: pointer to a struct to store the session
|
||||||
|
* In: pubnonces: array of pointers to public nonces sent by the signers
|
||||||
|
* n_pubnonces: number of elements in the pubnonces array. Must be
|
||||||
|
* greater than 0.
|
||||||
|
* msg32: the 32-byte message to sign
|
||||||
|
* agg_pk: the FROST-aggregated public key
|
||||||
|
* my_id: the ID of the participant who will use the session for
|
||||||
|
* signing
|
||||||
|
* ids: array of the IDs of the signers
|
||||||
|
* tweak_cache: pointer to frost_tweak_cache struct (can be NULL)
|
||||||
|
* adaptor: optional pointer to an adaptor point encoded as a
|
||||||
|
* public key if this signing session is part of an
|
||||||
|
* adaptor signature protocol (can be NULL)
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_process(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_session *session,
|
||||||
|
const secp256k1_frost_pubnonce * const *pubnonces,
|
||||||
|
size_t n_pubnonces,
|
||||||
|
const unsigned char *msg32,
|
||||||
|
const secp256k1_xonly_pubkey *agg_pk,
|
||||||
|
size_t my_id,
|
||||||
|
const size_t *ids,
|
||||||
|
const secp256k1_frost_tweak_cache *tweak_cache,
|
||||||
|
const secp256k1_pubkey *adaptor
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8);
|
||||||
|
|
||||||
|
/** Produces a partial signature
|
||||||
|
*
|
||||||
|
* This function overwrites the given secnonce with zeros and will abort if given a
|
||||||
|
* secnonce that is all zeros. This is a best effort attempt to protect against nonce
|
||||||
|
* reuse. However, this is of course easily defeated if the secnonce has been
|
||||||
|
* copied (or serialized). Remember that nonce reuse will leak the secret key!
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid or the provided secnonce has already
|
||||||
|
* been used for signing, 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: partial_sig: pointer to struct to store the partial signature
|
||||||
|
* In/Out: secnonce: pointer to the secnonce struct created in
|
||||||
|
* frost_nonce_gen that has been never used in a
|
||||||
|
* partial_sign call before
|
||||||
|
* In: agg_share: the aggregated share
|
||||||
|
* session: pointer to the session that was created with
|
||||||
|
* frost_nonce_process
|
||||||
|
* tweak_cache: pointer to frost_tweak_cache struct (can be NULL)
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_partial_sign(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
secp256k1_frost_partial_sig *partial_sig,
|
||||||
|
secp256k1_frost_secnonce *secnonce,
|
||||||
|
const secp256k1_frost_share *agg_share,
|
||||||
|
const secp256k1_frost_session *session,
|
||||||
|
const secp256k1_frost_tweak_cache *tweak_cache
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||||
|
|
||||||
|
/** Verifies an individual signer's partial signature
|
||||||
|
*
|
||||||
|
* The signature is verified for a specific signing session. In order to avoid
|
||||||
|
* accidentally verifying a signature from a different or non-existing signing
|
||||||
|
* session, you must ensure the following:
|
||||||
|
* 1. The `tweak_cache` argument is identical to the one used to create the
|
||||||
|
* `session` with `frost_nonce_process`.
|
||||||
|
* 2. The `pubshare` argument must be the output of
|
||||||
|
* `secp256k1_frost_shares_trusted_gen` for the signer's 'pk'.
|
||||||
|
* 3. The `pubnonce` argument must be identical to the one sent by the
|
||||||
|
* signer and used to create the `session` with `frost_nonce_process`.
|
||||||
|
*
|
||||||
|
* This function can be used to assign blame for a failed signature.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid or the partial signature does not
|
||||||
|
* verify, 1 otherwise
|
||||||
|
* Args ctx: pointer to a context object
|
||||||
|
* In: partial_sig: pointer to partial signature to verify, sent by
|
||||||
|
* the signer associated with `pubnonce` and `pubkey`
|
||||||
|
* pubnonce: public nonce of the signer in the signing session
|
||||||
|
* pubshare: public verification share of the signer in the signing
|
||||||
|
* session that is the output of
|
||||||
|
* `secp256k1_frost_shares_trusted_gen`
|
||||||
|
* session: pointer to the session that was created with
|
||||||
|
* `frost_nonce_process`
|
||||||
|
* tweak_cache: pointer to frost_tweak_cache struct (can be NULL)
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_verify(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
const secp256k1_frost_partial_sig *partial_sig,
|
||||||
|
const secp256k1_frost_pubnonce *pubnonce,
|
||||||
|
const secp256k1_pubkey *pubshare,
|
||||||
|
const secp256k1_frost_session *session,
|
||||||
|
const secp256k1_frost_tweak_cache *tweak_cache
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||||
|
|
||||||
|
/** Aggregates partial signatures
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid, 1 otherwise (which does NOT mean
|
||||||
|
* the resulting signature verifies).
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: sig64: complete (but possibly invalid) Schnorr signature
|
||||||
|
* In: session: pointer to the session that was created with
|
||||||
|
* frost_nonce_process
|
||||||
|
* partial_sigs: array of pointers to partial signatures to aggregate
|
||||||
|
* n_sigs: number of elements in the partial_sigs array. Must be
|
||||||
|
* greater than 0.
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_partial_sig_agg(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
unsigned char *sig64,
|
||||||
|
const secp256k1_frost_session *session,
|
||||||
|
const secp256k1_frost_partial_sig * const *partial_sigs,
|
||||||
|
size_t n_sigs
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||||
|
|
||||||
|
/** Extracts the nonce_parity bit from a session
|
||||||
|
*
|
||||||
|
* This is used for adaptor signatures.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: nonce_parity: pointer to an integer that indicates the parity
|
||||||
|
* of the aggregate public nonce. Used for adaptor
|
||||||
|
* signatures.
|
||||||
|
* In: session: pointer to the session that was created with
|
||||||
|
* frost_nonce_process
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_nonce_parity(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
int *nonce_parity,
|
||||||
|
const secp256k1_frost_session *session
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||||
|
|
||||||
|
/** Verifies that the adaptor can be extracted by combining the adaptor
|
||||||
|
* pre-signature and the completed signature.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid or the adaptor signature does not
|
||||||
|
* verify, 1 otherwise
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* In: pre_sig64: 64-byte pre-signature
|
||||||
|
* msg32: the 32-byte message being verified
|
||||||
|
* pubkey: pointer to an x-only public key to verify with
|
||||||
|
* adaptor: pointer to the adaptor point being verified
|
||||||
|
* nonce_parity: the output of `frost_nonce_parity` called with the
|
||||||
|
* session used for producing the pre-signature
|
||||||
|
*/
|
||||||
|
SECP256K1_API int secp256k1_frost_verify_adaptor(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
const unsigned char *pre_sig64,
|
||||||
|
const unsigned char *msg32,
|
||||||
|
const secp256k1_xonly_pubkey *pubkey,
|
||||||
|
const secp256k1_pubkey *adaptor,
|
||||||
|
int nonce_parity
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||||
|
|
||||||
|
/** Creates a signature from a pre-signature and an adaptor.
|
||||||
|
*
|
||||||
|
* If the sec_adaptor32 argument is incorrect, the output signature will be
|
||||||
|
* invalid. This function does not verify the signature.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are invalid, or pre_sig64 or sec_adaptor32 contain
|
||||||
|
* invalid (overflowing) values. 1 otherwise (which does NOT mean the
|
||||||
|
* signature or the adaptor are valid!)
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out: sig64: 64-byte signature. This pointer may point to the same
|
||||||
|
* memory area as `pre_sig`.
|
||||||
|
* In: pre_sig64: 64-byte pre-signature
|
||||||
|
* sec_adaptor32: 32-byte secret adaptor to add to the pre-signature
|
||||||
|
* nonce_parity: the output of `frost_nonce_parity` called with the
|
||||||
|
* session used for producing the pre-signature
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_adapt(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
unsigned char *sig64,
|
||||||
|
const unsigned char *pre_sig64,
|
||||||
|
const unsigned char *sec_adaptor32,
|
||||||
|
int nonce_parity
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||||
|
|
||||||
|
/** Extracts a secret adaptor from a FROST pre-signature and corresponding
|
||||||
|
* signature
|
||||||
|
*
|
||||||
|
* This function will not fail unless given grossly invalid data; if it is
|
||||||
|
* merely given signatures that do not verify, the returned value will be
|
||||||
|
* nonsense. It is therefore important that all data be verified at earlier
|
||||||
|
* steps of any protocol that uses this function. In particular, this includes
|
||||||
|
* verifying all partial signatures that were aggregated into pre_sig64.
|
||||||
|
*
|
||||||
|
* Returns: 0 if the arguments are NULL, or sig64 or pre_sig64 contain
|
||||||
|
* grossly invalid (overflowing) values. 1 otherwise (which does NOT
|
||||||
|
* mean the signatures or the adaptor are valid!)
|
||||||
|
* Args: ctx: pointer to a context object
|
||||||
|
* Out:sec_adaptor32: 32-byte secret adaptor
|
||||||
|
* In: sig64: complete, valid 64-byte signature
|
||||||
|
* pre_sig64: the pre-signature corresponding to sig64, i.e., the
|
||||||
|
* aggregate of partial signatures without the secret
|
||||||
|
* adaptor
|
||||||
|
* nonce_parity: the output of `frost_nonce_parity` called with the
|
||||||
|
* session used for producing sig64
|
||||||
|
*/
|
||||||
|
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_extract_adaptor(
|
||||||
|
const secp256k1_context *ctx,
|
||||||
|
unsigned char *sec_adaptor32,
|
||||||
|
const unsigned char *sig64,
|
||||||
|
const unsigned char *pre_sig64,
|
||||||
|
int nonce_parity
|
||||||
|
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -47,6 +47,10 @@
|
|||||||
#include "../include/secp256k1_musig.h"
|
#include "../include/secp256k1_musig.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_MODULE_FROST
|
||||||
|
#include "include/secp256k1_frost.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static void run_tests(secp256k1_context *ctx, unsigned char *key);
|
static void run_tests(secp256k1_context *ctx, unsigned char *key);
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
@ -349,4 +353,79 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
|
|||||||
CHECK(ret == 1);
|
CHECK(ret == 1);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_MODULE_FROST
|
||||||
|
{
|
||||||
|
secp256k1_xonly_pubkey pk;
|
||||||
|
unsigned char session_id[32];
|
||||||
|
secp256k1_frost_secnonce secnonce[2];
|
||||||
|
secp256k1_frost_pubnonce pubnonce[2];
|
||||||
|
const secp256k1_frost_pubnonce *pubnonce_ptr[2];
|
||||||
|
secp256k1_frost_tweak_cache cache;
|
||||||
|
secp256k1_frost_session session;
|
||||||
|
secp256k1_frost_partial_sig partial_sig;
|
||||||
|
const secp256k1_frost_partial_sig *partial_sig_ptr[1];
|
||||||
|
unsigned char extra_input[32];
|
||||||
|
unsigned char sec_adaptor[32];
|
||||||
|
secp256k1_pubkey adaptor;
|
||||||
|
unsigned char pre_sig[64];
|
||||||
|
int nonce_parity;
|
||||||
|
secp256k1_frost_share shares[2];
|
||||||
|
secp256k1_pubkey pubshares[2];
|
||||||
|
size_t ids[2];
|
||||||
|
|
||||||
|
pubnonce_ptr[0] = &pubnonce[0];
|
||||||
|
pubnonce_ptr[1] = &pubnonce[1];
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(key, 32);
|
||||||
|
memcpy(extra_input, key, sizeof(extra_input));
|
||||||
|
extra_input[0] = extra_input[0] + 1;
|
||||||
|
memcpy(sec_adaptor, key, sizeof(sec_adaptor));
|
||||||
|
sec_adaptor[0] = extra_input[0] + 2;
|
||||||
|
memcpy(session_id, key, sizeof(session_id));
|
||||||
|
session_id[0] = session_id[0] + 3;
|
||||||
|
partial_sig_ptr[0] = &partial_sig;
|
||||||
|
ids[0] = 1;
|
||||||
|
ids[1] = 2;
|
||||||
|
|
||||||
|
/* shares_gen */
|
||||||
|
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
|
||||||
|
ret = secp256k1_frost_shares_trusted_gen(ctx, shares, pubshares, &pk, key, 2, 2);
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
||||||
|
CHECK(ret == 1);
|
||||||
|
SECP256K1_CHECKMEM_UNDEFINE(&shares[0], sizeof(shares[0]));
|
||||||
|
SECP256K1_CHECKMEM_UNDEFINE(&shares[1], sizeof(shares[1]));
|
||||||
|
/* nonce_gen */
|
||||||
|
SECP256K1_CHECKMEM_UNDEFINE(session_id, sizeof(session_id));
|
||||||
|
CHECK(secp256k1_ec_pubkey_create(ctx, &adaptor, sec_adaptor));
|
||||||
|
SECP256K1_CHECKMEM_UNDEFINE(extra_input, sizeof(extra_input));
|
||||||
|
SECP256K1_CHECKMEM_UNDEFINE(sec_adaptor, sizeof(sec_adaptor));
|
||||||
|
CHECK(secp256k1_frost_pubkey_tweak(ctx, &cache, &pk) == 1);
|
||||||
|
ret = secp256k1_frost_nonce_gen(ctx, &secnonce[0], &pubnonce[0], session_id, &shares[0], msg, &pk, extra_input);
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
||||||
|
CHECK(ret == 1);
|
||||||
|
ret = secp256k1_frost_nonce_gen(ctx, &secnonce[1], &pubnonce[1], session_id, &shares[1], msg, &pk, extra_input);
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
||||||
|
CHECK(ret == 1);
|
||||||
|
/* partial_sign */
|
||||||
|
CHECK(secp256k1_frost_nonce_process(ctx, &session, pubnonce_ptr, 2, msg, &pk, 1, ids, &cache, &adaptor) == 1);
|
||||||
|
ret = secp256k1_keypair_create(ctx, &keypair, key);
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
||||||
|
CHECK(ret == 1);
|
||||||
|
ret = secp256k1_frost_partial_sign(ctx, &partial_sig, &secnonce[0], &shares[0], &session, &cache);
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
||||||
|
CHECK(ret == 1);
|
||||||
|
/* adapt */
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&partial_sig, sizeof(partial_sig));
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(ctx, pre_sig, &session, partial_sig_ptr, 1));
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(pre_sig, sizeof(pre_sig));
|
||||||
|
CHECK(secp256k1_frost_nonce_parity(ctx, &nonce_parity, &session));
|
||||||
|
ret = secp256k1_frost_adapt(ctx, sig, pre_sig, sec_adaptor, nonce_parity);
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
||||||
|
CHECK(ret == 1);
|
||||||
|
/* extract_adaptor */
|
||||||
|
ret = secp256k1_frost_extract_adaptor(ctx, sec_adaptor, sig, pre_sig, nonce_parity);
|
||||||
|
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
|
||||||
|
CHECK(ret == 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,10 @@ static void secp256k1_ge_from_bytes(secp256k1_ge *r, const unsigned char *buf);
|
|||||||
*/
|
*/
|
||||||
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge);
|
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge);
|
||||||
|
|
||||||
|
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge);
|
||||||
|
|
||||||
|
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data);
|
||||||
|
|
||||||
/** Check invariants on an affine group element (no-op unless VERIFY is enabled). */
|
/** Check invariants on an affine group element (no-op unless VERIFY is enabled). */
|
||||||
static void secp256k1_ge_verify(const secp256k1_ge *a);
|
static void secp256k1_ge_verify(const secp256k1_ge *a);
|
||||||
#define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a)
|
#define SECP256K1_GE_VERIFY(a) secp256k1_ge_verify(a)
|
||||||
|
@ -914,6 +914,23 @@ static int secp256k1_gej_has_quad_y_var(const secp256k1_gej *a) {
|
|||||||
return secp256k1_fe_is_square_var(&yz);
|
return secp256k1_fe_is_square_var(&yz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) {
|
||||||
|
if (secp256k1_ge_is_infinity(ge)) {
|
||||||
|
memset(data, 0, 64);
|
||||||
|
} else {
|
||||||
|
secp256k1_ge_to_bytes(data, ge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) {
|
||||||
|
unsigned char zeros[64] = { 0 };
|
||||||
|
if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) {
|
||||||
|
secp256k1_ge_set_infinity(ge);
|
||||||
|
} else {
|
||||||
|
secp256k1_ge_from_bytes(ge, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) {
|
static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) {
|
||||||
#ifdef EXHAUSTIVE_TEST_ORDER
|
#ifdef EXHAUSTIVE_TEST_ORDER
|
||||||
secp256k1_gej out;
|
secp256k1_gej out;
|
||||||
|
8
src/modules/frost/Makefile.am.include
Normal file
8
src/modules/frost/Makefile.am.include
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
include_HEADERS += include/secp256k1_frost.h
|
||||||
|
noinst_HEADERS += src/modules/frost/main_impl.h
|
||||||
|
noinst_HEADERS += src/modules/frost/keygen.h
|
||||||
|
noinst_HEADERS += src/modules/frost/keygen_impl.h
|
||||||
|
noinst_HEADERS += src/modules/frost/session.h
|
||||||
|
noinst_HEADERS += src/modules/frost/session_impl.h
|
||||||
|
noinst_HEADERS += src/modules/frost/adaptor_impl.h
|
||||||
|
noinst_HEADERS += src/modules/frost/tests_impl.h
|
168
src/modules/frost/adaptor_impl.h
Normal file
168
src/modules/frost/adaptor_impl.h
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/***********************************************************************
|
||||||
|
* Copyright (c) 2022-2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
***********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SECP256K1_MODULE_FROST_ADAPTOR_IMPL_H
|
||||||
|
#define SECP256K1_MODULE_FROST_ADAPTOR_IMPL_H
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../../../include/secp256k1.h"
|
||||||
|
#include "../../../include/secp256k1_frost.h"
|
||||||
|
|
||||||
|
#include "session.h"
|
||||||
|
#include "../../scalar.h"
|
||||||
|
|
||||||
|
int secp256k1_frost_nonce_parity(const secp256k1_context* ctx, int *nonce_parity, const secp256k1_frost_session *session) {
|
||||||
|
secp256k1_frost_session_internal session_i;
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(nonce_parity != NULL);
|
||||||
|
ARG_CHECK(session != NULL);
|
||||||
|
|
||||||
|
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*nonce_parity = session_i.fin_nonce_parity;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_verify_adaptor(const secp256k1_context* ctx, const unsigned char *pre_sig64, const unsigned char *msg32, const secp256k1_xonly_pubkey *pubkey, const secp256k1_pubkey *adaptor, int nonce_parity) {
|
||||||
|
secp256k1_scalar s;
|
||||||
|
secp256k1_scalar e;
|
||||||
|
secp256k1_gej rj;
|
||||||
|
secp256k1_ge pk;
|
||||||
|
secp256k1_gej pkj;
|
||||||
|
secp256k1_ge r;
|
||||||
|
unsigned char buf[32];
|
||||||
|
int overflow;
|
||||||
|
secp256k1_ge adaptorp;
|
||||||
|
secp256k1_xonly_pubkey noncepk;
|
||||||
|
secp256k1_gej fin_nonce_ptj;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(pre_sig64 != NULL);
|
||||||
|
ARG_CHECK(msg32 != NULL);
|
||||||
|
ARG_CHECK(pubkey != NULL);
|
||||||
|
ARG_CHECK(adaptor != NULL);
|
||||||
|
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
|
||||||
|
|
||||||
|
if (!secp256k1_xonly_pubkey_parse(ctx, &noncepk, &pre_sig64[0])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!secp256k1_xonly_pubkey_load(ctx, &r, &noncepk)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!nonce_parity) {
|
||||||
|
secp256k1_ge_neg(&adaptorp, &adaptorp);
|
||||||
|
}
|
||||||
|
secp256k1_gej_set_ge(&fin_nonce_ptj, &adaptorp);
|
||||||
|
secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &r, NULL);
|
||||||
|
if (secp256k1_gej_is_infinity(&fin_nonce_ptj)) {
|
||||||
|
/* unreachable with overwhelming probability */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
|
||||||
|
if (overflow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute e. */
|
||||||
|
secp256k1_fe_get_b32(buf, &pk.x);
|
||||||
|
secp256k1_schnorrsig_challenge(&e, &pre_sig64[0], msg32, 32, buf);
|
||||||
|
|
||||||
|
/* Compute rj = s*G + (-e)*pkj */
|
||||||
|
secp256k1_scalar_negate(&e, &e);
|
||||||
|
secp256k1_gej_set_ge(&pkj, &pk);
|
||||||
|
secp256k1_ecmult(&rj, &pkj, &e, &s);
|
||||||
|
|
||||||
|
/* secp256k1_ge_set_gej_var(&r, &rj); */
|
||||||
|
if (secp256k1_gej_is_infinity(&rj)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_gej_neg(&rj, &rj);
|
||||||
|
secp256k1_gej_add_var(&rj, &rj, &fin_nonce_ptj, NULL);
|
||||||
|
return secp256k1_gej_is_infinity(&rj);
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_adapt(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *pre_sig64, const unsigned char *sec_adaptor32, int nonce_parity) {
|
||||||
|
secp256k1_scalar s;
|
||||||
|
secp256k1_scalar t;
|
||||||
|
int overflow;
|
||||||
|
int ret = 1;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(sig64 != NULL);
|
||||||
|
ARG_CHECK(pre_sig64 != NULL);
|
||||||
|
ARG_CHECK(sec_adaptor32 != NULL);
|
||||||
|
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
|
||||||
|
|
||||||
|
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
|
||||||
|
if (overflow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_scalar_set_b32(&t, sec_adaptor32, &overflow);
|
||||||
|
ret &= !overflow;
|
||||||
|
|
||||||
|
/* Determine if the secret adaptor should be negated.
|
||||||
|
*
|
||||||
|
* The frost_session stores the X-coordinate and the parity of the "final nonce"
|
||||||
|
* (r + t)*G, where r*G is the aggregate public nonce and t is the secret adaptor.
|
||||||
|
*
|
||||||
|
* Since a BIP340 signature requires an x-only public nonce, in the case where
|
||||||
|
* (r + t)*G has odd Y-coordinate (i.e. nonce_parity == 1), the x-only public nonce
|
||||||
|
* corresponding to the signature is actually (-r - t)*G. Thus adapting a
|
||||||
|
* pre-signature requires negating t in this case.
|
||||||
|
*/
|
||||||
|
if (nonce_parity) {
|
||||||
|
secp256k1_scalar_negate(&t, &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_scalar_add(&s, &s, &t);
|
||||||
|
secp256k1_scalar_get_b32(&sig64[32], &s);
|
||||||
|
memmove(sig64, pre_sig64, 32);
|
||||||
|
secp256k1_scalar_clear(&t);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_extract_adaptor(const secp256k1_context* ctx, unsigned char *sec_adaptor32, const unsigned char *sig64, const unsigned char *pre_sig64, int nonce_parity) {
|
||||||
|
secp256k1_scalar t;
|
||||||
|
secp256k1_scalar s;
|
||||||
|
int overflow;
|
||||||
|
int ret = 1;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(sec_adaptor32 != NULL);
|
||||||
|
ARG_CHECK(sig64 != NULL);
|
||||||
|
ARG_CHECK(pre_sig64 != NULL);
|
||||||
|
ARG_CHECK(nonce_parity == 0 || nonce_parity == 1);
|
||||||
|
|
||||||
|
secp256k1_scalar_set_b32(&t, &sig64[32], &overflow);
|
||||||
|
ret &= !overflow;
|
||||||
|
secp256k1_scalar_negate(&t, &t);
|
||||||
|
|
||||||
|
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
|
||||||
|
if (overflow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_scalar_add(&t, &t, &s);
|
||||||
|
|
||||||
|
if (!nonce_parity) {
|
||||||
|
secp256k1_scalar_negate(&t, &t);
|
||||||
|
}
|
||||||
|
secp256k1_scalar_get_b32(sec_adaptor32, &t);
|
||||||
|
secp256k1_scalar_clear(&t);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
78
src/modules/frost/frost.md
Normal file
78
src/modules/frost/frost.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
Notes on the frost module API
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The following sections contain additional notes on the API of the frost module
|
||||||
|
(`include/secp256k1_frost.h`). A usage example can be found in
|
||||||
|
`examples/frost.c`.
|
||||||
|
|
||||||
|
# API misuse
|
||||||
|
|
||||||
|
Users of the frost module must take great care to make sure of the following:
|
||||||
|
|
||||||
|
1. The dealer establishes a secure communications channel with each participant
|
||||||
|
and uses that channel to transmit shares during key generation.
|
||||||
|
2. A unique set of coefficients per key generation session is generated in
|
||||||
|
`secp256k1_frost_share_gen`. See the corresponding comment in
|
||||||
|
`include/secp256k1_frost.h` for how to ensure that.
|
||||||
|
3. The `pubnonces` provided to `secp256k1_frost_nonce_process` are sorted by
|
||||||
|
the corresponding lexicographic ordering of the x-only pubkey of each
|
||||||
|
participant, and the `pubkeys` provided to `secp256k1_frost_nonce_process`
|
||||||
|
are sorted lexicographically.
|
||||||
|
4. A unique nonce per signing session is generated in
|
||||||
|
`secp256k1_frost_nonce_gen`. See the corresponding comment in
|
||||||
|
`include/secp256k1_frost.h` for how to ensure that.
|
||||||
|
5. The `secp256k1_frost_secnonce` structure is never copied or serialized. See
|
||||||
|
also the comment on `secp256k1_frost_secnonce` in
|
||||||
|
`include/secp256k1_frost.h`.
|
||||||
|
6. Opaque data structures are never written to or read from directly. Instead,
|
||||||
|
only the provided accessor functions are used.
|
||||||
|
7. If adaptor signatures are used, all partial signatures are verified.
|
||||||
|
|
||||||
|
# Key Generation
|
||||||
|
|
||||||
|
1. A trusted dealer generates shares with `secp256k1_frost_shares_trusted_gen`
|
||||||
|
and distributes a share and the public key to each participant using a
|
||||||
|
secure channel.
|
||||||
|
|
||||||
|
# Tweaking
|
||||||
|
|
||||||
|
A (Taproot) tweak can be added to the resulting public key with
|
||||||
|
`secp256k1_xonly_pubkey_tweak_add`, after converting it to an xonly pubkey if
|
||||||
|
necessary with `secp256k1_xonly_pubkey_from_pubkey`.
|
||||||
|
|
||||||
|
An ordinary tweak can be added to the resulting public key with
|
||||||
|
`secp256k1_ec_pubkey_tweak_add`, after converting it to an ordinary pubkey if
|
||||||
|
necessary with `secp256k1_frost_pubkey_get`.
|
||||||
|
|
||||||
|
Tweaks can also be chained together by tweaking an already tweaked key.
|
||||||
|
|
||||||
|
# Signing
|
||||||
|
|
||||||
|
1. Optionally add a tweak by calling `secp256k1_frost_pubkey_tweak` and then
|
||||||
|
`secp256k1_frost_pubkey_xonly_tweak_add` for a Taproot tweak and
|
||||||
|
`secp256k1_frost_pubkey_ec_tweak_add` for an ordinary tweak.
|
||||||
|
2. Generate a pair of secret and public nonce with `secp256k1_frost_nonce_gen`
|
||||||
|
and send the public nonce to the other signers.
|
||||||
|
3. Process the aggregate nonce with `secp256k1_frost_nonce_process`.
|
||||||
|
4. Create a partial signature with `secp256k1_frost_partial_sign`.
|
||||||
|
5. Verify the partial signatures (optional in some scenarios) with
|
||||||
|
`secp256k1_frost_partial_sig_verify`.
|
||||||
|
6. Someone (not necessarily the signer) obtains all partial signatures and
|
||||||
|
aggregates them into the final Schnorr signature using
|
||||||
|
`secp256k1_frost_partial_sig_agg`.
|
||||||
|
|
||||||
|
The aggregate signature can be verified with `secp256k1_schnorrsig_verify`.
|
||||||
|
|
||||||
|
Note that steps 1 to 3 can happen before the message to be signed is known to
|
||||||
|
the signers. Therefore, the communication round to exchange nonces can be
|
||||||
|
viewed as a pre-processing step that is run whenever convenient to the signers.
|
||||||
|
This disables some of the defense-in-depth measures that may protect against
|
||||||
|
API misuse in some cases. Similarly, the API supports an alternative protocol
|
||||||
|
flow where generating the key (see Key Generation above) is allowed to happen
|
||||||
|
after exchanging nonces (step 2).
|
||||||
|
|
||||||
|
# Verification
|
||||||
|
|
||||||
|
A participant who wants to verify the partial signatures, but does not sign
|
||||||
|
itself may do so using the above instructions except that the verifier skips
|
||||||
|
steps 2 and 4.
|
26
src/modules/frost/keygen.h
Normal file
26
src/modules/frost/keygen.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
* Copyright (c) 2021-2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SECP256K1_MODULE_FROST_KEYGEN_H
|
||||||
|
#define SECP256K1_MODULE_FROST_KEYGEN_H
|
||||||
|
|
||||||
|
#include "../../../include/secp256k1.h"
|
||||||
|
#include "../../../include/secp256k1_frost.h"
|
||||||
|
|
||||||
|
#include "../../group.h"
|
||||||
|
#include "../../scalar.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
secp256k1_ge pk;
|
||||||
|
secp256k1_scalar tweak;
|
||||||
|
int parity_acc;
|
||||||
|
} secp256k1_tweak_cache_internal;
|
||||||
|
|
||||||
|
static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache);
|
||||||
|
|
||||||
|
static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share);
|
||||||
|
|
||||||
|
#endif
|
257
src/modules/frost/keygen_impl.h
Normal file
257
src/modules/frost/keygen_impl.h
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
* Copyright (c) 2021-2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SECP256K1_MODULE_FROST_KEYGEN_IMPL_H
|
||||||
|
#define SECP256K1_MODULE_FROST_KEYGEN_IMPL_H
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../../../include/secp256k1.h"
|
||||||
|
#include "../../../include/secp256k1_extrakeys.h"
|
||||||
|
#include "../../../include/secp256k1_frost.h"
|
||||||
|
|
||||||
|
#include "keygen.h"
|
||||||
|
#include "../../ecmult.h"
|
||||||
|
#include "../../field.h"
|
||||||
|
#include "../../group.h"
|
||||||
|
#include "../../hash.h"
|
||||||
|
#include "../../scalar.h"
|
||||||
|
|
||||||
|
static const unsigned char secp256k1_frost_tweak_cache_magic[4] = { 0x40, 0x25, 0x2e, 0x41 };
|
||||||
|
|
||||||
|
/* A tweak cache consists of
|
||||||
|
* - 4 byte magic set during initialization to allow detecting an uninitialized
|
||||||
|
* object.
|
||||||
|
* - 64 byte aggregate (and potentially tweaked) public key
|
||||||
|
* - 1 byte the parity of the internal key (if tweaked, otherwise 0)
|
||||||
|
* - 32 byte tweak
|
||||||
|
*/
|
||||||
|
/* Requires that cache_i->pk is not infinity. */
|
||||||
|
static void secp256k1_tweak_cache_save(secp256k1_frost_tweak_cache *cache, secp256k1_tweak_cache_internal *cache_i) {
|
||||||
|
unsigned char *ptr = cache->data;
|
||||||
|
memcpy(ptr, secp256k1_frost_tweak_cache_magic, 4);
|
||||||
|
ptr += 4;
|
||||||
|
secp256k1_point_save_ext(ptr, &cache_i->pk);
|
||||||
|
ptr += 64;
|
||||||
|
*ptr = cache_i->parity_acc;
|
||||||
|
ptr += 1;
|
||||||
|
secp256k1_scalar_get_b32(ptr, &cache_i->tweak);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_tweak_cache_load(const secp256k1_context* ctx, secp256k1_tweak_cache_internal *cache_i, const secp256k1_frost_tweak_cache *cache) {
|
||||||
|
const unsigned char *ptr = cache->data;
|
||||||
|
ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_tweak_cache_magic, 4) == 0);
|
||||||
|
ptr += 4;
|
||||||
|
secp256k1_point_load_ext(&cache_i->pk, ptr);
|
||||||
|
ptr += 64;
|
||||||
|
cache_i->parity_acc = *ptr & 1;
|
||||||
|
ptr += 1;
|
||||||
|
secp256k1_scalar_set_b32(&cache_i->tweak, ptr, NULL);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned char secp256k1_frost_share_magic[4] = { 0xa1, 0x6a, 0x42, 0x03 };
|
||||||
|
|
||||||
|
static void secp256k1_frost_share_save(secp256k1_frost_share* share, secp256k1_scalar *s) {
|
||||||
|
memcpy(&share->data[0], secp256k1_frost_share_magic, 4);
|
||||||
|
secp256k1_scalar_get_b32(&share->data[4], s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_share_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_share* share) {
|
||||||
|
int overflow;
|
||||||
|
|
||||||
|
/* The magic is non-secret so it can be declassified to allow branching. */
|
||||||
|
secp256k1_declassify(ctx, &share->data[0], 4);
|
||||||
|
ARG_CHECK(secp256k1_memcmp_var(&share->data[0], secp256k1_frost_share_magic, 4) == 0);
|
||||||
|
secp256k1_scalar_set_b32(s, &share->data[4], &overflow);
|
||||||
|
/* Parsed shares cannot overflow */
|
||||||
|
VERIFY_CHECK(!overflow);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_share_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_share* share) {
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(out32 != NULL);
|
||||||
|
ARG_CHECK(share != NULL);
|
||||||
|
memcpy(out32, &share->data[4], 32);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_share_parse(const secp256k1_context* ctx, secp256k1_frost_share* share, const unsigned char *in32) {
|
||||||
|
secp256k1_scalar tmp;
|
||||||
|
int overflow;
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(share != NULL);
|
||||||
|
ARG_CHECK(in32 != NULL);
|
||||||
|
|
||||||
|
secp256k1_scalar_set_b32(&tmp, in32, &overflow);
|
||||||
|
if (overflow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_frost_share_save(share, &tmp);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_shares_trusted_gen(const secp256k1_context *ctx, secp256k1_frost_share *shares, secp256k1_pubkey *pubshares, secp256k1_xonly_pubkey *pk, const unsigned char *seed32, size_t threshold, size_t n_participants) {
|
||||||
|
secp256k1_sha256 sha;
|
||||||
|
secp256k1_gej rj;
|
||||||
|
secp256k1_ge rp;
|
||||||
|
unsigned char polygen[32];
|
||||||
|
size_t i, j;
|
||||||
|
int ret = 1;
|
||||||
|
int pk_parity = 0;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
|
||||||
|
ARG_CHECK(shares != NULL);
|
||||||
|
for (i = 0; i < n_participants; i++) {
|
||||||
|
memset(&shares[i], 0, sizeof(shares[i]));
|
||||||
|
}
|
||||||
|
ARG_CHECK(pubshares != NULL);
|
||||||
|
ARG_CHECK(pk != NULL);
|
||||||
|
ARG_CHECK(seed32 != NULL);
|
||||||
|
ARG_CHECK(threshold > 1);
|
||||||
|
ARG_CHECK(n_participants >= threshold);
|
||||||
|
|
||||||
|
/* Commit to threshold, n_participants, and seed */
|
||||||
|
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/trusted-shares-polygen", sizeof("FROST/trusted-shares-polygen") - 1);
|
||||||
|
secp256k1_sha256_write(&sha, seed32, 32);
|
||||||
|
secp256k1_write_be64(&polygen[0], threshold);
|
||||||
|
secp256k1_write_be64(&polygen[8], n_participants);
|
||||||
|
secp256k1_sha256_write(&sha, polygen, 16);
|
||||||
|
secp256k1_sha256_finalize(&sha, polygen);
|
||||||
|
|
||||||
|
/* Derive shares */
|
||||||
|
/* See draft-irtf-cfrg-frost-08#appendix-C.1 */
|
||||||
|
for (i = 0; i < n_participants; i++) {
|
||||||
|
secp256k1_scalar share_i, idx;
|
||||||
|
|
||||||
|
secp256k1_scalar_set_int(&share_i, 0);
|
||||||
|
|
||||||
|
for (j = 0; j < threshold; j++) {
|
||||||
|
unsigned char buf[32];
|
||||||
|
secp256k1_scalar coeff_i;
|
||||||
|
|
||||||
|
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/trusted-shares-coeffgen", sizeof("FROST/trusted-shares-coeffgen") - 1);
|
||||||
|
secp256k1_sha256_write(&sha, polygen, 32);
|
||||||
|
secp256k1_write_be64(&buf[0], j);
|
||||||
|
secp256k1_sha256_write(&sha, buf, 8);
|
||||||
|
secp256k1_sha256_finalize(&sha, buf);
|
||||||
|
secp256k1_scalar_set_b32(&coeff_i, buf, NULL);
|
||||||
|
|
||||||
|
/* Horner's method to evaluate polynomial to derive shares */
|
||||||
|
secp256k1_scalar_add(&share_i, &share_i, &coeff_i);
|
||||||
|
if (j < threshold - 1) {
|
||||||
|
secp256k1_scalar_set_int(&idx, i + 1);
|
||||||
|
secp256k1_scalar_mul(&share_i, &share_i, &idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute x-only public key for constant term */
|
||||||
|
if (i == 0 && j == threshold - 1) {
|
||||||
|
/* Compute commitment to constant term */
|
||||||
|
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &coeff_i);
|
||||||
|
secp256k1_ge_set_gej(&rp, &rj);
|
||||||
|
/* The commitment is non-secret so it can be declassified to
|
||||||
|
* allow branching. */
|
||||||
|
secp256k1_declassify(ctx, &rp, sizeof(rp));
|
||||||
|
secp256k1_fe_normalize_var(&rp.y);
|
||||||
|
pk_parity = secp256k1_extrakeys_ge_even_y(&rp);
|
||||||
|
secp256k1_xonly_pubkey_save(pk, &rp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pk_parity == 1) {
|
||||||
|
secp256k1_scalar_negate(&share_i, &share_i);
|
||||||
|
}
|
||||||
|
secp256k1_frost_share_save(&shares[i], &share_i);
|
||||||
|
/* Compute pubshare */
|
||||||
|
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &share_i);
|
||||||
|
secp256k1_ge_set_gej(&rp, &rj);
|
||||||
|
secp256k1_pubkey_save(&pubshares[i], &rp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *ec_pk, const secp256k1_xonly_pubkey *xonly_pk) {
|
||||||
|
secp256k1_ge pk;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(ec_pk != NULL);
|
||||||
|
memset(ec_pk, 0, sizeof(*ec_pk));
|
||||||
|
ARG_CHECK(xonly_pk != NULL);
|
||||||
|
|
||||||
|
/* The output of keygen is an aggregated public key that *always* has an
|
||||||
|
* even Y coordinate. */
|
||||||
|
if (!secp256k1_xonly_pubkey_load(ctx, &pk, xonly_pk)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_pubkey_save(ec_pk, &pk);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_pubkey_tweak(const secp256k1_context* ctx, secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_xonly_pubkey *pk) {
|
||||||
|
secp256k1_tweak_cache_internal cache_i = { 0 };
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(tweak_cache != NULL);
|
||||||
|
ARG_CHECK(pk != NULL);
|
||||||
|
|
||||||
|
/* The output of keygen is an aggregated public key that *always* has an
|
||||||
|
* even Y coordinate. */
|
||||||
|
if (!secp256k1_xonly_pubkey_load(ctx, &cache_i.pk, pk)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_tweak_cache_save(tweak_cache, &cache_i);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32, int xonly) {
|
||||||
|
secp256k1_tweak_cache_internal cache_i;
|
||||||
|
int overflow = 0;
|
||||||
|
secp256k1_scalar tweak;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
if (output_pubkey != NULL) {
|
||||||
|
memset(output_pubkey, 0, sizeof(*output_pubkey));
|
||||||
|
}
|
||||||
|
ARG_CHECK(tweak_cache != NULL);
|
||||||
|
ARG_CHECK(tweak32 != NULL);
|
||||||
|
|
||||||
|
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_scalar_set_b32(&tweak, tweak32, &overflow);
|
||||||
|
if (overflow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (xonly && secp256k1_extrakeys_ge_even_y(&cache_i.pk)) {
|
||||||
|
cache_i.parity_acc ^= 1;
|
||||||
|
secp256k1_scalar_negate(&cache_i.tweak, &cache_i.tweak);
|
||||||
|
}
|
||||||
|
secp256k1_scalar_add(&cache_i.tweak, &cache_i.tweak, &tweak);
|
||||||
|
if (!secp256k1_eckey_pubkey_tweak_add(&cache_i.pk, &tweak)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* eckey_pubkey_tweak_add fails if cache_i.pk is infinity */
|
||||||
|
VERIFY_CHECK(!secp256k1_ge_is_infinity(&cache_i.pk));
|
||||||
|
secp256k1_tweak_cache_save(tweak_cache, &cache_i);
|
||||||
|
if (output_pubkey != NULL) {
|
||||||
|
secp256k1_pubkey_save(output_pubkey, &cache_i.pk);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_pubkey_ec_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32) {
|
||||||
|
return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32) {
|
||||||
|
return secp256k1_frost_pubkey_tweak_add_internal(ctx, output_pubkey, tweak_cache, tweak32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
14
src/modules/frost/main_impl.h
Normal file
14
src/modules/frost/main_impl.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
* Copyright (c) 2021-2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SECP256K1_MODULE_FROST_MAIN
|
||||||
|
#define SECP256K1_MODULE_FROST_MAIN
|
||||||
|
|
||||||
|
#include "keygen_impl.h"
|
||||||
|
#include "session_impl.h"
|
||||||
|
#include "adaptor_impl.h"
|
||||||
|
|
||||||
|
#endif
|
25
src/modules/frost/session.h
Normal file
25
src/modules/frost/session.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
* Copyright (c) 2021-2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SECP256K1_MODULE_FROST_SESSION_H
|
||||||
|
#define SECP256K1_MODULE_FROST_SESSION_H
|
||||||
|
|
||||||
|
#include "../../../include/secp256k1.h"
|
||||||
|
#include "../../../include/secp256k1_frost.h"
|
||||||
|
|
||||||
|
#include "../../scalar.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int fin_nonce_parity;
|
||||||
|
unsigned char fin_nonce[32];
|
||||||
|
secp256k1_scalar noncecoef;
|
||||||
|
secp256k1_scalar challenge;
|
||||||
|
secp256k1_scalar s_part;
|
||||||
|
} secp256k1_frost_session_internal;
|
||||||
|
|
||||||
|
static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_frost_session_internal *session_i, const secp256k1_frost_session *session);
|
||||||
|
|
||||||
|
#endif
|
658
src/modules/frost/session_impl.h
Normal file
658
src/modules/frost/session_impl.h
Normal file
@ -0,0 +1,658 @@
|
|||||||
|
/**********************************************************************
|
||||||
|
* Copyright (c) 2021-2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SECP256K1_MODULE_FROST_SESSION_IMPL_H
|
||||||
|
#define SECP256K1_MODULE_FROST_SESSION_IMPL_H
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../../../include/secp256k1.h"
|
||||||
|
#include "../../../include/secp256k1_extrakeys.h"
|
||||||
|
#include "../../../include/secp256k1_frost.h"
|
||||||
|
|
||||||
|
#include "keygen.h"
|
||||||
|
#include "session.h"
|
||||||
|
#include "../../eckey.h"
|
||||||
|
#include "../../hash.h"
|
||||||
|
#include "../../scalar.h"
|
||||||
|
#include "../../util.h"
|
||||||
|
|
||||||
|
static const unsigned char secp256k1_frost_secnonce_magic[4] = { 0x84, 0x7d, 0x46, 0x25 };
|
||||||
|
|
||||||
|
static void secp256k1_frost_secnonce_save(secp256k1_frost_secnonce *secnonce, secp256k1_scalar *k) {
|
||||||
|
memcpy(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4);
|
||||||
|
secp256k1_scalar_get_b32(&secnonce->data[4], &k[0]);
|
||||||
|
secp256k1_scalar_get_b32(&secnonce->data[36], &k[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_secnonce_load(const secp256k1_context* ctx, secp256k1_scalar *k, secp256k1_frost_secnonce *secnonce) {
|
||||||
|
int is_zero;
|
||||||
|
ARG_CHECK(secp256k1_memcmp_var(&secnonce->data[0], secp256k1_frost_secnonce_magic, 4) == 0);
|
||||||
|
secp256k1_scalar_set_b32(&k[0], &secnonce->data[4], NULL);
|
||||||
|
secp256k1_scalar_set_b32(&k[1], &secnonce->data[36], NULL);
|
||||||
|
/* We make very sure that the nonce isn't invalidated by checking the values
|
||||||
|
* in addition to the magic. */
|
||||||
|
is_zero = secp256k1_scalar_is_zero(&k[0]) & secp256k1_scalar_is_zero(&k[1]);
|
||||||
|
secp256k1_declassify(ctx, &is_zero, sizeof(is_zero));
|
||||||
|
ARG_CHECK(!is_zero);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If flag is true, invalidate the secnonce; otherwise leave it. Constant-time. */
|
||||||
|
static void secp256k1_frost_secnonce_invalidate(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, int flag) {
|
||||||
|
secp256k1_memczero(secnonce->data, sizeof(secnonce->data), flag);
|
||||||
|
/* The flag argument is usually classified. So, above code makes the magic
|
||||||
|
* classified. However, we need the magic to be declassified to be able to
|
||||||
|
* compare it during secnonce_load. */
|
||||||
|
secp256k1_declassify(ctx, secnonce->data, sizeof(secp256k1_frost_secnonce_magic));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned char secp256k1_frost_pubnonce_magic[4] = { 0x8b, 0xcf, 0xe2, 0xc2 };
|
||||||
|
|
||||||
|
/* Requires that none of the provided group elements is infinity. Works for both
|
||||||
|
* frost_pubnonce and frost_aggnonce. */
|
||||||
|
static void secp256k1_frost_pubnonce_save(secp256k1_frost_pubnonce* nonce, secp256k1_ge* ge) {
|
||||||
|
int i;
|
||||||
|
memcpy(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4);
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
secp256k1_point_save_ext(nonce->data + 4+64*i, &ge[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Works for both frost_pubnonce and frost_aggnonce. Returns 1 unless the nonce
|
||||||
|
* wasn't properly initialized */
|
||||||
|
static int secp256k1_frost_pubnonce_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_frost_pubnonce* nonce) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ARG_CHECK(secp256k1_memcmp_var(&nonce->data[0], secp256k1_frost_pubnonce_magic, 4) == 0);
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
secp256k1_point_load_ext(&ge[i], nonce->data + 4+64*i);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned char secp256k1_frost_session_cache_magic[4] = { 0x5c, 0x11, 0xa8, 0x3 };
|
||||||
|
|
||||||
|
/* A session consists of
|
||||||
|
* - 4 byte session cache magic
|
||||||
|
* - 1 byte the parity of the final nonce
|
||||||
|
* - 32 byte serialized x-only final nonce
|
||||||
|
* - 32 byte nonce coefficient b
|
||||||
|
* - 32 byte signature challenge hash e
|
||||||
|
* - 32 byte scalar s that is added to the partial signatures of the signers
|
||||||
|
*/
|
||||||
|
static void secp256k1_frost_session_save(secp256k1_frost_session *session, const secp256k1_frost_session_internal *session_i) {
|
||||||
|
unsigned char *ptr = session->data;
|
||||||
|
|
||||||
|
memcpy(ptr, secp256k1_frost_session_cache_magic, 4);
|
||||||
|
ptr += 4;
|
||||||
|
*ptr = session_i->fin_nonce_parity;
|
||||||
|
ptr += 1;
|
||||||
|
memcpy(ptr, session_i->fin_nonce, 32);
|
||||||
|
ptr += 32;
|
||||||
|
secp256k1_scalar_get_b32(ptr, &session_i->noncecoef);
|
||||||
|
ptr += 32;
|
||||||
|
secp256k1_scalar_get_b32(ptr, &session_i->challenge);
|
||||||
|
ptr += 32;
|
||||||
|
secp256k1_scalar_get_b32(ptr, &session_i->s_part);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_session_load(const secp256k1_context* ctx, secp256k1_frost_session_internal *session_i, const secp256k1_frost_session *session) {
|
||||||
|
const unsigned char *ptr = session->data;
|
||||||
|
|
||||||
|
ARG_CHECK(secp256k1_memcmp_var(ptr, secp256k1_frost_session_cache_magic, 4) == 0);
|
||||||
|
ptr += 4;
|
||||||
|
session_i->fin_nonce_parity = *ptr;
|
||||||
|
ptr += 1;
|
||||||
|
memcpy(session_i->fin_nonce, ptr, 32);
|
||||||
|
ptr += 32;
|
||||||
|
secp256k1_scalar_set_b32(&session_i->noncecoef, ptr, NULL);
|
||||||
|
ptr += 32;
|
||||||
|
secp256k1_scalar_set_b32(&session_i->challenge, ptr, NULL);
|
||||||
|
ptr += 32;
|
||||||
|
secp256k1_scalar_set_b32(&session_i->s_part, ptr, NULL);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const unsigned char secp256k1_frost_partial_sig_magic[4] = { 0x8d, 0xd8, 0x31, 0x6e };
|
||||||
|
|
||||||
|
static void secp256k1_frost_partial_sig_save(secp256k1_frost_partial_sig* sig, secp256k1_scalar *s) {
|
||||||
|
memcpy(&sig->data[0], secp256k1_frost_partial_sig_magic, 4);
|
||||||
|
secp256k1_scalar_get_b32(&sig->data[4], s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_partial_sig_load(const secp256k1_context* ctx, secp256k1_scalar *s, const secp256k1_frost_partial_sig* sig) {
|
||||||
|
int overflow;
|
||||||
|
|
||||||
|
ARG_CHECK(secp256k1_memcmp_var(&sig->data[0], secp256k1_frost_partial_sig_magic, 4) == 0);
|
||||||
|
secp256k1_scalar_set_b32(s, &sig->data[4], &overflow);
|
||||||
|
/* Parsed signatures can not overflow */
|
||||||
|
VERIFY_CHECK(!overflow);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_pubnonce_serialize(const secp256k1_context* ctx, unsigned char *out66, const secp256k1_frost_pubnonce* nonce) {
|
||||||
|
secp256k1_ge ge[2];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(out66 != NULL);
|
||||||
|
memset(out66, 0, 66);
|
||||||
|
ARG_CHECK(nonce != NULL);
|
||||||
|
|
||||||
|
if (!secp256k1_frost_pubnonce_load(ctx, ge, nonce)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
int ret;
|
||||||
|
size_t size = 33;
|
||||||
|
ret = secp256k1_eckey_pubkey_serialize(&ge[i], &out66[33*i], &size, 1);
|
||||||
|
#ifdef VERIFY
|
||||||
|
/* serialize must succeed because the point was just loaded */
|
||||||
|
VERIFY_CHECK(ret && size == 33);
|
||||||
|
#else
|
||||||
|
(void) ret;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_pubnonce_parse(const secp256k1_context* ctx, secp256k1_frost_pubnonce* nonce, const unsigned char *in66) {
|
||||||
|
secp256k1_ge ge[2];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(nonce != NULL);
|
||||||
|
ARG_CHECK(in66 != NULL);
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
if (!secp256k1_eckey_pubkey_parse(&ge[i], &in66[33*i], 33)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!secp256k1_ge_is_in_correct_subgroup(&ge[i])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* The group elements can not be infinity because they were just parsed */
|
||||||
|
secp256k1_frost_pubnonce_save(nonce, ge);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_partial_sig_serialize(const secp256k1_context* ctx, unsigned char *out32, const secp256k1_frost_partial_sig* sig) {
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(out32 != NULL);
|
||||||
|
ARG_CHECK(sig != NULL);
|
||||||
|
memcpy(out32, &sig->data[4], 32);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_partial_sig_parse(const secp256k1_context* ctx, secp256k1_frost_partial_sig* sig, const unsigned char *in32) {
|
||||||
|
secp256k1_scalar tmp;
|
||||||
|
int overflow;
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(sig != NULL);
|
||||||
|
ARG_CHECK(in32 != NULL);
|
||||||
|
|
||||||
|
secp256k1_scalar_set_b32(&tmp, in32, &overflow);
|
||||||
|
if (overflow) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_frost_partial_sig_save(sig, &tmp);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void secp256k1_nonce_function_frost(secp256k1_scalar *k, const unsigned char *session_id, const unsigned char *msg32, const unsigned char *key32, const unsigned char *pk32, const unsigned char *extra_input32) {
|
||||||
|
secp256k1_sha256 sha;
|
||||||
|
unsigned char seed[32];
|
||||||
|
unsigned char i;
|
||||||
|
enum { n_extra_in = 4 };
|
||||||
|
const unsigned char *extra_in[n_extra_in];
|
||||||
|
|
||||||
|
/* TODO: this doesn't have the same sidechannel resistance as the BIP340
|
||||||
|
* nonce function because the seckey feeds directly into SHA. */
|
||||||
|
|
||||||
|
/* Subtract one from `sizeof` to avoid hashing the implicit null byte */
|
||||||
|
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/nonce", sizeof("FROST/nonce") - 1);
|
||||||
|
secp256k1_sha256_write(&sha, session_id, 32);
|
||||||
|
extra_in[0] = msg32;
|
||||||
|
extra_in[1] = key32;
|
||||||
|
extra_in[2] = pk32;
|
||||||
|
extra_in[3] = extra_input32;
|
||||||
|
for (i = 0; i < n_extra_in; i++) {
|
||||||
|
unsigned char len;
|
||||||
|
if (extra_in[i] != NULL) {
|
||||||
|
len = 32;
|
||||||
|
secp256k1_sha256_write(&sha, &len, 1);
|
||||||
|
secp256k1_sha256_write(&sha, extra_in[i], 32);
|
||||||
|
} else {
|
||||||
|
len = 0;
|
||||||
|
secp256k1_sha256_write(&sha, &len, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secp256k1_sha256_finalize(&sha, seed);
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
unsigned char buf[32];
|
||||||
|
secp256k1_sha256_initialize(&sha);
|
||||||
|
secp256k1_sha256_write(&sha, seed, 32);
|
||||||
|
secp256k1_sha256_write(&sha, &i, sizeof(i));
|
||||||
|
secp256k1_sha256_finalize(&sha, buf);
|
||||||
|
secp256k1_scalar_set_b32(&k[i], buf, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_nonce_gen(const secp256k1_context* ctx, secp256k1_frost_secnonce *secnonce, secp256k1_frost_pubnonce *pubnonce, const unsigned char *session_id32, const secp256k1_frost_share *share, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, const unsigned char *extra_input32) {
|
||||||
|
secp256k1_scalar k[2];
|
||||||
|
secp256k1_ge nonce_pt[2];
|
||||||
|
int i;
|
||||||
|
unsigned char pk_ser[32];
|
||||||
|
unsigned char *pk_ser_ptr = NULL;
|
||||||
|
unsigned char sk_ser[32];
|
||||||
|
unsigned char *sk_ser_ptr = NULL;
|
||||||
|
int sk_serialize_success;
|
||||||
|
int ret = 1;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(secnonce != NULL);
|
||||||
|
memset(secnonce, 0, sizeof(*secnonce));
|
||||||
|
ARG_CHECK(pubnonce != NULL);
|
||||||
|
memset(pubnonce, 0, sizeof(*pubnonce));
|
||||||
|
ARG_CHECK(session_id32 != NULL);
|
||||||
|
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
|
||||||
|
if (share == NULL) {
|
||||||
|
/* Check in constant time that the session_id is not 0 as a
|
||||||
|
* defense-in-depth measure that may protect against a faulty RNG. */
|
||||||
|
unsigned char acc = 0;
|
||||||
|
for (i = 0; i < 32; i++) {
|
||||||
|
acc |= session_id32[i];
|
||||||
|
}
|
||||||
|
ret &= !!acc;
|
||||||
|
memset(&acc, 0, sizeof(acc));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (share != NULL) {
|
||||||
|
/* Check that the share is valid to be able to sign for it later. */
|
||||||
|
secp256k1_scalar sk;
|
||||||
|
|
||||||
|
ret &= secp256k1_frost_share_load(ctx, &sk, share);
|
||||||
|
secp256k1_scalar_clear(&sk);
|
||||||
|
|
||||||
|
sk_serialize_success = secp256k1_frost_share_serialize(ctx, sk_ser, share);
|
||||||
|
sk_ser_ptr = sk_ser;
|
||||||
|
#ifdef VERIFY
|
||||||
|
VERIFY_CHECK(sk_serialize_success);
|
||||||
|
#else
|
||||||
|
(void) sk_serialize_success;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pk != NULL) {
|
||||||
|
if (!secp256k1_xonly_pubkey_serialize(ctx, pk_ser, pk)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pk_ser_ptr = pk_ser;
|
||||||
|
}
|
||||||
|
secp256k1_nonce_function_frost(k, session_id32, msg32, sk_ser_ptr, pk_ser_ptr, extra_input32);
|
||||||
|
VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[0]));
|
||||||
|
VERIFY_CHECK(!secp256k1_scalar_is_zero(&k[1]));
|
||||||
|
VERIFY_CHECK(!secp256k1_scalar_eq(&k[0], &k[1]));
|
||||||
|
secp256k1_frost_secnonce_save(secnonce, k);
|
||||||
|
secp256k1_frost_secnonce_invalidate(ctx, secnonce, !ret);
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
secp256k1_gej nonce_ptj;
|
||||||
|
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj, &k[i]);
|
||||||
|
secp256k1_ge_set_gej(&nonce_pt[i], &nonce_ptj);
|
||||||
|
secp256k1_declassify(ctx, &nonce_pt[i], sizeof(nonce_pt));
|
||||||
|
secp256k1_scalar_clear(&k[i]);
|
||||||
|
}
|
||||||
|
/* nonce_pt won't be infinity because k != 0 with overwhelming probability */
|
||||||
|
secp256k1_frost_pubnonce_save(pubnonce, nonce_pt);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_sum_nonces(const secp256k1_context* ctx, secp256k1_gej *summed_nonces, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces) {
|
||||||
|
size_t i;
|
||||||
|
int j;
|
||||||
|
|
||||||
|
secp256k1_gej_set_infinity(&summed_nonces[0]);
|
||||||
|
secp256k1_gej_set_infinity(&summed_nonces[1]);
|
||||||
|
|
||||||
|
for (i = 0; i < n_pubnonces; i++) {
|
||||||
|
secp256k1_ge nonce_pt[2];
|
||||||
|
if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonces[i])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (j = 0; j < 2; j++) {
|
||||||
|
secp256k1_gej_add_ge_var(&summed_nonces[j], &summed_nonces[j], &nonce_pt[j], NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: consider updating to frost-08 to address maleability at the cost of performance */
|
||||||
|
/* See https://github.com/cfrg/draft-irtf-cfrg-frost/pull/217 */
|
||||||
|
static int secp256k1_frost_compute_noncehash(const secp256k1_context* ctx, unsigned char *noncehash, const unsigned char *msg, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *pk32, const size_t *ids) {
|
||||||
|
unsigned char buf[66];
|
||||||
|
secp256k1_sha256 sha;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
secp256k1_sha256_initialize_tagged(&sha, (unsigned char*)"FROST/noncecoef", sizeof("FROST/noncecoef") - 1);
|
||||||
|
for (i = 0; i < n_pubnonces; i++) {
|
||||||
|
secp256k1_scalar idx;
|
||||||
|
|
||||||
|
secp256k1_scalar_set_int(&idx, ids[i]);
|
||||||
|
secp256k1_scalar_get_b32(buf, &idx);
|
||||||
|
secp256k1_sha256_write(&sha, buf, 32);
|
||||||
|
if (!secp256k1_frost_pubnonce_serialize(ctx, buf, pubnonces[i])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_sha256_write(&sha, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
secp256k1_sha256_write(&sha, pk32, 32);
|
||||||
|
secp256k1_sha256_write(&sha, msg, 32);
|
||||||
|
secp256k1_sha256_finalize(&sha, noncehash);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_nonce_process_internal(const secp256k1_context* ctx, int *fin_nonce_parity, unsigned char *fin_nonce, secp256k1_scalar *b, secp256k1_gej *aggnoncej, const unsigned char *msg, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *pk32, const size_t *ids) {
|
||||||
|
unsigned char noncehash[32];
|
||||||
|
secp256k1_ge fin_nonce_pt;
|
||||||
|
secp256k1_gej fin_nonce_ptj;
|
||||||
|
secp256k1_ge aggnonce[2];
|
||||||
|
|
||||||
|
secp256k1_ge_set_gej(&aggnonce[0], &aggnoncej[0]);
|
||||||
|
secp256k1_ge_set_gej(&aggnonce[1], &aggnoncej[1]);
|
||||||
|
if (!secp256k1_frost_compute_noncehash(ctx, noncehash, msg, pubnonces, n_pubnonces, pk32, ids)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* fin_nonce = aggnonce[0] + b*aggnonce[1] */
|
||||||
|
secp256k1_scalar_set_b32(b, noncehash, NULL);
|
||||||
|
secp256k1_ecmult(&fin_nonce_ptj, &aggnoncej[1], b, NULL);
|
||||||
|
secp256k1_gej_add_ge_var(&fin_nonce_ptj, &fin_nonce_ptj, &aggnonce[0], NULL);
|
||||||
|
secp256k1_ge_set_gej(&fin_nonce_pt, &fin_nonce_ptj);
|
||||||
|
|
||||||
|
if (secp256k1_ge_is_infinity(&fin_nonce_pt)) {
|
||||||
|
/* unreachable with overwhelming probability */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_fe_normalize_var(&fin_nonce_pt.x);
|
||||||
|
secp256k1_fe_get_b32(fin_nonce, &fin_nonce_pt.x);
|
||||||
|
|
||||||
|
secp256k1_fe_normalize_var(&fin_nonce_pt.y);
|
||||||
|
*fin_nonce_parity = secp256k1_fe_is_odd(&fin_nonce_pt.y);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int secp256k1_frost_lagrange_coefficient(secp256k1_scalar *r, const size_t *ids, size_t n_participants, size_t my_id) {
|
||||||
|
size_t i;
|
||||||
|
secp256k1_scalar num;
|
||||||
|
secp256k1_scalar den;
|
||||||
|
secp256k1_scalar party_idx;
|
||||||
|
|
||||||
|
secp256k1_scalar_set_int(&num, 1);
|
||||||
|
secp256k1_scalar_set_int(&den, 1);
|
||||||
|
secp256k1_scalar_set_int(&party_idx, my_id);
|
||||||
|
for (i = 0; i < n_participants; i++) {
|
||||||
|
secp256k1_scalar mul;
|
||||||
|
|
||||||
|
secp256k1_scalar_set_int(&mul, ids[i]);
|
||||||
|
if (secp256k1_scalar_eq(&mul, &party_idx)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_scalar_negate(&mul, &mul);
|
||||||
|
secp256k1_scalar_mul(&num, &num, &mul);
|
||||||
|
secp256k1_scalar_add(&mul, &mul, &party_idx);
|
||||||
|
secp256k1_scalar_mul(&den, &den, &mul);
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_scalar_inverse_var(&den, &den);
|
||||||
|
secp256k1_scalar_mul(r, &num, &den);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_nonce_process(const secp256k1_context* ctx, secp256k1_frost_session *session, const secp256k1_frost_pubnonce * const* pubnonces, size_t n_pubnonces, const unsigned char *msg32, const secp256k1_xonly_pubkey *pk, size_t my_id, const size_t *ids, const secp256k1_frost_tweak_cache *tweak_cache, const secp256k1_pubkey *adaptor) {
|
||||||
|
secp256k1_ge aggnonce_pt[2];
|
||||||
|
secp256k1_gej aggnonce_ptj[2];
|
||||||
|
unsigned char fin_nonce[32];
|
||||||
|
secp256k1_frost_session_internal session_i = { 0 };
|
||||||
|
unsigned char pk32[32];
|
||||||
|
size_t i;
|
||||||
|
secp256k1_scalar l;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(session != NULL);
|
||||||
|
ARG_CHECK(msg32 != NULL);
|
||||||
|
ARG_CHECK(pubnonces != NULL);
|
||||||
|
ARG_CHECK(ids != NULL);
|
||||||
|
ARG_CHECK(n_pubnonces > 1);
|
||||||
|
ARG_CHECK(my_id != 0);
|
||||||
|
for (i = 0; i < n_pubnonces; i++) {
|
||||||
|
ARG_CHECK(ids[i] != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secp256k1_xonly_pubkey_serialize(ctx, pk32, pk)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secp256k1_frost_sum_nonces(ctx, aggnonce_ptj, pubnonces, n_pubnonces)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
if (secp256k1_gej_is_infinity(&aggnonce_ptj[i])) {
|
||||||
|
/* There must be at least one dishonest signer. If we would return 0
|
||||||
|
here, we will never be able to determine who it is. Therefore, we
|
||||||
|
should continue such that the culprit is revealed when collecting
|
||||||
|
and verifying partial signatures.
|
||||||
|
|
||||||
|
However, dealing with the point at infinity (loading,
|
||||||
|
de-/serializing) would require a lot of extra code complexity.
|
||||||
|
Instead, we set the aggregate nonce to some arbitrary point (the
|
||||||
|
generator). This is secure, because it only restricts the
|
||||||
|
abilities of the attacker: an attacker that forces the sum of
|
||||||
|
nonces to be infinity by sending some maliciously generated nonce
|
||||||
|
pairs can be turned into an attacker that forces the sum to be
|
||||||
|
the generator (by simply adding the generator to one of the
|
||||||
|
malicious nonces), and this does not change the winning condition
|
||||||
|
of the EUF-CMA game. */
|
||||||
|
aggnonce_pt[i] = secp256k1_ge_const_g;
|
||||||
|
} else {
|
||||||
|
secp256k1_ge_set_gej(&aggnonce_pt[i], &aggnonce_ptj[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Add public adaptor to nonce */
|
||||||
|
if (adaptor != NULL) {
|
||||||
|
secp256k1_ge adaptorp;
|
||||||
|
if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_gej_add_ge_var(&aggnonce_ptj[0], &aggnonce_ptj[0], &adaptorp, NULL);
|
||||||
|
}
|
||||||
|
if (!secp256k1_frost_nonce_process_internal(ctx, &session_i.fin_nonce_parity, fin_nonce, &session_i.noncecoef, aggnonce_ptj, msg32, pubnonces, n_pubnonces, pk32, ids)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_schnorrsig_challenge(&session_i.challenge, fin_nonce, msg32, 32, pk32);
|
||||||
|
|
||||||
|
/* If there is a tweak then set `challenge` times `tweak` to the `s`-part.*/
|
||||||
|
secp256k1_scalar_set_int(&session_i.s_part, 0);
|
||||||
|
if (tweak_cache != NULL) {
|
||||||
|
secp256k1_tweak_cache_internal cache_i;
|
||||||
|
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!secp256k1_scalar_is_zero(&cache_i.tweak)) {
|
||||||
|
secp256k1_scalar e_tmp;
|
||||||
|
secp256k1_scalar_mul(&e_tmp, &session_i.challenge, &cache_i.tweak);
|
||||||
|
if (secp256k1_fe_is_odd(&cache_i.pk.y)) {
|
||||||
|
secp256k1_scalar_negate(&e_tmp, &e_tmp);
|
||||||
|
}
|
||||||
|
secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &e_tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Update the challenge by multiplying the Lagrange coefficient to prepare
|
||||||
|
* for signing. */
|
||||||
|
if (!secp256k1_frost_lagrange_coefficient(&l, ids, n_pubnonces, my_id)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_scalar_mul(&session_i.challenge, &session_i.challenge, &l);
|
||||||
|
memcpy(session_i.fin_nonce, fin_nonce, sizeof(session_i.fin_nonce));
|
||||||
|
secp256k1_frost_session_save(session, &session_i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void secp256k1_frost_partial_sign_clear(secp256k1_scalar *sk, secp256k1_scalar *k) {
|
||||||
|
secp256k1_scalar_clear(sk);
|
||||||
|
secp256k1_scalar_clear(&k[0]);
|
||||||
|
secp256k1_scalar_clear(&k[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_partial_sign(const secp256k1_context* ctx, secp256k1_frost_partial_sig *partial_sig, secp256k1_frost_secnonce *secnonce, const secp256k1_frost_share *share, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) {
|
||||||
|
secp256k1_scalar sk;
|
||||||
|
secp256k1_scalar k[2];
|
||||||
|
secp256k1_scalar s;
|
||||||
|
secp256k1_frost_session_internal session_i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
|
||||||
|
ARG_CHECK(secnonce != NULL);
|
||||||
|
/* Fails if the magic doesn't match */
|
||||||
|
ret = secp256k1_frost_secnonce_load(ctx, k, secnonce);
|
||||||
|
/* Set nonce to zero to avoid nonce reuse. This will cause subsequent calls
|
||||||
|
* of this function to fail */
|
||||||
|
memset(secnonce, 0, sizeof(*secnonce));
|
||||||
|
if (!ret) {
|
||||||
|
secp256k1_frost_partial_sign_clear(&sk, k);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARG_CHECK(partial_sig != NULL);
|
||||||
|
ARG_CHECK(share != NULL);
|
||||||
|
ARG_CHECK(session != NULL);
|
||||||
|
|
||||||
|
if (!secp256k1_frost_share_load(ctx, &sk, share)) {
|
||||||
|
secp256k1_frost_partial_sign_clear(&sk, k);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
|
||||||
|
secp256k1_frost_partial_sign_clear(&sk, k);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tweak_cache != NULL) {
|
||||||
|
secp256k1_tweak_cache_internal cache_i;
|
||||||
|
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
|
||||||
|
secp256k1_frost_partial_sign_clear(&sk, k);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (secp256k1_fe_is_odd(&cache_i.pk.y) != cache_i.parity_acc) {
|
||||||
|
secp256k1_scalar_negate(&sk, &sk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session_i.fin_nonce_parity) {
|
||||||
|
secp256k1_scalar_negate(&k[0], &k[0]);
|
||||||
|
secp256k1_scalar_negate(&k[1], &k[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sign */
|
||||||
|
secp256k1_scalar_mul(&s, &session_i.challenge, &sk);
|
||||||
|
secp256k1_scalar_mul(&k[1], &session_i.noncecoef, &k[1]);
|
||||||
|
secp256k1_scalar_add(&k[0], &k[0], &k[1]);
|
||||||
|
secp256k1_scalar_add(&s, &s, &k[0]);
|
||||||
|
secp256k1_frost_partial_sig_save(partial_sig, &s);
|
||||||
|
secp256k1_frost_partial_sign_clear(&sk, k);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_partial_sig_verify(const secp256k1_context* ctx, const secp256k1_frost_partial_sig *partial_sig, const secp256k1_frost_pubnonce *pubnonce, const secp256k1_pubkey *pubshare, const secp256k1_frost_session *session, const secp256k1_frost_tweak_cache *tweak_cache) {
|
||||||
|
secp256k1_frost_session_internal session_i;
|
||||||
|
secp256k1_scalar e, s;
|
||||||
|
secp256k1_gej pkj;
|
||||||
|
secp256k1_ge nonce_pt[2];
|
||||||
|
secp256k1_gej rj;
|
||||||
|
secp256k1_gej tmp;
|
||||||
|
secp256k1_ge pkp;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(partial_sig != NULL);
|
||||||
|
ARG_CHECK(pubnonce != NULL);
|
||||||
|
ARG_CHECK(pubshare != NULL);
|
||||||
|
ARG_CHECK(session != NULL);
|
||||||
|
|
||||||
|
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute "effective" nonce rj = aggnonce[0] + b*aggnonce[1] */
|
||||||
|
/* TODO: use multiexp to compute -s*G + e*pubshare + aggnonce[0] + b*aggnonce[1] */
|
||||||
|
if (!secp256k1_frost_pubnonce_load(ctx, nonce_pt, pubnonce)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_gej_set_ge(&rj, &nonce_pt[1]);
|
||||||
|
secp256k1_ecmult(&rj, &rj, &session_i.noncecoef, NULL);
|
||||||
|
secp256k1_gej_add_ge_var(&rj, &rj, &nonce_pt[0], NULL);
|
||||||
|
|
||||||
|
if (!secp256k1_pubkey_load(ctx, &pkp, pubshare)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_scalar_set_int(&e, 1);
|
||||||
|
if (tweak_cache != NULL) {
|
||||||
|
secp256k1_tweak_cache_internal cache_i;
|
||||||
|
if (!secp256k1_tweak_cache_load(ctx, &cache_i, tweak_cache)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||||
|
!= cache_i.parity_acc) {
|
||||||
|
secp256k1_scalar_negate(&e, &e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secp256k1_scalar_mul(&e, &e, &session_i.challenge);
|
||||||
|
|
||||||
|
if (!secp256k1_frost_partial_sig_load(ctx, &s, partial_sig)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Compute -s*G + e*pkj + rj (e already includes the lagrange coefficient l) */
|
||||||
|
secp256k1_scalar_negate(&s, &s);
|
||||||
|
secp256k1_gej_set_ge(&pkj, &pkp);
|
||||||
|
secp256k1_ecmult(&tmp, &pkj, &e, &s);
|
||||||
|
if (session_i.fin_nonce_parity) {
|
||||||
|
secp256k1_gej_neg(&rj, &rj);
|
||||||
|
}
|
||||||
|
secp256k1_gej_add_var(&tmp, &tmp, &rj, NULL);
|
||||||
|
|
||||||
|
return secp256k1_gej_is_infinity(&tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
int secp256k1_frost_partial_sig_agg(const secp256k1_context* ctx, unsigned char *sig64, const secp256k1_frost_session *session, const secp256k1_frost_partial_sig * const* partial_sigs, size_t n_sigs) {
|
||||||
|
size_t i;
|
||||||
|
secp256k1_frost_session_internal session_i;
|
||||||
|
|
||||||
|
VERIFY_CHECK(ctx != NULL);
|
||||||
|
ARG_CHECK(sig64 != NULL);
|
||||||
|
ARG_CHECK(session != NULL);
|
||||||
|
ARG_CHECK(partial_sigs != NULL);
|
||||||
|
ARG_CHECK(n_sigs > 0);
|
||||||
|
|
||||||
|
if (!secp256k1_frost_session_load(ctx, &session_i, session)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < n_sigs; i++) {
|
||||||
|
secp256k1_scalar term;
|
||||||
|
if (!secp256k1_frost_partial_sig_load(ctx, &term, partial_sigs[i])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
secp256k1_scalar_add(&session_i.s_part, &session_i.s_part, &term);
|
||||||
|
}
|
||||||
|
secp256k1_scalar_get_b32(&sig64[32], &session_i.s_part);
|
||||||
|
memcpy(&sig64[0], session_i.fin_nonce, 32);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
738
src/modules/frost/tests_impl.h
Normal file
738
src/modules/frost/tests_impl.h
Normal file
@ -0,0 +1,738 @@
|
|||||||
|
/***********************************************************************
|
||||||
|
* Copyright (c) 2022, 2023 Jesse Posner *
|
||||||
|
* Distributed under the MIT software license, see the accompanying *
|
||||||
|
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||||
|
***********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SECP256K1_MODULE_FROST_TESTS_IMPL_H
|
||||||
|
#define SECP256K1_MODULE_FROST_TESTS_IMPL_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../../../include/secp256k1.h"
|
||||||
|
#include "../../../include/secp256k1_extrakeys.h"
|
||||||
|
#include "../../../include/secp256k1_frost.h"
|
||||||
|
|
||||||
|
#include "session.h"
|
||||||
|
#include "keygen.h"
|
||||||
|
#include "../../scalar.h"
|
||||||
|
#include "../../scratch.h"
|
||||||
|
#include "../../field.h"
|
||||||
|
#include "../../group.h"
|
||||||
|
#include "../../hash.h"
|
||||||
|
#include "../../util.h"
|
||||||
|
|
||||||
|
/* Simple (non-adaptor, non-tweaked) 3-of-5 FROST aggregate, sign, verify
|
||||||
|
* test. */
|
||||||
|
void frost_simple_test(void) {
|
||||||
|
secp256k1_frost_pubnonce pubnonce[5];
|
||||||
|
const secp256k1_frost_pubnonce *pubnonce_ptr[5];
|
||||||
|
unsigned char msg[32];
|
||||||
|
secp256k1_xonly_pubkey pk;
|
||||||
|
unsigned char seed[32];
|
||||||
|
secp256k1_frost_share shares[5];
|
||||||
|
secp256k1_frost_secnonce secnonce[5];
|
||||||
|
secp256k1_pubkey pubshares[5];
|
||||||
|
secp256k1_frost_partial_sig partial_sig[5];
|
||||||
|
const secp256k1_frost_partial_sig *partial_sig_ptr[5];
|
||||||
|
unsigned char final_sig[64];
|
||||||
|
secp256k1_frost_session session;
|
||||||
|
int i;
|
||||||
|
size_t ids[5];
|
||||||
|
|
||||||
|
secp256k1_testrand256(seed);
|
||||||
|
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
pubnonce_ptr[i] = &pubnonce[i];
|
||||||
|
partial_sig_ptr[i] = &partial_sig[i];
|
||||||
|
ids[i] = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 5) == 1);
|
||||||
|
|
||||||
|
secp256k1_testrand256(msg);
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
unsigned char session_id[32];
|
||||||
|
|
||||||
|
secp256k1_testrand256(session_id);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id, &shares[i], NULL, NULL, NULL) == 1);
|
||||||
|
}
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, &pk, ids[i], ids, NULL, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &shares[i], &session, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[i], &pubnonce[i], &pubshares[i], &session, NULL) == 1);
|
||||||
|
}
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1);
|
||||||
|
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void frost_pubnonce_summing_to_inf(secp256k1_frost_pubnonce *pubnonce) {
|
||||||
|
secp256k1_ge ge[2];
|
||||||
|
int i;
|
||||||
|
secp256k1_gej summed_nonces[2];
|
||||||
|
const secp256k1_frost_pubnonce *pubnonce_ptr[2];
|
||||||
|
|
||||||
|
ge[0] = secp256k1_ge_const_g;
|
||||||
|
ge[1] = secp256k1_ge_const_g;
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
secp256k1_frost_pubnonce_save(&pubnonce[i], ge);
|
||||||
|
pubnonce_ptr[i] = &pubnonce[i];
|
||||||
|
secp256k1_ge_neg(&ge[0], &ge[0]);
|
||||||
|
secp256k1_ge_neg(&ge[1], &ge[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
secp256k1_frost_sum_nonces(CTX, summed_nonces, pubnonce_ptr, 2);
|
||||||
|
CHECK(secp256k1_gej_is_infinity(&summed_nonces[0]));
|
||||||
|
CHECK(secp256k1_gej_is_infinity(&summed_nonces[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
int frost_memcmp_and_randomize(unsigned char *value, const unsigned char *expected, size_t len) {
|
||||||
|
int ret;
|
||||||
|
size_t i;
|
||||||
|
ret = secp256k1_memcmp_var(value, expected, len);
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
value[i] = secp256k1_testrand_bits(8);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void frost_api_tests(void) {
|
||||||
|
secp256k1_frost_partial_sig partial_sig[5];
|
||||||
|
const secp256k1_frost_partial_sig *partial_sig_ptr[5];
|
||||||
|
secp256k1_frost_partial_sig invalid_partial_sig;
|
||||||
|
const secp256k1_frost_partial_sig *invalid_partial_sig_ptr[5];
|
||||||
|
unsigned char final_sig[64];
|
||||||
|
unsigned char pre_sig[64];
|
||||||
|
unsigned char buf[32];
|
||||||
|
/* unsigned char sk[5][32]; */
|
||||||
|
unsigned char max64[64];
|
||||||
|
unsigned char zeros68[68] = { 0 };
|
||||||
|
unsigned char session_id[5][32];
|
||||||
|
unsigned char seed[32];
|
||||||
|
secp256k1_frost_secnonce secnonce[5];
|
||||||
|
secp256k1_frost_secnonce secnonce_tmp;
|
||||||
|
secp256k1_frost_secnonce invalid_secnonce;
|
||||||
|
secp256k1_frost_pubnonce pubnonce[5];
|
||||||
|
const secp256k1_frost_pubnonce *pubnonce_ptr[5];
|
||||||
|
unsigned char pubnonce_ser[66];
|
||||||
|
secp256k1_frost_pubnonce inf_pubnonce[5];
|
||||||
|
secp256k1_frost_pubnonce invalid_pubnonce;
|
||||||
|
const secp256k1_frost_pubnonce *invalid_pubnonce_ptr[5];
|
||||||
|
unsigned char msg[32];
|
||||||
|
secp256k1_xonly_pubkey pk;
|
||||||
|
secp256k1_pubkey full_pk;
|
||||||
|
secp256k1_frost_tweak_cache tweak_cache;
|
||||||
|
secp256k1_frost_tweak_cache invalid_tweak_cache;
|
||||||
|
secp256k1_frost_session session[5];
|
||||||
|
secp256k1_frost_session invalid_session;
|
||||||
|
secp256k1_xonly_pubkey invalid_pk;
|
||||||
|
unsigned char tweak[32];
|
||||||
|
int nonce_parity;
|
||||||
|
unsigned char sec_adaptor[32];
|
||||||
|
unsigned char sec_adaptor1[32];
|
||||||
|
secp256k1_pubkey adaptor;
|
||||||
|
secp256k1_pubkey invalid_vss_pk;
|
||||||
|
secp256k1_frost_share invalid_share;
|
||||||
|
secp256k1_frost_share shares[5];
|
||||||
|
secp256k1_pubkey pubshares[5];
|
||||||
|
int i;
|
||||||
|
size_t ids[5];
|
||||||
|
size_t invalid_ids[5];
|
||||||
|
|
||||||
|
/** setup **/
|
||||||
|
memset(max64, 0xff, sizeof(max64));
|
||||||
|
memset(&invalid_share, 0xff, sizeof(invalid_share));
|
||||||
|
/* Simulate structs being uninitialized by setting it to 0s. We don't want
|
||||||
|
* to produce undefined behavior by actually providing uninitialized
|
||||||
|
* structs. */
|
||||||
|
memset(&invalid_pk, 0, sizeof(invalid_pk));
|
||||||
|
memset(&invalid_secnonce, 0, sizeof(invalid_secnonce));
|
||||||
|
memset(&invalid_partial_sig, 0, sizeof(invalid_partial_sig));
|
||||||
|
memset(&invalid_pubnonce, 0, sizeof(invalid_pubnonce));
|
||||||
|
memset(&invalid_vss_pk, 0, sizeof(invalid_vss_pk));
|
||||||
|
memset(&invalid_tweak_cache, 0, sizeof(invalid_tweak_cache));
|
||||||
|
memset(&invalid_session, 0, sizeof(invalid_session));
|
||||||
|
frost_pubnonce_summing_to_inf(inf_pubnonce);
|
||||||
|
|
||||||
|
secp256k1_testrand256(sec_adaptor);
|
||||||
|
secp256k1_testrand256(msg);
|
||||||
|
secp256k1_testrand256(tweak);
|
||||||
|
secp256k1_testrand256(seed);
|
||||||
|
CHECK(secp256k1_ec_pubkey_create(CTX, &adaptor, sec_adaptor) == 1);
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
pubnonce_ptr[i] = &pubnonce[i];
|
||||||
|
partial_sig_ptr[i] = &partial_sig[i];
|
||||||
|
invalid_partial_sig_ptr[i] = &partial_sig[i];
|
||||||
|
ids[i] = i + 1;
|
||||||
|
invalid_ids[i] = i + 1;
|
||||||
|
secp256k1_testrand256(session_id[i]);
|
||||||
|
}
|
||||||
|
invalid_pubnonce_ptr[0] = &invalid_pubnonce;
|
||||||
|
invalid_partial_sig_ptr[0] = &invalid_partial_sig;
|
||||||
|
invalid_ids[2] = 0;
|
||||||
|
|
||||||
|
/** main test body **/
|
||||||
|
|
||||||
|
/** Key generation **/
|
||||||
|
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 5) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, NULL, pubshares, &pk, seed, 3, 5));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, NULL, &pk, seed, 3, 5));
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
|
||||||
|
}
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, NULL, seed, 3, 5));
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
|
||||||
|
}
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, NULL, 3, 5));
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
|
||||||
|
}
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 0, 5));
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
CHECK(frost_memcmp_and_randomize(shares[i].data, zeros68, sizeof(shares[i].data)) == 0);
|
||||||
|
}
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 0));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 2));
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &pk, seed, 3, 5) == 1);
|
||||||
|
|
||||||
|
/* pubkey_get */
|
||||||
|
CHECK(secp256k1_frost_pubkey_get(CTX, &full_pk, &pk) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, NULL, &pk));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_get(CTX, &full_pk, NULL));
|
||||||
|
CHECK(secp256k1_memcmp_var(&full_pk, zeros68, sizeof(full_pk)) == 0);
|
||||||
|
|
||||||
|
/** Tweaking **/
|
||||||
|
|
||||||
|
/* pubkey_tweak */
|
||||||
|
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &pk) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, NULL, &pk));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, NULL));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &invalid_pk));
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &pk) == 1);
|
||||||
|
|
||||||
|
/* tweak_add */
|
||||||
|
{
|
||||||
|
int (*tweak_func[2]) (const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_frost_tweak_cache *tweak_cache, const unsigned char *tweak32);
|
||||||
|
tweak_func[0] = secp256k1_frost_pubkey_ec_tweak_add;
|
||||||
|
tweak_func[1] = secp256k1_frost_pubkey_xonly_tweak_add;
|
||||||
|
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &pk) == 1);
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
secp256k1_pubkey tmp_output_pk;
|
||||||
|
secp256k1_frost_tweak_cache tmp_tweak_cache = tweak_cache;
|
||||||
|
CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1);
|
||||||
|
/* Reset tweak_cache */
|
||||||
|
tmp_tweak_cache = tweak_cache;
|
||||||
|
CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, tweak) == 1);
|
||||||
|
tmp_tweak_cache = tweak_cache;
|
||||||
|
CHECK((*tweak_func[i])(CTX, NULL, &tmp_tweak_cache, tweak) == 1);
|
||||||
|
tmp_tweak_cache = tweak_cache;
|
||||||
|
CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, NULL, tweak));
|
||||||
|
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||||
|
tmp_tweak_cache = tweak_cache;
|
||||||
|
CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, NULL));
|
||||||
|
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||||
|
tmp_tweak_cache = tweak_cache;
|
||||||
|
CHECK((*tweak_func[i])(CTX, &tmp_output_pk, &tmp_tweak_cache, max64) == 0);
|
||||||
|
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||||
|
tmp_tweak_cache = tweak_cache;
|
||||||
|
/* Uninitialized tweak_cache */
|
||||||
|
CHECK_ILLEGAL(CTX, (*tweak_func[i])(CTX, &tmp_output_pk, &invalid_tweak_cache, tweak));
|
||||||
|
CHECK(frost_memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Session creation **/
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &pk, max64) == 1);
|
||||||
|
CHECK_ILLEGAL(STATIC_CTX, secp256k1_frost_nonce_gen(STATIC_CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &pk, max64));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, NULL, &pubnonce[0], session_id[0], &shares[0], msg, &pk, max64));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], NULL, session_id[0], &shares[0], msg, &pk, max64));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], NULL, &shares[0], msg, &pk, max64));
|
||||||
|
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
|
||||||
|
/* no seckey and session_id is 0 */
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], zeros68, NULL, msg, &pk, max64) == 0);
|
||||||
|
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
|
||||||
|
/* session_id 0 is fine when a seckey is provided */
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], zeros68, &shares[0], msg, &pk, max64) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, msg, &pk, max64) == 1);
|
||||||
|
/* invalid share */
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &invalid_share, msg, &pk, max64));
|
||||||
|
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], NULL, &pk, max64) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, NULL, max64) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &invalid_pk, max64));
|
||||||
|
CHECK(frost_memcmp_and_randomize(secnonce[0].data, zeros68, sizeof(secnonce[0].data)) == 0);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], &shares[0], msg, &pk, NULL) == 1);
|
||||||
|
|
||||||
|
/* Every in-argument except session_id can be NULL */
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], NULL, NULL, NULL, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[1], &pubnonce[1], session_id[1], &shares[1], NULL, NULL, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[2], &pubnonce[2], session_id[2], &shares[2], NULL, NULL, NULL) == 1);
|
||||||
|
|
||||||
|
/** Serialize and parse public nonces **/
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, NULL, &pubnonce[0]));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, NULL));
|
||||||
|
CHECK(frost_memcmp_and_randomize(pubnonce_ser, zeros68, sizeof(pubnonce_ser)) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &invalid_pubnonce));
|
||||||
|
CHECK(frost_memcmp_and_randomize(pubnonce_ser, zeros68, sizeof(pubnonce_ser)) == 0);
|
||||||
|
CHECK(secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce[0]) == 1);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], pubnonce_ser) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_parse(CTX, NULL, pubnonce_ser));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], NULL));
|
||||||
|
CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], zeros68) == 0);
|
||||||
|
CHECK(secp256k1_frost_pubnonce_parse(CTX, &pubnonce[0], pubnonce_ser) == 1);
|
||||||
|
|
||||||
|
{
|
||||||
|
/* Check that serialize and parse results in the same value */
|
||||||
|
secp256k1_frost_pubnonce tmp;
|
||||||
|
CHECK(secp256k1_frost_pubnonce_serialize(CTX, pubnonce_ser, &pubnonce[0]) == 1);
|
||||||
|
CHECK(secp256k1_frost_pubnonce_parse(CTX, &tmp, pubnonce_ser) == 1);
|
||||||
|
CHECK(secp256k1_memcmp_var(&tmp, &pubnonce[0], sizeof(tmp)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Process nonces **/
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, NULL, pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], NULL, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 0, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], invalid_pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, NULL, &pk, ids[0], ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, NULL, ids[0], ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, 0, ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], invalid_ids, &tweak_cache, &adaptor));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], NULL, &tweak_cache, &adaptor));
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, NULL, &adaptor) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &invalid_tweak_cache, &adaptor));
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, NULL) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, (secp256k1_pubkey *)&invalid_pk));
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, &pk, ids[0], ids, &tweak_cache, &adaptor) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, &pk, ids[1], ids, &tweak_cache, &adaptor) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, &pk, ids[2], ids, &tweak_cache, &adaptor) == 1);
|
||||||
|
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &tweak_cache) == 1);
|
||||||
|
/* The secnonce is set to 0 and subsequent signing attempts fail */
|
||||||
|
CHECK(secp256k1_memcmp_var(&secnonce_tmp, zeros68, sizeof(secnonce_tmp)) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &tweak_cache));
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, NULL, &secnonce_tmp, &shares[0], &session[0], &tweak_cache));
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], NULL, &shares[0], &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &invalid_secnonce, &shares[0], &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, NULL, &session[0], &tweak_cache));
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &invalid_share, &session[0], &tweak_cache));
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], NULL, &tweak_cache));
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &invalid_session, &tweak_cache));
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], NULL) == 1);
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &shares[0], &session[0], &invalid_tweak_cache));
|
||||||
|
memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp));
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], &shares[0], &session[0], &tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], &shares[1], &session[1], &tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], &shares[2], &session[2], &tweak_cache) == 1);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_partial_sig_serialize(CTX, buf, &partial_sig[0]) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_serialize(CTX, NULL, &partial_sig[0]));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_serialize(CTX, buf, NULL));
|
||||||
|
CHECK(secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], buf) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_parse(CTX, NULL, buf));
|
||||||
|
CHECK(secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], max64) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_parse(CTX, &partial_sig[0], NULL));
|
||||||
|
|
||||||
|
{
|
||||||
|
/* Check that serialize and parse results in the same value */
|
||||||
|
secp256k1_frost_partial_sig tmp;
|
||||||
|
CHECK(secp256k1_frost_partial_sig_serialize(CTX, buf, &partial_sig[0]) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_parse(CTX, &tmp, buf) == 1);
|
||||||
|
CHECK(secp256k1_memcmp_var(&tmp, &partial_sig[0], sizeof(tmp)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Partial signature verification */
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], &tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[0], &pubshares[0], &session[0], &tweak_cache) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, NULL, &pubnonce[0], &pubshares[0], &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &invalid_partial_sig, &pubnonce[0], &pubshares[0], &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], NULL, &pubshares[0], &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &invalid_pubnonce, &pubshares[0], &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], NULL, &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &invalid_vss_pk, &session[0], &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], NULL, &tweak_cache));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &invalid_session, &tweak_cache));
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], NULL) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], &invalid_tweak_cache));
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], &pubshares[0], &session[0], &tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], &pubshares[1], &session[1], &tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], &pubshares[2], &session[2], &tweak_cache) == 1);
|
||||||
|
|
||||||
|
/** Signature aggregation and verification */
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 3) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, NULL, &session[0], partial_sig_ptr, 3));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, NULL, partial_sig_ptr, 3));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &invalid_session, partial_sig_ptr, 3));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], NULL, 3));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], invalid_partial_sig_ptr, 3));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 0));
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[0], partial_sig_ptr, 1) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[1], partial_sig_ptr, 2) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(CTX, pre_sig, &session[2], partial_sig_ptr, 3) == 1);
|
||||||
|
|
||||||
|
/** Adaptor signature verification */
|
||||||
|
CHECK(secp256k1_frost_nonce_parity(CTX, &nonce_parity, &session[0]) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, NULL, &session[0]));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, &nonce_parity, NULL));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_nonce_parity(CTX, &nonce_parity, &invalid_session));
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, NULL, pre_sig, sec_adaptor, 0));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, NULL, sec_adaptor, 0));
|
||||||
|
CHECK(secp256k1_frost_adapt(CTX, final_sig, max64, sec_adaptor, 0) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, pre_sig, NULL, 0));
|
||||||
|
CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, max64, 0) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, 2));
|
||||||
|
/* sig and pre_sig argument point to the same location */
|
||||||
|
memcpy(final_sig, pre_sig, sizeof(final_sig));
|
||||||
|
CHECK(secp256k1_frost_adapt(CTX, final_sig, final_sig, sec_adaptor, nonce_parity) == 1);
|
||||||
|
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk) == 1);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1);
|
||||||
|
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &pk) == 1);
|
||||||
|
|
||||||
|
/** Secret adaptor can be extracted from signature */
|
||||||
|
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, nonce_parity) == 1);
|
||||||
|
CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) == 0);
|
||||||
|
/* wrong nonce parity */
|
||||||
|
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, !nonce_parity) == 1);
|
||||||
|
CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) != 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, NULL, final_sig, pre_sig, 0));
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, NULL, pre_sig, 0));
|
||||||
|
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, max64, pre_sig, 0) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, NULL, 0));
|
||||||
|
CHECK(secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, max64, 0) == 0);
|
||||||
|
CHECK_ILLEGAL(CTX, secp256k1_frost_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void frost_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes) {
|
||||||
|
secp256k1_scalar k1[2], k2[2];
|
||||||
|
|
||||||
|
secp256k1_nonce_function_frost(k1, args[0], args[1], args[2], args[3], args[4]);
|
||||||
|
secp256k1_testrand_flip(args[n_flip], n_bytes);
|
||||||
|
secp256k1_nonce_function_frost(k2, args[0], args[1], args[2], args[3], args[4]);
|
||||||
|
CHECK(secp256k1_scalar_eq(&k1[0], &k2[0]) == 0);
|
||||||
|
CHECK(secp256k1_scalar_eq(&k1[1], &k2[1]) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void frost_nonce_test(void) {
|
||||||
|
unsigned char *args[5];
|
||||||
|
unsigned char session_id[32];
|
||||||
|
unsigned char sk[32];
|
||||||
|
unsigned char msg[32];
|
||||||
|
unsigned char agg_pk[32];
|
||||||
|
unsigned char extra_input[32];
|
||||||
|
int i, j;
|
||||||
|
secp256k1_scalar k[5][2];
|
||||||
|
|
||||||
|
secp256k1_testrand_bytes_test(session_id, sizeof(session_id));
|
||||||
|
secp256k1_testrand_bytes_test(sk, sizeof(sk));
|
||||||
|
secp256k1_testrand_bytes_test(msg, sizeof(msg));
|
||||||
|
secp256k1_testrand_bytes_test(agg_pk, sizeof(agg_pk));
|
||||||
|
secp256k1_testrand_bytes_test(extra_input, sizeof(extra_input));
|
||||||
|
|
||||||
|
/* Check that a bitflip in an argument results in different nonces. */
|
||||||
|
args[0] = session_id;
|
||||||
|
args[1] = msg;
|
||||||
|
args[2] = sk;
|
||||||
|
args[3] = agg_pk;
|
||||||
|
args[4] = extra_input;
|
||||||
|
for (i = 0; i < COUNT; i++) {
|
||||||
|
frost_nonce_bitflip(args, 0, sizeof(session_id));
|
||||||
|
frost_nonce_bitflip(args, 1, sizeof(msg));
|
||||||
|
frost_nonce_bitflip(args, 2, sizeof(sk));
|
||||||
|
frost_nonce_bitflip(args, 3, sizeof(agg_pk));
|
||||||
|
frost_nonce_bitflip(args, 4, sizeof(extra_input));
|
||||||
|
}
|
||||||
|
/* Check that if any argument is NULL, a different nonce is produced than if
|
||||||
|
* any other argument is NULL. */
|
||||||
|
memcpy(msg, session_id, sizeof(msg));
|
||||||
|
memcpy(sk, session_id, sizeof(sk));
|
||||||
|
memcpy(agg_pk, session_id, sizeof(agg_pk));
|
||||||
|
memcpy(extra_input, session_id, sizeof(extra_input));
|
||||||
|
secp256k1_nonce_function_frost(k[0], args[0], args[1], args[2], args[3], args[4]);
|
||||||
|
secp256k1_nonce_function_frost(k[1], args[0], NULL, args[2], args[3], args[4]);
|
||||||
|
secp256k1_nonce_function_frost(k[2], args[0], args[1], NULL, args[3], args[4]);
|
||||||
|
secp256k1_nonce_function_frost(k[3], args[0], args[1], args[2], NULL, args[4]);
|
||||||
|
secp256k1_nonce_function_frost(k[4], args[0], args[1], args[2], args[3], NULL);
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
for (j = i+1; j < 5; j++) {
|
||||||
|
CHECK(secp256k1_scalar_eq(&k[i][0], &k[j][0]) == 0);
|
||||||
|
CHECK(secp256k1_scalar_eq(&k[i][1], &k[j][1]) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempts to create a signature for the aggregate public key using given secret
|
||||||
|
* keys and tweak_cache. */
|
||||||
|
void frost_tweak_test_helper(const secp256k1_xonly_pubkey* pk, const secp256k1_frost_share *sr0, const secp256k1_frost_share *sr1, const secp256k1_frost_share *sr2, secp256k1_frost_tweak_cache *tweak_cache, const size_t *ids, const secp256k1_pubkey *sr_pk0, const secp256k1_pubkey *sr_pk1, const secp256k1_pubkey *sr_pk2) {
|
||||||
|
unsigned char session_id[3][32];
|
||||||
|
unsigned char msg[32];
|
||||||
|
secp256k1_frost_secnonce secnonce[3];
|
||||||
|
secp256k1_frost_pubnonce pubnonce[3];
|
||||||
|
const secp256k1_frost_pubnonce *pubnonce_ptr[3];
|
||||||
|
secp256k1_frost_session session[5];
|
||||||
|
secp256k1_frost_partial_sig partial_sig[3];
|
||||||
|
const secp256k1_frost_partial_sig *partial_sig_ptr[3];
|
||||||
|
unsigned char final_sig[64];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
pubnonce_ptr[i] = &pubnonce[i];
|
||||||
|
partial_sig_ptr[i] = &partial_sig[i];
|
||||||
|
|
||||||
|
secp256k1_testrand256(session_id[i]);
|
||||||
|
}
|
||||||
|
secp256k1_testrand256(msg);
|
||||||
|
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[0], &pubnonce[0], session_id[0], sr0, NULL, NULL, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[1], &pubnonce[1], session_id[1], sr1, NULL, NULL, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[2], &pubnonce[2], session_id[2], sr2, NULL, NULL, NULL) == 1);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[0], pubnonce_ptr, 3, msg, pk, ids[0], ids, tweak_cache, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[1], pubnonce_ptr, 3, msg, pk, ids[1], ids, tweak_cache, NULL) == 1);
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session[2], pubnonce_ptr, 3, msg, pk, ids[2], ids, tweak_cache, NULL) == 1);
|
||||||
|
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[0], &secnonce[0], sr0, &session[0], tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[1], &secnonce[1], sr1, &session[1], tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[2], &secnonce[2], sr2, &session[2], tweak_cache) == 1);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[0], &pubnonce[0], sr_pk0, &session[0], tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[1], &pubnonce[1], sr_pk1, &session[1], tweak_cache) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sig_verify(CTX, &partial_sig[2], &pubnonce[2], sr_pk2, &session[2], tweak_cache) == 1);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session[0], partial_sig_ptr, 3) == 1);
|
||||||
|
CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), pk) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create aggregate public key P[0], tweak multiple times (using xonly and
|
||||||
|
* ordinary tweaking) and test signing. */
|
||||||
|
void frost_tweak_test(void) {
|
||||||
|
secp256k1_pubkey pubshares[5];
|
||||||
|
secp256k1_frost_tweak_cache tweak_cache;
|
||||||
|
enum { N_TWEAKS = 8 };
|
||||||
|
secp256k1_pubkey P[N_TWEAKS + 1];
|
||||||
|
secp256k1_xonly_pubkey P_xonly[N_TWEAKS + 1];
|
||||||
|
unsigned char seed[32];
|
||||||
|
secp256k1_frost_share shares[5];
|
||||||
|
int i;
|
||||||
|
size_t ids[5];
|
||||||
|
|
||||||
|
secp256k1_testrand256(seed);
|
||||||
|
|
||||||
|
/* Key Setup */
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
ids[i] = i + 1;
|
||||||
|
}
|
||||||
|
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, &P_xonly[0], seed, 3, 5) == 1);
|
||||||
|
|
||||||
|
frost_tweak_test_helper(&P_xonly[0], &shares[0], &shares[1], &shares[2], NULL, ids, &pubshares[0], &pubshares[1], &pubshares[2]);
|
||||||
|
CHECK(secp256k1_frost_pubkey_get(CTX, &P[0], &P_xonly[0]));
|
||||||
|
CHECK(secp256k1_frost_pubkey_tweak(CTX, &tweak_cache, &P_xonly[0]) == 1);
|
||||||
|
|
||||||
|
/* Compute Pi = f(Pj) + tweaki*G where where j = i-1 and try signing for */
|
||||||
|
/* that key. If xonly is set to true, the function f is normalizes the input */
|
||||||
|
/* point to have an even X-coordinate ("xonly-tweaking"). */
|
||||||
|
/* Otherwise, the function f is the identity function. */
|
||||||
|
for (i = 1; i <= N_TWEAKS; i++) {
|
||||||
|
unsigned char tweak[32];
|
||||||
|
int P_parity;
|
||||||
|
int xonly = secp256k1_testrand_bits(1);
|
||||||
|
|
||||||
|
secp256k1_testrand256(tweak);
|
||||||
|
if (xonly) {
|
||||||
|
CHECK(secp256k1_frost_pubkey_xonly_tweak_add(CTX, &P[i], &tweak_cache, tweak) == 1);
|
||||||
|
} else {
|
||||||
|
CHECK(secp256k1_frost_pubkey_ec_tweak_add(CTX, &P[i], &tweak_cache, tweak) == 1);
|
||||||
|
}
|
||||||
|
CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &P_xonly[i], &P_parity, &P[i]));
|
||||||
|
/* Check that frost_pubkey_tweak_add produces same result as */
|
||||||
|
/* xonly_pubkey_tweak_add or ec_pubkey_tweak_add. */
|
||||||
|
if (xonly) {
|
||||||
|
unsigned char P_serialized[32];
|
||||||
|
CHECK(secp256k1_xonly_pubkey_serialize(CTX, P_serialized, &P_xonly[i]));
|
||||||
|
CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, P_serialized, P_parity, &P_xonly[i-1], tweak) == 1);
|
||||||
|
} else {
|
||||||
|
secp256k1_pubkey tmp_key = P[i-1];
|
||||||
|
CHECK(secp256k1_ec_pubkey_tweak_add(CTX, &tmp_key, tweak));
|
||||||
|
CHECK(secp256k1_memcmp_var(&tmp_key, &P[i], sizeof(tmp_key)) == 0);
|
||||||
|
}
|
||||||
|
/* Test signing for P[i] */
|
||||||
|
frost_tweak_test_helper(&P_xonly[i], &shares[0], &shares[1], &shares[2], &tweak_cache, ids, &pubshares[0], &pubshares[1], &pubshares[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Performs a FROST DKG */
|
||||||
|
void frost_dkg_test_helper(secp256k1_frost_share *shares, secp256k1_xonly_pubkey *pk) {
|
||||||
|
unsigned char seed[32];
|
||||||
|
secp256k1_pubkey pubshares[5];
|
||||||
|
|
||||||
|
secp256k1_testrand256(seed);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_shares_trusted_gen(CTX, shares, pubshares, pk, seed, 3, 5) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signs a message with a FROST keypair */
|
||||||
|
int frost_sign_test_helper(unsigned char *final_sig, const secp256k1_frost_share *share, const secp256k1_xonly_pubkey *pk, const unsigned char *msg, const secp256k1_pubkey *adaptor) {
|
||||||
|
unsigned char session_id[3][32];
|
||||||
|
secp256k1_frost_secnonce secnonce[3];
|
||||||
|
secp256k1_frost_pubnonce pubnonce[3];
|
||||||
|
const secp256k1_frost_pubnonce *pubnonce_ptr[3];
|
||||||
|
secp256k1_frost_partial_sig partial_sig[5];
|
||||||
|
const secp256k1_frost_partial_sig *partial_sig_ptr[5];
|
||||||
|
secp256k1_frost_session session;
|
||||||
|
int i;
|
||||||
|
int nonce_parity;
|
||||||
|
secp256k1_frost_session_internal session_i;
|
||||||
|
size_t ids[5];
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
pubnonce_ptr[i] = &pubnonce[i];
|
||||||
|
partial_sig_ptr[i] = &partial_sig[i];
|
||||||
|
ids[i] = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
secp256k1_testrand256(session_id[i]);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_nonce_gen(CTX, &secnonce[i], &pubnonce[i], session_id[i], &share[i], NULL, NULL, NULL) == 1);
|
||||||
|
}
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
CHECK(secp256k1_frost_nonce_process(CTX, &session, pubnonce_ptr, 3, msg, pk, i + 1, ids, NULL, adaptor) == 1);
|
||||||
|
CHECK(secp256k1_frost_partial_sign(CTX, &partial_sig[i], &secnonce[i], &share[i], &session, NULL) == 1);
|
||||||
|
}
|
||||||
|
CHECK(secp256k1_frost_partial_sig_agg(CTX, final_sig, &session, partial_sig_ptr, 3) == 1);
|
||||||
|
|
||||||
|
CHECK(secp256k1_frost_nonce_parity(CTX, &nonce_parity, &session));
|
||||||
|
|
||||||
|
secp256k1_frost_session_load(CTX, &session_i, &session);
|
||||||
|
|
||||||
|
return nonce_parity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void frost_rand_scalar(secp256k1_scalar *scalar) {
|
||||||
|
unsigned char buf32[32];
|
||||||
|
secp256k1_testrand256(buf32);
|
||||||
|
secp256k1_scalar_set_b32(scalar, buf32, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void frost_multi_hop_lock_tests(void) {
|
||||||
|
secp256k1_frost_share share_a[5];
|
||||||
|
secp256k1_frost_share share_b[5];
|
||||||
|
secp256k1_xonly_pubkey agg_pk_a;
|
||||||
|
secp256k1_xonly_pubkey agg_pk_b;
|
||||||
|
unsigned char asig_ab[64];
|
||||||
|
unsigned char asig_bc[64];
|
||||||
|
unsigned char pop[32];
|
||||||
|
secp256k1_pubkey pubkey_pop;
|
||||||
|
unsigned char tx_ab[32];
|
||||||
|
unsigned char tx_bc[32];
|
||||||
|
unsigned char buf[32];
|
||||||
|
secp256k1_scalar t1, t2, tp;
|
||||||
|
secp256k1_pubkey l, r;
|
||||||
|
secp256k1_ge l_ge, r_ge;
|
||||||
|
secp256k1_scalar deckey;
|
||||||
|
unsigned char sig_ab[64];
|
||||||
|
unsigned char sig_bc[64];
|
||||||
|
int nonce_parity_ab;
|
||||||
|
int nonce_parity_bc;
|
||||||
|
|
||||||
|
/* Alice DKG */
|
||||||
|
frost_dkg_test_helper(share_a, &agg_pk_a);
|
||||||
|
|
||||||
|
/* Bob DKG */
|
||||||
|
frost_dkg_test_helper(share_b, &agg_pk_b);
|
||||||
|
|
||||||
|
/* Carol setup */
|
||||||
|
/* Proof of payment */
|
||||||
|
secp256k1_testrand256(pop);
|
||||||
|
CHECK(secp256k1_ec_pubkey_create(CTX, &pubkey_pop, pop));
|
||||||
|
|
||||||
|
/* Alice setup */
|
||||||
|
secp256k1_testrand256(tx_ab);
|
||||||
|
frost_rand_scalar(&t1);
|
||||||
|
frost_rand_scalar(&t2);
|
||||||
|
secp256k1_scalar_add(&tp, &t1, &t2);
|
||||||
|
/* Left lock */
|
||||||
|
secp256k1_pubkey_load(CTX, &l_ge, &pubkey_pop);
|
||||||
|
CHECK(secp256k1_eckey_pubkey_tweak_add(&l_ge, &t1));
|
||||||
|
secp256k1_pubkey_save(&l, &l_ge);
|
||||||
|
/* Right lock */
|
||||||
|
secp256k1_pubkey_load(CTX, &r_ge, &pubkey_pop);
|
||||||
|
CHECK(secp256k1_eckey_pubkey_tweak_add(&r_ge, &tp));
|
||||||
|
secp256k1_pubkey_save(&r, &r_ge);
|
||||||
|
/* Encrypt Alice's signature with the left lock as the encryption key */
|
||||||
|
nonce_parity_ab = frost_sign_test_helper(asig_ab, share_a, &agg_pk_a, tx_ab, &l);
|
||||||
|
|
||||||
|
/* Bob setup */
|
||||||
|
CHECK(secp256k1_frost_verify_adaptor(CTX, asig_ab, tx_ab, &agg_pk_a, &l, nonce_parity_ab) == 1);
|
||||||
|
secp256k1_testrand256(tx_bc);
|
||||||
|
/* Encrypt Bob's signature with the right lock as the encryption key */
|
||||||
|
nonce_parity_bc = frost_sign_test_helper(asig_bc, share_b, &agg_pk_b, tx_bc, &r);
|
||||||
|
|
||||||
|
/* Carol decrypt */
|
||||||
|
CHECK(secp256k1_frost_verify_adaptor(CTX, asig_bc, tx_bc, &agg_pk_b, &r, nonce_parity_bc) == 1);
|
||||||
|
secp256k1_scalar_set_b32(&deckey, pop, NULL);
|
||||||
|
secp256k1_scalar_add(&deckey, &deckey, &tp);
|
||||||
|
secp256k1_scalar_get_b32(buf, &deckey);
|
||||||
|
CHECK(secp256k1_frost_adapt(CTX, sig_bc, asig_bc, buf, nonce_parity_bc));
|
||||||
|
CHECK(secp256k1_schnorrsig_verify(CTX, sig_bc, tx_bc, sizeof(tx_bc), &agg_pk_b) == 1);
|
||||||
|
|
||||||
|
/* Bob recover and decrypt */
|
||||||
|
CHECK(secp256k1_frost_extract_adaptor(CTX, buf, sig_bc, asig_bc, nonce_parity_bc));
|
||||||
|
secp256k1_scalar_set_b32(&deckey, buf, NULL);
|
||||||
|
secp256k1_scalar_negate(&t2, &t2);
|
||||||
|
secp256k1_scalar_add(&deckey, &deckey, &t2);
|
||||||
|
secp256k1_scalar_get_b32(buf, &deckey);
|
||||||
|
CHECK(secp256k1_frost_adapt(CTX, sig_ab, asig_ab, buf, nonce_parity_ab));
|
||||||
|
CHECK(secp256k1_schnorrsig_verify(CTX, sig_ab, tx_ab, sizeof(tx_ab), &agg_pk_a) == 1);
|
||||||
|
|
||||||
|
/* Alice recover and derive proof of payment */
|
||||||
|
CHECK(secp256k1_frost_extract_adaptor(CTX, buf, sig_ab, asig_ab, nonce_parity_ab));
|
||||||
|
secp256k1_scalar_set_b32(&deckey, buf, NULL);
|
||||||
|
secp256k1_scalar_negate(&t1, &t1);
|
||||||
|
secp256k1_scalar_add(&deckey, &deckey, &t1);
|
||||||
|
secp256k1_scalar_get_b32(buf, &deckey);
|
||||||
|
CHECK(secp256k1_memcmp_var(buf, pop, 32) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_frost_tests(void) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < COUNT; i++) {
|
||||||
|
frost_simple_test();
|
||||||
|
}
|
||||||
|
frost_api_tests();
|
||||||
|
frost_nonce_test();
|
||||||
|
for (i = 0; i < COUNT; i++) {
|
||||||
|
/* Run multiple times to ensure that pk and nonce have different y
|
||||||
|
* parities */
|
||||||
|
frost_tweak_test();
|
||||||
|
}
|
||||||
|
for (i = 0; i < COUNT; i++) {
|
||||||
|
frost_multi_hop_lock_tests();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -27,12 +27,6 @@ typedef struct {
|
|||||||
int parity_acc;
|
int parity_acc;
|
||||||
} secp256k1_keyagg_cache_internal;
|
} secp256k1_keyagg_cache_internal;
|
||||||
|
|
||||||
/* point_save_ext and point_load_ext are identical to point_save and point_load
|
|
||||||
* except that they allow saving and loading the point at infinity */
|
|
||||||
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge);
|
|
||||||
|
|
||||||
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data);
|
|
||||||
|
|
||||||
static int secp256k1_keyagg_cache_load(const secp256k1_context* ctx, secp256k1_keyagg_cache_internal *cache_i, const secp256k1_musig_keyagg_cache *cache);
|
static int secp256k1_keyagg_cache_load(const secp256k1_context* ctx, secp256k1_keyagg_cache_internal *cache_i, const secp256k1_musig_keyagg_cache *cache);
|
||||||
|
|
||||||
static void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_keyagg_cache_internal *cache_i, secp256k1_ge *pk);
|
static void secp256k1_musig_keyaggcoef(secp256k1_scalar *r, const secp256k1_keyagg_cache_internal *cache_i, secp256k1_ge *pk);
|
||||||
|
@ -17,23 +17,6 @@
|
|||||||
#include "../../hash.h"
|
#include "../../hash.h"
|
||||||
#include "../../util.h"
|
#include "../../util.h"
|
||||||
|
|
||||||
static void secp256k1_point_save_ext(unsigned char *data, secp256k1_ge *ge) {
|
|
||||||
if (secp256k1_ge_is_infinity(ge)) {
|
|
||||||
memset(data, 0, 64);
|
|
||||||
} else {
|
|
||||||
secp256k1_ge_to_bytes(data, ge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void secp256k1_point_load_ext(secp256k1_ge *ge, const unsigned char *data) {
|
|
||||||
unsigned char zeros[64] = { 0 };
|
|
||||||
if (secp256k1_memcmp_var(data, zeros, sizeof(zeros)) == 0) {
|
|
||||||
secp256k1_ge_set_infinity(ge);
|
|
||||||
} else {
|
|
||||||
secp256k1_ge_from_bytes(ge, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const unsigned char secp256k1_musig_keyagg_cache_magic[4] = { 0xf4, 0xad, 0xbb, 0xdf };
|
static const unsigned char secp256k1_musig_keyagg_cache_magic[4] = { 0xf4, 0xad, 0xbb, 0xdf };
|
||||||
|
|
||||||
/* A keyagg cache consists of
|
/* A keyagg cache consists of
|
||||||
|
@ -889,6 +889,10 @@ static int secp256k1_ge_parse_ext(secp256k1_ge* ge, const unsigned char *in33) {
|
|||||||
# include "modules/ecdsa_adaptor/main_impl.h"
|
# include "modules/ecdsa_adaptor/main_impl.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_MODULE_FROST
|
||||||
|
# include "modules/frost/main_impl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_MODULE_MUSIG
|
#ifdef ENABLE_MODULE_MUSIG
|
||||||
# include "modules/musig/main_impl.h"
|
# include "modules/musig/main_impl.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -7502,6 +7502,10 @@ static void run_ecdsa_wycheproof(void) {
|
|||||||
# include "modules/ecdsa_adaptor/tests_impl.h"
|
# include "modules/ecdsa_adaptor/tests_impl.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_MODULE_FROST
|
||||||
|
# include "modules/frost/tests_impl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static void run_secp256k1_memczero_test(void) {
|
static void run_secp256k1_memczero_test(void) {
|
||||||
unsigned char buf1[6] = {1, 2, 3, 4, 5, 6};
|
unsigned char buf1[6] = {1, 2, 3, 4, 5, 6};
|
||||||
unsigned char buf2[sizeof(buf1)];
|
unsigned char buf2[sizeof(buf1)];
|
||||||
@ -7892,6 +7896,10 @@ int main(int argc, char **argv) {
|
|||||||
run_ecdsa_adaptor_tests();
|
run_ecdsa_adaptor_tests();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_MODULE_FROST
|
||||||
|
run_frost_tests();
|
||||||
|
#endif
|
||||||
|
|
||||||
/* util tests */
|
/* util tests */
|
||||||
run_secp256k1_memczero_test();
|
run_secp256k1_memczero_test();
|
||||||
run_secp256k1_byteorder_tests();
|
run_secp256k1_byteorder_tests();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user