Compare commits
54 Commits
main
...
2019-05-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f3b0c05c2 | ||
|
|
250ebb364e | ||
|
|
4a7763361d | ||
|
|
898c9f05bb | ||
|
|
15d92782d3 | ||
|
|
86240b207d | ||
|
|
865b76186c | ||
|
|
cd5ba5c3b9 | ||
|
|
ff16651273 | ||
|
|
0ad6b6036f | ||
|
|
b61a1a9d98 | ||
|
|
5d5374f92c | ||
|
|
a8ae6baff3 | ||
|
|
9a8a71e8bb | ||
|
|
14769b9648 | ||
|
|
0593861cc5 | ||
|
|
e9fea74278 | ||
|
|
dec1b9ce27 | ||
|
|
ea62bfe221 | ||
|
|
e32924f0ee | ||
|
|
972d056fac | ||
|
|
2cc4c6fef1 | ||
|
|
65ffea43d5 | ||
|
|
cb786d6d1a | ||
|
|
b387ba0389 | ||
|
|
8da432855c | ||
|
|
6f14fe40d9 | ||
|
|
ab4fbc1be8 | ||
|
|
c908c97d67 | ||
|
|
f723bf5b37 | ||
|
|
6872069de9 | ||
|
|
6ceccb75be | ||
|
|
a3ad4a8668 | ||
|
|
e93e886cb4 | ||
|
|
f1d6e4b831 | ||
|
|
68be611317 | ||
|
|
51fc58ae6b | ||
|
|
36b100c779 | ||
|
|
c8f54e12ec | ||
|
|
56fca50778 | ||
|
|
4617f04784 | ||
|
|
cd4e438a3a | ||
|
|
2cc7f1e045 | ||
|
|
c4097f758f | ||
|
|
5ee6bf3418 | ||
|
|
94e81a250e | ||
|
|
a66ea35227 | ||
|
|
2bb5133615 | ||
|
|
9b00b61d9d | ||
|
|
54fa2639e1 | ||
|
|
023aa86ac0 | ||
|
|
89e7451d42 | ||
|
|
f126331bc9 | ||
|
|
e1fb4af90b |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
src/precomputed_ecmult.c linguist-generated
|
||||
src/precomputed_ecmult_gen.c linguist-generated
|
||||
@@ -1,34 +0,0 @@
|
||||
name: "Install Valgrind"
|
||||
description: "Install Homebrew's Valgrind package and cache it."
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: |
|
||||
brew tap LouisBrunner/valgrind
|
||||
brew trust --formula LouisBrunner/valgrind/valgrind
|
||||
brew fetch --HEAD LouisBrunner/valgrind/valgrind
|
||||
echo "CI_HOMEBREW_CELLAR_VALGRIND=$(brew --cellar valgrind)" >> "$GITHUB_ENV"
|
||||
shell: bash
|
||||
|
||||
- run: |
|
||||
sw_vers > valgrind_fingerprint
|
||||
brew --version >> valgrind_fingerprint
|
||||
git -C "$(brew --cache)/valgrind--git" rev-parse HEAD >> valgrind_fingerprint
|
||||
cat valgrind_fingerprint
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v5
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ env.CI_HOMEBREW_CELLAR_VALGRIND }}
|
||||
key: ${{ github.job }}-valgrind-${{ hashFiles('valgrind_fingerprint') }}
|
||||
|
||||
- if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
brew install --HEAD LouisBrunner/valgrind/valgrind
|
||||
shell: bash
|
||||
|
||||
- if: steps.cache.outputs.cache-hit == 'true'
|
||||
run: |
|
||||
brew link valgrind
|
||||
shell: bash
|
||||
34
.github/actions/print-logs/action.yml
vendored
34
.github/actions/print-logs/action.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: "Print logs"
|
||||
description: "Print the log files produced by ci/ci.sh"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- shell: bash
|
||||
run: |
|
||||
# Print the log files produced by ci/ci.sh
|
||||
|
||||
# Helper functions
|
||||
group() {
|
||||
title=$1
|
||||
echo "::group::$title"
|
||||
}
|
||||
endgroup() {
|
||||
echo "::endgroup::"
|
||||
}
|
||||
cat_file() {
|
||||
file=$1
|
||||
group "$file"
|
||||
cat "$file"
|
||||
endgroup
|
||||
}
|
||||
|
||||
# Print all *.log files
|
||||
shopt -s nullglob
|
||||
for file in *.log; do
|
||||
cat_file "$file"
|
||||
done
|
||||
|
||||
# Print environment
|
||||
group "CI env"
|
||||
env
|
||||
endgroup
|
||||
52
.github/actions/run-in-docker-action/action.yml
vendored
52
.github/actions/run-in-docker-action/action.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: 'Run in Docker with environment'
|
||||
description: 'Run a command in a Docker container, while passing explicitly set environment variables into the container.'
|
||||
inputs:
|
||||
dockerfile:
|
||||
description: 'A Dockerfile that defines an image'
|
||||
required: true
|
||||
scope:
|
||||
description: 'A cached image scope'
|
||||
required: true
|
||||
command:
|
||||
description: 'A command to run in a container'
|
||||
required: true
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
|
||||
- uses: docker/build-push-action@v7
|
||||
id: main_builder
|
||||
continue-on-error: true
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
load: true
|
||||
cache-from: type=gha,scope=${{ inputs.scope }}
|
||||
|
||||
- uses: docker/build-push-action@v7
|
||||
id: retry_builder
|
||||
if: steps.main_builder.outcome == 'failure'
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
load: true
|
||||
cache-from: type=gha,scope=${{ inputs.scope }}
|
||||
|
||||
- # Workaround for https://github.com/google/sanitizers/issues/1614 .
|
||||
# The underlying issue has been fixed in clang 18.1.3.
|
||||
run: sudo sysctl -w vm.mmap_rnd_bits=28
|
||||
shell: bash
|
||||
|
||||
- # Tell Docker to pass environment variables in `env` into the container.
|
||||
run: >
|
||||
docker run \
|
||||
$(echo '${{ toJSON(env) }}' | jq -r 'keys[] | "--env \(.) "') \
|
||||
--volume ${{ github.workspace }}:${{ github.workspace }} \
|
||||
--workdir ${{ github.workspace }} \
|
||||
${{ case(steps.main_builder.outcome == 'success', steps.main_builder.outputs.imageid, steps.retry_builder.outputs.imageid) }} \
|
||||
bash -c "
|
||||
git config --global --add safe.directory ${{ github.workspace }}
|
||||
${{ inputs.command }}
|
||||
"
|
||||
shell: bash
|
||||
866
.github/workflows/ci.yml
vendored
866
.github/workflows/ci.yml
vendored
@@ -1,866 +0,0 @@
|
||||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
tags-ignore:
|
||||
- '**'
|
||||
schedule:
|
||||
# Run on the default branch every Monday morning.
|
||||
# This also warms the Docker caches after key rotation.
|
||||
- cron: '22 2 * * 1'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.event_name != 'pull_request' && github.run_id || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
### compiler options
|
||||
HOST:
|
||||
WRAPPER_CMD:
|
||||
# Specific warnings can be disabled with -Wno-error=foo.
|
||||
# -pedantic-errors is not equivalent to -Werror=pedantic and thus not implied by -Werror according to the GCC manual.
|
||||
WERROR_CFLAGS: '-Werror -pedantic-errors'
|
||||
MAKEFLAGS: '-j4'
|
||||
BUILD: 'check'
|
||||
### secp256k1 config
|
||||
ECMULTWINDOW: 15
|
||||
ECMULTGENKB: 86
|
||||
ASM: 'no'
|
||||
WIDEMUL: 'auto'
|
||||
WITH_VALGRIND: 'yes'
|
||||
EXTRAFLAGS:
|
||||
### secp256k1 modules
|
||||
EXPERIMENTAL: 'no'
|
||||
ECDH: 'no'
|
||||
RECOVERY: 'no'
|
||||
EXTRAKEYS: 'no'
|
||||
SCHNORRSIG: 'no'
|
||||
MUSIG: 'no'
|
||||
ELLSWIFT: 'no'
|
||||
ECDSA_S2C: 'no'
|
||||
GENERATOR: 'no'
|
||||
RANGEPROOF: 'no'
|
||||
SURJECTIONPROOF: 'no'
|
||||
WHITELIST: 'no'
|
||||
ECDSAADAPTOR: 'no'
|
||||
BPPP: 'no'
|
||||
SCHNORRSIG_HALFAGG: 'no'
|
||||
FROST: 'no'
|
||||
CHILLDKG: 'no'
|
||||
ICEBERG: 'no'
|
||||
PREFRACTAL: 'no'
|
||||
FROST_ENROLLMENT: 'no'
|
||||
### test options
|
||||
SECP256K1_TEST_ITERS: 64
|
||||
BENCH: 'yes'
|
||||
SECP256K1_BENCH_ITERS: 2
|
||||
CTIMETESTS: 'yes'
|
||||
SYMBOL_CHECK: 'yes'
|
||||
# Compile and run the examples.
|
||||
EXAMPLES: 'yes'
|
||||
# Disable Docker build summary generation.
|
||||
# See https://github.com/docker/build-push-action/blob/master/README.md#environment-variables.
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
|
||||
jobs:
|
||||
docker_cache:
|
||||
name: "Build ${{ matrix.arch }} Docker image"
|
||||
runs-on: ${{ matrix.runner }}
|
||||
outputs:
|
||||
cache_scope: ${{ steps.cache_timestamp.outputs.period }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
runner: ubuntu-latest
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- name: Get cache validity period
|
||||
id: cache_timestamp
|
||||
run: echo "period=$((10#$(date +%V) / 4))" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
# See: https://github.com/moby/buildkit/issues/3969.
|
||||
driver-opts: |
|
||||
network=host
|
||||
|
||||
- name: Build container
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
file: ./ci/linux-debian.Dockerfile
|
||||
cache-from: type=gha,scope=${{ runner.arch }}-${{ steps.cache_timestamp.outputs.period }}
|
||||
cache-to: type=gha,scope=${{ runner.arch }}-${{ steps.cache_timestamp.outputs.period }},mode=min
|
||||
|
||||
x86_64-debian:
|
||||
name: "x86_64: Linux (Debian stable)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: { WIDEMUL: 'int64', RECOVERY: 'yes' }
|
||||
- env_vars: { WIDEMUL: 'int64', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes'}
|
||||
- env_vars: { WIDEMUL: 'int128' }
|
||||
- env_vars: { WIDEMUL: 'int128_struct', ELLSWIFT: 'yes' }
|
||||
- env_vars: { WIDEMUL: 'int128', RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
|
||||
- env_vars: { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes'}
|
||||
- env_vars: { WIDEMUL: 'int128', ASM: 'x86_64', ELLSWIFT: 'yes' }
|
||||
- env_vars: { RECOVERY: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes'}
|
||||
- env_vars: { CTIMETESTS: 'no', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CPPFLAGS: '-DVERIFY' }
|
||||
- env_vars: { BUILD: 'distcheck', WITH_VALGRIND: 'no', CTIMETESTS: 'no', BENCH: 'no' }
|
||||
- env_vars: { CPPFLAGS: '-DDETERMINISTIC' }
|
||||
- env_vars: { CFLAGS: '-O0', CTIMETESTS: 'no' }
|
||||
- env_vars: { CFLAGS: '-O1', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes' }
|
||||
- env_vars: { ECMULTGENKB: 2, ECMULTWINDOW: 2 }
|
||||
- env_vars: { ECMULTGENKB: 86, ECMULTWINDOW: 4 }
|
||||
cc:
|
||||
- 'gcc'
|
||||
- 'clang'
|
||||
- 'gcc-snapshot'
|
||||
- 'clang-snapshot'
|
||||
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
|
||||
steps:
|
||||
- &CHECKOUT
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- &CI_SCRIPT_IN_DOCKER
|
||||
name: CI script
|
||||
env: ${{ matrix.configuration.env_vars }}
|
||||
uses: ./.github/actions/run-in-docker-action
|
||||
with:
|
||||
dockerfile: ./ci/linux-debian.Dockerfile
|
||||
scope: ${{ runner.arch }}-${{ needs.docker_cache.outputs.cache_scope }}
|
||||
command: ./ci/ci.sh
|
||||
|
||||
- &PRINT_LOGS
|
||||
name: Print logs
|
||||
uses: ./.github/actions/print-logs
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
i686_debian:
|
||||
name: "i686: Linux (Debian stable)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: {}
|
||||
cc:
|
||||
- 'i686-linux-gnu-gcc'
|
||||
- 'clang --target=i686-pc-linux-gnu -isystem /usr/i686-linux-gnu/include'
|
||||
|
||||
env:
|
||||
HOST: 'i686-linux-gnu'
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CC: ${{ matrix.cc }}
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
s390x_debian:
|
||||
name: "s390x (big-endian): Linux (Debian stable, QEMU)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: {}
|
||||
|
||||
env:
|
||||
WRAPPER_CMD: 'qemu-s390x'
|
||||
SECP256K1_TEST_ITERS: 16
|
||||
HOST: 's390x-linux-gnu'
|
||||
WITH_VALGRIND: 'no'
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CTIMETESTS: 'no'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
arm32_debian:
|
||||
name: "ARM32: Linux (Debian stable, QEMU)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: {}
|
||||
- env_vars: { EXPERIMENTAL: 'yes', ASM: 'arm32' }
|
||||
|
||||
env:
|
||||
WRAPPER_CMD: 'qemu-arm'
|
||||
SECP256K1_TEST_ITERS: 16
|
||||
HOST: 'arm-linux-gnueabihf'
|
||||
WITH_VALGRIND: 'no'
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CTIMETESTS: 'no'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
arm64-debian:
|
||||
name: "arm64: Linux (Debian stable)"
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs: docker_cache
|
||||
|
||||
env:
|
||||
SECP256K1_TEST_ITERS: 16
|
||||
WITH_VALGRIND: 'no'
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CTIMETESTS: 'no'
|
||||
CC: ${{ matrix.cc }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: {}
|
||||
cc:
|
||||
- 'gcc'
|
||||
- 'clang'
|
||||
- 'gcc-snapshot'
|
||||
- 'clang-snapshot'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
ppc64le_debian:
|
||||
name: "ppc64le: Linux (Debian stable, QEMU)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: {}
|
||||
|
||||
env:
|
||||
WRAPPER_CMD: 'qemu-ppc64le'
|
||||
SECP256K1_TEST_ITERS: 16
|
||||
HOST: 'powerpc64le-linux-gnu'
|
||||
WITH_VALGRIND: 'no'
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CTIMETESTS: 'no'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
valgrind_debian:
|
||||
name: "Valgrind ${{ matrix.configuration.binary_arch }} (memcheck)"
|
||||
runs-on: ${{ matrix.configuration.runner }}
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- runner: ubuntu-latest
|
||||
binary_arch: x64
|
||||
env_vars: { CC: 'clang', ASM: 'auto' }
|
||||
- runner: ubuntu-latest
|
||||
binary_arch: i686
|
||||
env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'auto' }
|
||||
- runner: ubuntu-24.04-arm
|
||||
binary_arch: arm64
|
||||
env_vars: { CC: 'clang', ASM: 'auto' }
|
||||
- runner: ubuntu-latest
|
||||
binary_arch: x64
|
||||
env_vars: { CC: 'clang', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
|
||||
- runner: ubuntu-latest
|
||||
binary_arch: i686
|
||||
env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
|
||||
- runner: ubuntu-24.04-arm
|
||||
binary_arch: arm64
|
||||
env_vars: { CC: 'clang', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
|
||||
|
||||
env:
|
||||
# The `--error-exitcode` is required to make the test fail if valgrind found errors,
|
||||
# otherwise it will return 0 (https://www.valgrind.org/docs/manual/manual-core.html).
|
||||
WRAPPER_CMD: 'valgrind --error-exitcode=42'
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CTIMETESTS: 'no'
|
||||
SECP256K1_TEST_ITERS: 2
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
sanitizers_debian:
|
||||
name: "UBSan, ASan, LSan"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: { CC: 'clang', ASM: 'auto' }
|
||||
- env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'auto' }
|
||||
- env_vars: { CC: 'clang', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
|
||||
- env_vars: { CC: 'i686-linux-gnu-gcc', HOST: 'i686-linux-gnu', ASM: 'no', ECMULTGENKB: 2, ECMULTWINDOW: 2 }
|
||||
|
||||
env:
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CTIMETESTS: 'no'
|
||||
CFLAGS: '-fsanitize=undefined,address -g'
|
||||
UBSAN_OPTIONS: 'print_stacktrace=1:halt_on_error=1'
|
||||
ASAN_OPTIONS: 'strict_string_checks=1:detect_stack_use_after_return=1:detect_leaks=1'
|
||||
LSAN_OPTIONS: 'use_unaligned=1'
|
||||
SECP256K1_TEST_ITERS: 32
|
||||
SYMBOL_CHECK: 'no'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
msan_debian:
|
||||
name: "MSan"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars:
|
||||
CTIMETESTS: 'yes'
|
||||
CFLAGS: '-fsanitize=memory -fsanitize-recover=memory -g'
|
||||
- env_vars:
|
||||
ECMULTGENKB: 2
|
||||
ECMULTWINDOW: 2
|
||||
CTIMETESTS: 'yes'
|
||||
CFLAGS: '-fsanitize=memory -fsanitize-recover=memory -g -O3'
|
||||
- env_vars:
|
||||
# -fsanitize-memory-param-retval is clang's default, but our build system disables it
|
||||
# when ctime_tests when enabled.
|
||||
CFLAGS: '-fsanitize=memory -fsanitize-recover=memory -fsanitize-memory-param-retval -g'
|
||||
CTIMETESTS: 'no'
|
||||
cc:
|
||||
- 'clang'
|
||||
- 'clang-snapshot'
|
||||
|
||||
env:
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CC: ${{ matrix.cc }}
|
||||
SECP256K1_TEST_ITERS: 32
|
||||
ASM: 'no'
|
||||
WITH_VALGRIND: 'no'
|
||||
SYMBOL_CHECK: 'no'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
mingw_debian:
|
||||
name: ${{ matrix.configuration.job_name }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
env:
|
||||
WRAPPER_CMD: 'wine'
|
||||
WITH_VALGRIND: 'no'
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
CTIMETESTS: 'no'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- job_name: 'x86_64 (mingw32-w64): Windows (Debian stable, Wine)'
|
||||
env_vars:
|
||||
HOST: 'x86_64-w64-mingw32'
|
||||
- job_name: 'i686 (mingw32-w64): Windows (Debian stable, Wine)'
|
||||
env_vars:
|
||||
HOST: 'i686-w64-mingw32'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
x86_64-macos-native:
|
||||
name: "x86_64: macOS Sequoia, Valgrind"
|
||||
runs-on: macos-15-intel
|
||||
|
||||
env:
|
||||
CC: 'clang'
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
SYMBOL_CHECK: 'no'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
env_vars:
|
||||
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes' }
|
||||
- { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 }
|
||||
- { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CC: 'gcc' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CC: 'gcc', WRAPPER_CMD: 'valgrind --error-exitcode=42', SECP256K1_TEST_ITERS: 2 }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', FROST: 'yes', CHILLDKG: 'yes', ICEBERG: 'yes', PREFRACTAL: 'yes', FROST_ENROLLMENT: 'yes', CPPFLAGS: '-DVERIFY', CTIMETESTS: 'no' }
|
||||
- BUILD: 'distcheck'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
|
||||
- name: Install Homebrew packages
|
||||
run: |
|
||||
brew install --quiet automake libtool gcc
|
||||
ln -s $(brew --prefix gcc)/bin/gcc-?? /usr/local/bin/gcc
|
||||
|
||||
- name: Install and cache Valgrind
|
||||
uses: ./.github/actions/install-homebrew-valgrind
|
||||
|
||||
- &CI_SCRIPT_ON_HOST
|
||||
name: CI script
|
||||
env: ${{ matrix.env_vars }}
|
||||
run: ./ci/ci.sh
|
||||
|
||||
- &SYMBOL_CHECK_MACOS
|
||||
name: Symbol check
|
||||
env:
|
||||
VIRTUAL_ENV: '${{ github.workspace }}/venv'
|
||||
run: |
|
||||
python3 --version
|
||||
python3 -m venv $VIRTUAL_ENV
|
||||
export PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
python3 -m pip install lief
|
||||
python3 ./tools/symbol-check.py .libs/libsecp256k1.dylib
|
||||
|
||||
- *PRINT_LOGS
|
||||
|
||||
arm64-macos-native:
|
||||
name: "ARM64: macOS Sonoma"
|
||||
# See: https://github.com/actions/runner-images#available-images.
|
||||
runs-on: macos-14
|
||||
|
||||
env:
|
||||
CC: 'clang'
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
WITH_VALGRIND: 'no'
|
||||
CTIMETESTS: 'no'
|
||||
SYMBOL_CHECK: 'no'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
env_vars:
|
||||
- { WIDEMUL: 'int64', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' }
|
||||
- { WIDEMUL: 'int128_struct', ECMULTGENKB: 2, ECMULTWINDOW: 4 }
|
||||
- { WIDEMUL: 'int128', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CC: 'gcc' }
|
||||
- { WIDEMUL: 'int128', RECOVERY: 'yes', ECDH: 'yes', EXTRAKEYS: 'yes', SCHNORRSIG: 'yes', MUSIG: 'yes', ELLSWIFT: 'yes', EXPERIMENTAL: 'yes', ECDSA_S2C: 'yes', RANGEPROOF: 'yes', SURJECTIONPROOF: 'yes', WHITELIST: 'yes', GENERATOR: 'yes', ECDSAADAPTOR: 'yes', BPPP: 'yes', SCHNORRSIG_HALFAGG: 'yes', CPPFLAGS: '-DVERIFY' }
|
||||
- BUILD: 'distcheck'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
|
||||
- name: Install Homebrew packages
|
||||
run: |
|
||||
brew install --quiet automake libtool gcc
|
||||
ln -s $(brew --prefix gcc)/bin/gcc-?? /usr/local/bin/gcc
|
||||
|
||||
- *CI_SCRIPT_ON_HOST
|
||||
- *SYMBOL_CHECK_MACOS
|
||||
- *PRINT_LOGS
|
||||
|
||||
win64-native:
|
||||
name: ${{ matrix.configuration.job_name }}
|
||||
# See: https://github.com/actions/runner-images#available-images.
|
||||
runs-on: windows-2022
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
configuration:
|
||||
- job_name: 'x64 (MSVC): Windows (VS 2022, shared)'
|
||||
cmake_options: '-A x64 -DBUILD_SHARED_LIBS=ON'
|
||||
symbol_check: 'true'
|
||||
- job_name: 'x64 (MSVC): Windows (VS 2022, static)'
|
||||
cmake_options: '-A x64 -DBUILD_SHARED_LIBS=OFF'
|
||||
- job_name: 'x64 (MSVC): Windows (VS 2022, int128_struct)'
|
||||
cmake_options: '-A x64 -DSECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int128_struct'
|
||||
- job_name: 'x64 (MSVC): Windows (VS 2022, int128_struct with __(u)mulh)'
|
||||
cmake_options: '-A x64 -DSECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int128_struct'
|
||||
cpp_flags: '/DSECP256K1_MSVC_MULH_TEST_OVERRIDE'
|
||||
- job_name: 'x86 (MSVC): Windows (VS 2022)'
|
||||
cmake_options: '-A Win32'
|
||||
- job_name: 'x64 (clang-cl): Windows (VS 2022, shared)'
|
||||
cmake_options: '-T ClangCL -DBUILD_SHARED_LIBS=ON'
|
||||
symbol_check: 'true'
|
||||
- job_name: 'x64 (clang-cl): Windows (VS 2022, static)'
|
||||
cmake_options: '-T ClangCL -DBUILD_SHARED_LIBS=OFF'
|
||||
- job_name: 'x64 (clang-cl): Windows (VS 2022, int128_struct)'
|
||||
cmake_options: '-T ClangCL -DSECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int128_struct'
|
||||
- job_name: 'x64 (clang-cl): Windows (VS 2022, int128_struct with __(u)mulh)'
|
||||
cmake_options: '-T ClangCL -DSECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int128_struct'
|
||||
cpp_flags: '/DSECP256K1_MSVC_MULH_TEST_OVERRIDE'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
|
||||
- name: Generate buildsystem
|
||||
run: cmake -E env CFLAGS="/WX ${{ matrix.configuration.cpp_flags }}" cmake -B build -DSECP256K1_ENABLE_MODULE_RECOVERY=ON -DSECP256K1_BUILD_EXAMPLES=ON ${{ matrix.configuration.cmake_options }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config RelWithDebInfo -- /p:UseMultiToolTask=true /maxCpuCount
|
||||
|
||||
- name: Binaries info
|
||||
# Use the bash shell included with Git for Windows.
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/bin/RelWithDebInfo && file *tests.exe bench*.exe libsecp256k1-*.dll || true
|
||||
|
||||
- name: Symbol check
|
||||
if: ${{ matrix.configuration.symbol_check }}
|
||||
shell: bash
|
||||
run: |
|
||||
py -3 --version
|
||||
py -3 -m pip install lief
|
||||
py -3 ./tools/symbol-check.py build/bin/RelWithDebInfo/libsecp256k1-*.dll
|
||||
|
||||
- name: Check
|
||||
run: |
|
||||
ctest -C RelWithDebInfo --test-dir build -j ([int]$env:NUMBER_OF_PROCESSORS + 1)
|
||||
build\bin\RelWithDebInfo\bench_ecmult.exe
|
||||
build\bin\RelWithDebInfo\bench_internal.exe
|
||||
build\bin\RelWithDebInfo\bench.exe
|
||||
|
||||
win64-native-headers:
|
||||
name: "x64 (MSVC): C++ (public headers)"
|
||||
# See: https://github.com/actions/runner-images#available-images.
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
|
||||
- name: C++ (public headers)
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
cl.exe -c -WX -TP include/*.h
|
||||
|
||||
cxx_fpermissive_debian:
|
||||
name: "C++ -fpermissive (entire project)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
configuration:
|
||||
- env_vars: {}
|
||||
|
||||
env:
|
||||
CC: 'g++'
|
||||
CFLAGS: '-fpermissive -g'
|
||||
CPPFLAGS: '-DSECP256K1_CPLUSPLUS_TEST_OVERRIDE'
|
||||
WERROR_CFLAGS:
|
||||
ECDH: 'yes'
|
||||
RECOVERY: 'yes'
|
||||
EXTRAKEYS: 'yes'
|
||||
SCHNORRSIG: 'yes'
|
||||
MUSIG: 'yes'
|
||||
ELLSWIFT: 'yes'
|
||||
EXPERIMENTAL: 'yes'
|
||||
ECDSA_S2C: 'yes'
|
||||
GENERATOR: 'yes'
|
||||
RANGEPROOF: 'yes'
|
||||
SURJECTIONPROOF: 'yes'
|
||||
WHITELIST: 'yes'
|
||||
ECDSAADAPTOR: 'yes'
|
||||
BPPP: 'yes'
|
||||
SCHNORRSIG_HALFAGG: 'yes'
|
||||
FROST: 'yes'
|
||||
CHILLDKG: 'yes'
|
||||
ICEBERG: 'yes'
|
||||
PREFRACTAL: 'yes'
|
||||
FROST_ENROLLMENT: 'yes'
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
- *CI_SCRIPT_IN_DOCKER
|
||||
- *PRINT_LOGS
|
||||
|
||||
cxx_headers_debian:
|
||||
name: "C++ (public headers)"
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_cache
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
|
||||
- name: CI script
|
||||
uses: ./.github/actions/run-in-docker-action
|
||||
with:
|
||||
dockerfile: ./ci/linux-debian.Dockerfile
|
||||
scope: ${{ runner.arch }}-${{ needs.docker_cache.outputs.cache_scope }}
|
||||
command: |
|
||||
g++ -Werror include/*.h
|
||||
clang -Werror -x c++-header include/*.h
|
||||
|
||||
sage:
|
||||
name: "SageMath prover"
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: sagemath/sagemath:latest
|
||||
options: --user root
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
|
||||
- name: CI script
|
||||
run: |
|
||||
cd sage
|
||||
sage prove_group_implementations.sage
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- *CHECKOUT
|
||||
|
||||
- run: ./autogen.sh && ./configure --enable-dev-mode && make distcheck
|
||||
|
||||
- name: Check installation with Autotools
|
||||
env:
|
||||
CI_INSTALL: ${{ runner.temp }}/${{ github.run_id }}${{ github.action }}/install
|
||||
run: |
|
||||
./autogen.sh && ./configure --prefix=${{ env.CI_INSTALL }} && make clean && make install && ls -RlAh ${{ env.CI_INSTALL }}
|
||||
gcc -o ecdsa examples/ecdsa.c $(PKG_CONFIG_PATH=${{ env.CI_INSTALL }}/lib/pkgconfig pkg-config --cflags --libs libsecp256k1) -Wl,-rpath,"${{ env.CI_INSTALL }}/lib" && ./ecdsa
|
||||
|
||||
# The experimental module flags are the only CMake coverage this fork's
|
||||
# modules get. Every other CMake invocation in this workflow is the
|
||||
# win64-native job, which builds with MSVC and /WX and has never
|
||||
# compiled any of them; turning them on there would be a first port,
|
||||
# not a regression guard. This step is gcc on Linux, so it exercises
|
||||
# what actually needs guarding: the SECP256K1_EXPERIMENTAL gate, the
|
||||
# dependent-module block ordering in src/CMakeLists.txt (where a block
|
||||
# placed after the one it force-enables silently produces a library
|
||||
# whose secp256k1.c never included the module), and the PUBLIC_HEADER
|
||||
# appends, which the install step then checks landed.
|
||||
- name: Check installation with CMake
|
||||
env:
|
||||
CI_BUILD: ${{ runner.temp }}/${{ github.run_id }}${{ github.action }}/build
|
||||
CI_INSTALL: ${{ runner.temp }}/${{ github.run_id }}${{ github.action }}/install
|
||||
run: |
|
||||
cmake -B ${{ env.CI_BUILD }} -DCMAKE_INSTALL_PREFIX=${{ env.CI_INSTALL }} -DSECP256K1_EXPERIMENTAL=ON -DSECP256K1_ENABLE_MODULE_FROST=ON -DSECP256K1_ENABLE_MODULE_CHILLDKG=ON -DSECP256K1_ENABLE_MODULE_ICEBERG=ON -DSECP256K1_ENABLE_MODULE_PREFRACTAL=ON -DSECP256K1_ENABLE_MODULE_FROST_ENROLLMENT=ON && cmake --build ${{ env.CI_BUILD }} && cmake --install ${{ env.CI_BUILD }} && ls -RlAh ${{ env.CI_INSTALL }}
|
||||
gcc -o ecdsa examples/ecdsa.c -I ${{ env.CI_INSTALL }}/include -L ${{ env.CI_INSTALL }}/lib*/ -l secp256k1 -Wl,-rpath,"${{ env.CI_INSTALL }}/lib",-rpath,"${{ env.CI_INSTALL }}/lib64" && ./ecdsa
|
||||
# Assert the modules actually landed. `ls -RlAh` above only prints,
|
||||
# so without these a regression that silently drops a module from
|
||||
# the CMake build -- a deleted add_compile_definitions or
|
||||
# PUBLIC_HEADER line during an upstream sync -- would keep this step
|
||||
# green: no module code means no undefined references, and the ecdsa
|
||||
# link exercises core symbols only.
|
||||
ls ${{ env.CI_INSTALL }}/include/secp256k1_frost.h ${{ env.CI_INSTALL }}/include/secp256k1_chilldkg.h ${{ env.CI_INSTALL }}/include/secp256k1_iceberg.h ${{ env.CI_INSTALL }}/include/secp256k1_prefractal.h ${{ env.CI_INSTALL }}/include/secp256k1_frost_enrollment.h
|
||||
# And that the symbols are really in the library, not just the
|
||||
# headers on disk.
|
||||
gcc -o frost_enrollment examples/frost_enrollment.c -I ${{ env.CI_INSTALL }}/include -L ${{ env.CI_INSTALL }}/lib*/ -l secp256k1 -Wl,-rpath,"${{ env.CI_INSTALL }}/lib",-rpath,"${{ env.CI_INSTALL }}/lib64" && ./frost_enrollment
|
||||
61
.github/workflows/sync.yml
vendored
61
.github/workflows/sync.yml
vendored
@@ -1,61 +0,0 @@
|
||||
name: Upstream Sync
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 1 * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-upstream:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
UPSTREAM: "https://github.com/bitcoin-core/secp256k1.git"
|
||||
BASE_BRANCH: "master"
|
||||
UPSTREAM_REF: "upstream/master"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.SYNC_PAT }}
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Fetch upstream
|
||||
run: |
|
||||
git remote add upstream ${{ env.UPSTREAM }}
|
||||
git fetch upstream
|
||||
gh repo set-default ${{ github.repository }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SYNC_PAT }}
|
||||
|
||||
- name: Run sync-upstream.sh
|
||||
id: sync
|
||||
run: |
|
||||
OUTPUT=$(./contrib/sync-upstream.sh --switch "${{ env.BASE_BRANCH }}" "${{ env.UPSTREAM_REF }}" 2>&1) || { echo "$OUTPUT"; exit 1; }
|
||||
echo "$OUTPUT"
|
||||
if echo "$OUTPUT" | grep -qv "^No merge commits"; then
|
||||
echo "newcommits=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Skipping further workflow steps."
|
||||
fi
|
||||
|
||||
- name: Push sync branch
|
||||
id: push
|
||||
if: steps.sync.outputs.newcommits == 'true'
|
||||
run: |
|
||||
if git push -u origin HEAD; then
|
||||
echo "pushed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Skipping further workflow steps."
|
||||
fi
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.sync.outputs.newcommits == 'true' && steps.push.outputs.pushed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SYNC_PAT }}
|
||||
run: ./gh-pr-create.sh
|
||||
69
.gitignore
vendored
69
.gitignore
vendored
@@ -1,29 +1,20 @@
|
||||
bench
|
||||
bench_bppp
|
||||
bench_inv
|
||||
bench_ecdh
|
||||
bench_ecmult
|
||||
bench_generator
|
||||
bench_rangeproof
|
||||
bench_schnorrsig
|
||||
bench_sign
|
||||
bench_verify
|
||||
bench_recover
|
||||
bench_internal
|
||||
bench_whitelist
|
||||
bench_iceberg
|
||||
noverify_tests
|
||||
tests
|
||||
exhaustive_tests
|
||||
precompute_ecmult_gen
|
||||
precompute_ecmult
|
||||
ctime_tests
|
||||
ecdh_example
|
||||
ecdsa_example
|
||||
schnorr_example
|
||||
ellswift_example
|
||||
musig_example
|
||||
gen_context
|
||||
*.exe
|
||||
*.so
|
||||
*.a
|
||||
*.csv
|
||||
*.log
|
||||
*.trs
|
||||
*.sage.py
|
||||
!.gitignore
|
||||
|
||||
Makefile
|
||||
configure
|
||||
@@ -33,7 +24,6 @@ aclocal.m4
|
||||
autom4te.cache/
|
||||
config.log
|
||||
config.status
|
||||
conftest*
|
||||
*.tar.gz
|
||||
*.la
|
||||
libtool
|
||||
@@ -42,30 +32,21 @@ libtool
|
||||
*.lo
|
||||
*.o
|
||||
*~
|
||||
|
||||
coverage/
|
||||
coverage.html
|
||||
coverage.*.html
|
||||
*.gcda
|
||||
*.gcno
|
||||
*.gcov
|
||||
|
||||
/autotools-aux/
|
||||
!/autotools-aux/m4/bitcoin_secp.m4
|
||||
|
||||
src/libsecp256k1-config.h
|
||||
src/libsecp256k1-config.h.in
|
||||
src/ecmult_static_context.h
|
||||
build-aux/config.guess
|
||||
build-aux/config.sub
|
||||
build-aux/depcomp
|
||||
build-aux/install-sh
|
||||
build-aux/ltmain.sh
|
||||
build-aux/m4/libtool.m4
|
||||
build-aux/m4/lt~obsolete.m4
|
||||
build-aux/m4/ltoptions.m4
|
||||
build-aux/m4/ltsugar.m4
|
||||
build-aux/m4/ltversion.m4
|
||||
build-aux/missing
|
||||
build-aux/compile
|
||||
build-aux/test-driver
|
||||
src/stamp-h1
|
||||
libsecp256k1.pc
|
||||
contrib/gh-pr-create.sh
|
||||
|
||||
frost_example
|
||||
chilldkg_example
|
||||
iceberg_example
|
||||
frost_enrollment_example
|
||||
|
||||
### CMake
|
||||
/CMakeUserPresets.json
|
||||
# CMake build directories.
|
||||
/*build*
|
||||
|
||||
### Python
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
|
||||
70
.travis.yml
Normal file
70
.travis.yml
Normal file
@@ -0,0 +1,70 @@
|
||||
language: c
|
||||
os: linux
|
||||
addons:
|
||||
apt:
|
||||
packages: libgmp-dev
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
cache:
|
||||
directories:
|
||||
- src/java/guava/
|
||||
env:
|
||||
global:
|
||||
- FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no EXPERIMENTAL=no JNI=no GENERATOR=no RANGEPROOF=no WHITELIST=no
|
||||
- GUAVA_URL=https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar GUAVA_JAR=src/java/guava/guava-18.0.jar
|
||||
matrix:
|
||||
- SCALAR=32bit FIELD=32bit EXPERIMENTAL=yes RANGEPROOF=yes WHITELIST=yes GENERATOR=yes
|
||||
- FIELD=64bit EXPERIMENTAL=yes RANGEPROOF=yes WHITELIST=yes GENERATOR=yes
|
||||
- SCALAR=32bit RECOVERY=yes
|
||||
- SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes
|
||||
- SCALAR=64bit
|
||||
- FIELD=64bit RECOVERY=yes
|
||||
- FIELD=64bit ENDOMORPHISM=yes
|
||||
- FIELD=64bit ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes
|
||||
- FIELD=64bit ASM=x86_64
|
||||
- FIELD=64bit ENDOMORPHISM=yes ASM=x86_64
|
||||
- FIELD=32bit ENDOMORPHISM=yes
|
||||
- BIGNUM=no
|
||||
- BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes
|
||||
- BIGNUM=no STATICPRECOMPUTATION=no
|
||||
- BUILD=distcheck
|
||||
- EXTRAFLAGS=CPPFLAGS=-DDETERMINISTIC
|
||||
- EXTRAFLAGS=CFLAGS=-O0
|
||||
- BUILD=check-java JNI=yes ECDH=yes EXPERIMENTAL=yes
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- compiler: clang
|
||||
env: HOST=i686-linux-gnu ENDOMORPHISM=yes
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- gcc-multilib
|
||||
- libgmp-dev:i386
|
||||
- compiler: clang
|
||||
env: HOST=i686-linux-gnu
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- gcc-multilib
|
||||
- compiler: gcc
|
||||
env: HOST=i686-linux-gnu ENDOMORPHISM=yes
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- gcc-multilib
|
||||
- compiler: gcc
|
||||
env: HOST=i686-linux-gnu
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- gcc-multilib
|
||||
- libgmp-dev:i386
|
||||
before_install: mkdir -p `dirname $GUAVA_JAR`
|
||||
install: if [ ! -f $GUAVA_JAR ]; then wget $GUAVA_URL -O $GUAVA_JAR; fi
|
||||
before_script: ./autogen.sh
|
||||
script:
|
||||
- if [ -n "$HOST" ]; then export USE_HOST="--host=$HOST"; fi
|
||||
- if [ "x$HOST" = "xi686-linux-gnu" ]; then export CC="$CC -m32"; fi
|
||||
- ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY --enable-module-rangeproof=$RANGEPROOF --enable-module-whitelist=$WHITELIST --enable-module-generator=$GENERATOR --enable-jni=$JNI $EXTRAFLAGS $USE_HOST && make -j2 $BUILD
|
||||
217
CHANGELOG.md
217
CHANGELOG.md
@@ -1,217 +0,0 @@
|
||||
**This changelog is not the libsecp256k1-zkp's changelog.**
|
||||
Instead, it is the changelog of the upstream library [libsecp256k1](https://github.com/bitcoin-core/secp256k1).
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.7.1] - 2026-01-26
|
||||
|
||||
#### Changed
|
||||
- Tests: Introduced a unit test framework with support for parallel test execution, selective test running, and named command-line arguments. Run `./tests -help` for usage information.
|
||||
|
||||
#### Fixed
|
||||
- Increased the number of cases where the library attempts to clear secrets from the stack.
|
||||
- build: Fixed x86_64 assembly feature check that could fail when user-provided `CFLAGS` included `-Werror`. This would cause the build to fall back to the slower C implementation instead of using the optimized x86_64 assembly.
|
||||
|
||||
#### ABI Compatibility
|
||||
The ABI is backward compatible with version 0.7.0.
|
||||
|
||||
## [0.7.0] - 2025-07-21
|
||||
|
||||
#### Added
|
||||
- CMake: Added `secp256k1_objs` interface library to allow parent projects to embed libsecp256k1 object files into their own static libraries.
|
||||
- build: Added `SECP256K1_NO_API_VISIBILITY_ATTRIBUTES` preprocessor flag (CMake option: `SECP256K1_ENABLE_API_VISIBILITY_ATTRIBUTES`) that disables explicit "visibility" attributes for API symbols. Defining this macro enables the user to control the visibility of the API symbols via `-fvisibility=<value>` when building libsecp256k1. (All non-API declarations will always have hidden visibility, even with `SECP256K1_ENABLE_API_VISIBILITY_ATTRIBUTES` defined.) For instance, `-fvisibility=hidden` can be useful even for the API symbols, e.g., when building a static libsecp256k1 which is linked into a shared library, and the latter should not re-export the libsecp256k1 API.
|
||||
|
||||
#### Changed
|
||||
- The pointers `secp256k1_context_static` and `secp256k1_context_no_precomp` to the constant context objects are now `const`.
|
||||
- Removed `SECP256K1_WARN_UNUSED_RESULT` attribute (defined as `__attribute__ ((__warn_unused_result__))`) from several API functions that always return 1. Compilers will no longer warn if the return value is unused.
|
||||
- CMake: Building with CMake is no longer considered experimental.
|
||||
- CMake: The minimum required CMake version was increased to 3.22.
|
||||
- CMake: Shared libraries built with CMake on FreeBSD now create the full versioned filename and symlink chain, matching the behavior of autotools builds.
|
||||
|
||||
#### Removed
|
||||
- Removed previously deprecated function aliases `secp256k1_ec_privkey_negate`, `secp256k1_ec_privkey_tweak_add` and
|
||||
`secp256k1_ec_privkey_tweak_mul`. Use `secp256k1_ec_seckey_negate`, `secp256k1_ec_seckey_tweak_add` and
|
||||
`secp256k1_ec_seckey_tweak_mul` instead.
|
||||
|
||||
#### ABI Compatibility
|
||||
The symbols `secp256k1_ec_privkey_negate`, `secp256k1_ec_privkey_tweak_add`, and `secp256k1_ec_privkey_tweak_mul` were removed.
|
||||
The pointers `secp256k1_context_static` and `secp256k1_context_no_precomp` have been made `const`.
|
||||
Otherwise, the library maintains backward compatibility with version 0.6.0.
|
||||
|
||||
## [0.6.0] - 2024-11-04
|
||||
|
||||
#### Added
|
||||
- New module `musig` implements the MuSig2 multisignature scheme according to the [BIP 327 specification](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki). See:
|
||||
- Header file `include/secp256k1_musig.h` which defines the new API.
|
||||
- Document `doc/musig.md` for further notes on API usage.
|
||||
- Usage example `examples/musig.c`.
|
||||
- New CMake variable `SECP256K1_APPEND_LDFLAGS` for appending linker flags to the build command.
|
||||
|
||||
#### Changed
|
||||
- API functions now use a significantly more robust method to clear secrets from the stack before returning. However, secret clearing remains a best-effort security measure and cannot guarantee complete removal.
|
||||
- Any type `secp256k1_foo` can now be forward-declared using `typedef struct secp256k1_foo secp256k1_foo;` (or also `struct secp256k1_foo;` in C++).
|
||||
- Organized CMake build artifacts into dedicated directories (`bin/` for executables, `lib/` for libraries) to improve build output structure and Windows shared library compatibility.
|
||||
|
||||
#### Removed
|
||||
- Removed the `secp256k1_scratch_space` struct and its associated functions `secp256k1_scratch_space_create` and `secp256k1_scratch_space_destroy` because the scratch space was unused in the API.
|
||||
|
||||
#### ABI Compatibility
|
||||
The symbols `secp256k1_scratch_space_create` and `secp256k1_scratch_space_destroy` were removed.
|
||||
Otherwise, the library maintains backward compatibility with versions 0.3.x through 0.5.x.
|
||||
|
||||
## [0.5.1] - 2024-08-01
|
||||
|
||||
#### Added
|
||||
- Added usage example for an ElligatorSwift key exchange.
|
||||
|
||||
#### Changed
|
||||
- The default size of the precomputed table for signing was changed from 22 KiB to 86 KiB. The size can be changed with the configure option `--ecmult-gen-kb` (`SECP256K1_ECMULT_GEN_KB` for CMake).
|
||||
- "auto" is no longer an accepted value for the `--with-ecmult-window` and `--with-ecmult-gen-kb` configure options (this also applies to `SECP256K1_ECMULT_WINDOW_SIZE` and `SECP256K1_ECMULT_GEN_KB` in CMake). To achieve the same configuration as previously provided by the "auto" value, omit setting the configure option explicitly.
|
||||
|
||||
#### Fixed
|
||||
- Fixed compilation when the extrakeys module is disabled.
|
||||
|
||||
#### ABI Compatibility
|
||||
The ABI is backward compatible with versions 0.5.0, 0.4.x and 0.3.x.
|
||||
|
||||
## [0.5.0] - 2024-05-06
|
||||
|
||||
#### Added
|
||||
- New function `secp256k1_ec_pubkey_sort` that sorts public keys using lexicographic (of compressed serialization) order.
|
||||
|
||||
#### Changed
|
||||
- The implementation of the point multiplication algorithm used for signing and public key generation was changed, resulting in improved performance for those operations.
|
||||
- The related configure option `--ecmult-gen-precision` was replaced with `--ecmult-gen-kb` (`SECP256K1_ECMULT_GEN_KB` for CMake).
|
||||
- This changes the supported precomputed table sizes for these operations. The new supported sizes are 2 KiB, 22 KiB, or 86 KiB (while the old supported sizes were 32 KiB, 64 KiB, or 512 KiB).
|
||||
|
||||
#### ABI Compatibility
|
||||
The ABI is backward compatible with versions 0.4.x and 0.3.x.
|
||||
|
||||
## [0.4.1] - 2023-12-21
|
||||
|
||||
#### Changed
|
||||
- The point multiplication algorithm used for ECDH operations (module `ecdh`) was replaced with a slightly faster one.
|
||||
- Optional handwritten x86_64 assembly for field operations was removed because modern C compilers are able to output more efficient assembly. This change results in a significant speedup of some library functions when handwritten x86_64 assembly is enabled (`--with-asm=x86_64` in GNU Autotools, `-DSECP256K1_ASM=x86_64` in CMake), which is the default on x86_64. Benchmarks with GCC 10.5.0 show a 10% speedup for `secp256k1_ecdsa_verify` and `secp256k1_schnorrsig_verify`.
|
||||
|
||||
#### ABI Compatibility
|
||||
The ABI is backward compatible with versions 0.4.0 and 0.3.x.
|
||||
|
||||
## [0.4.0] - 2023-09-04
|
||||
|
||||
#### Added
|
||||
- New module `ellswift` implements ElligatorSwift encoding for public keys and x-only Diffie-Hellman key exchange for them.
|
||||
ElligatorSwift permits representing secp256k1 public keys as 64-byte arrays which cannot be distinguished from uniformly random. See:
|
||||
- Header file `include/secp256k1_ellswift.h` which defines the new API.
|
||||
- Document `doc/ellswift.md` which explains the mathematical background of the scheme.
|
||||
- The [paper](https://eprint.iacr.org/2022/759) on which the scheme is based.
|
||||
- We now test the library with unreleased development snapshots of GCC and Clang. This gives us an early chance to catch miscompilations and constant-time issues introduced by the compiler (such as those that led to the previous two releases).
|
||||
|
||||
#### Fixed
|
||||
- Fixed symbol visibility in Windows DLL builds, where three internal library symbols were wrongly exported.
|
||||
|
||||
#### Changed
|
||||
- When consuming libsecp256k1 as a static library on Windows, the user must now define the `SECP256K1_STATIC` macro before including `secp256k1.h`.
|
||||
|
||||
#### ABI Compatibility
|
||||
This release is backward compatible with the ABI of 0.3.0, 0.3.1, and 0.3.2. Symbol visibility is now believed to be handled properly on supported platforms and is now considered to be part of the ABI. Please report any improperly exported symbols as a bug.
|
||||
|
||||
## [0.3.2] - 2023-05-13
|
||||
We strongly recommend updating to 0.3.2 if you use or plan to use GCC >=13 to compile libsecp256k1. When in doubt, check the GCC version using `gcc -v`.
|
||||
|
||||
#### Security
|
||||
- Module `ecdh`: Fix "constant-timeness" issue with GCC 13.1 (and potentially future versions of GCC) that could leave applications using libsecp256k1's ECDH module vulnerable to a timing side-channel attack. The fix avoids secret-dependent control flow during ECDH computations when libsecp256k1 is compiled with GCC 13.1.
|
||||
|
||||
#### Fixed
|
||||
- Fixed an old bug that permitted compilers to potentially output bad assembly code on x86_64. In theory, it could lead to a crash or a read of unrelated memory, but this has never been observed on any compilers so far.
|
||||
|
||||
#### Changed
|
||||
- Various improvements and changes to CMake builds. CMake builds remain experimental.
|
||||
- Made API versioning consistent with GNU Autotools builds.
|
||||
- Switched to `BUILD_SHARED_LIBS` variable for controlling whether to build a static or a shared library.
|
||||
- Added `SECP256K1_INSTALL` variable for the controlling whether to install the build artefacts.
|
||||
- Renamed asm build option `arm` to `arm32`. Use `--with-asm=arm32` instead of `--with-asm=arm` (GNU Autotools), and `-DSECP256K1_ASM=arm32` instead of `-DSECP256K1_ASM=arm` (CMake).
|
||||
|
||||
#### ABI Compatibility
|
||||
The ABI is compatible with versions 0.3.0 and 0.3.1.
|
||||
|
||||
## [0.3.1] - 2023-04-10
|
||||
We strongly recommend updating to 0.3.1 if you use or plan to use Clang >=14 to compile libsecp256k1, e.g., Xcode >=14 on macOS has Clang >=14. When in doubt, check the Clang version using `clang -v`.
|
||||
|
||||
#### Security
|
||||
- Fix "constant-timeness" issue with Clang >=14 that could leave applications using libsecp256k1 vulnerable to a timing side-channel attack. The fix avoids secret-dependent control flow and secret-dependent memory accesses in conditional moves of memory objects when libsecp256k1 is compiled with Clang >=14.
|
||||
|
||||
#### Added
|
||||
- Added tests against [Project Wycheproof's](https://github.com/C2SP/wycheproof/) set of ECDSA test vectors (Bitcoin "low-S" variant), a fixed set of test cases designed to trigger various edge cases.
|
||||
|
||||
#### Changed
|
||||
- Increased minimum required CMake version to 3.13. CMake builds remain experimental.
|
||||
|
||||
#### ABI Compatibility
|
||||
The ABI is compatible with version 0.3.0.
|
||||
|
||||
## [0.3.0] - 2023-03-08
|
||||
|
||||
#### Added
|
||||
- Added experimental support for CMake builds. Traditional GNU Autotools builds (`./configure` and `make`) remain fully supported.
|
||||
- Usage examples: Added a recommended method for securely clearing sensitive data, e.g., secret keys, from memory.
|
||||
- Tests: Added a new test binary `noverify_tests`. This binary runs the tests without some additional checks present in the ordinary `tests` binary and is thereby closer to production binaries. The `noverify_tests` binary is automatically run as part of the `make check` target.
|
||||
|
||||
#### Fixed
|
||||
- Fixed declarations of API variables for MSVC (`__declspec(dllimport)`). This fixes MSVC builds of programs which link against a libsecp256k1 DLL dynamically and use API variables (and not only API functions). Unfortunately, the MSVC linker now will emit warning `LNK4217` when trying to link against libsecp256k1 statically. Pass `/ignore:4217` to the linker to suppress this warning.
|
||||
|
||||
#### Changed
|
||||
- Forbade cloning or destroying `secp256k1_context_static`. Create a new context instead of cloning the static context. (If this change breaks your code, your code is probably wrong.)
|
||||
- Forbade randomizing (copies of) `secp256k1_context_static`. Randomizing a copy of `secp256k1_context_static` did not have any effect and did not provide defense-in-depth protection against side-channel attacks. Create a new context if you want to benefit from randomization.
|
||||
|
||||
#### Removed
|
||||
- Removed the configuration header `src/libsecp256k1-config.h`. We recommend passing flags to `./configure` or `cmake` to set configuration options (see `./configure --help` or `cmake -LH`). If you cannot or do not want to use one of the supported build systems, pass configuration flags such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG` manually to the compiler (see the file `configure.ac` for supported flags).
|
||||
|
||||
#### ABI Compatibility
|
||||
Due to changes in the API regarding `secp256k1_context_static` described above, the ABI is *not* compatible with previous versions.
|
||||
|
||||
## [0.2.0] - 2022-12-12
|
||||
|
||||
#### Added
|
||||
- Added usage examples for common use cases in a new `examples/` directory.
|
||||
- Added `secp256k1_selftest`, to be used in conjunction with `secp256k1_context_static`.
|
||||
- Added support for 128-bit wide multiplication on MSVC for x86_64 and arm64, giving roughly a 20% speedup on those platforms.
|
||||
|
||||
#### Changed
|
||||
- Enabled modules `schnorrsig`, `extrakeys` and `ecdh` by default in `./configure`.
|
||||
- The `secp256k1_nonce_function_rfc6979` nonce function, used by default by `secp256k1_ecdsa_sign`, now reduces the message hash modulo the group order to match the specification. This only affects improper use of ECDSA signing API.
|
||||
|
||||
#### Deprecated
|
||||
- Deprecated context flags `SECP256K1_CONTEXT_VERIFY` and `SECP256K1_CONTEXT_SIGN`. Use `SECP256K1_CONTEXT_NONE` instead.
|
||||
- Renamed `secp256k1_context_no_precomp` to `secp256k1_context_static`.
|
||||
- Module `schnorrsig`: renamed `secp256k1_schnorrsig_sign` to `secp256k1_schnorrsig_sign32`.
|
||||
|
||||
#### ABI Compatibility
|
||||
Since this is the first release, we do not compare application binary interfaces.
|
||||
However, there are earlier unreleased versions of libsecp256k1 that are *not* ABI compatible with this version.
|
||||
|
||||
## [0.1.0] - 2013-03-05 to 2021-12-25
|
||||
|
||||
This version was in fact never released.
|
||||
The number was given by the build system since the introduction of autotools in Jan 2014 (ea0fe5a5bf0c04f9cc955b2966b614f5f378c6f6).
|
||||
Therefore, this version number does not uniquely identify a set of source files.
|
||||
|
||||
[Unreleased]: https://github.com/bitcoin-core/secp256k1/compare/v0.7.1...HEAD
|
||||
[0.7.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.7.0...v0.7.1
|
||||
[0.7.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.6.0...v0.7.0
|
||||
[0.6.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.1...v0.6.0
|
||||
[0.5.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.5.0...v0.5.1
|
||||
[0.5.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.4.1...v0.5.0
|
||||
[0.4.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.4.0...v0.4.1
|
||||
[0.4.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.3.2...v0.4.0
|
||||
[0.3.2]: https://github.com/bitcoin-core/secp256k1/compare/v0.3.1...v0.3.2
|
||||
[0.3.1]: https://github.com/bitcoin-core/secp256k1/compare/v0.3.0...v0.3.1
|
||||
[0.3.0]: https://github.com/bitcoin-core/secp256k1/compare/v0.2.0...v0.3.0
|
||||
[0.2.0]: https://github.com/bitcoin-core/secp256k1/compare/423b6d19d373f1224fd671a982584d7e7900bc93..v0.2.0
|
||||
[0.1.0]: https://github.com/bitcoin-core/secp256k1/commit/423b6d19d373f1224fd671a982584d7e7900bc93
|
||||
410
CMakeLists.txt
410
CMakeLists.txt
@@ -1,410 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
#=============================
|
||||
# Project / Package metadata
|
||||
#=============================
|
||||
project(libsecp256k1
|
||||
# The package (a.k.a. release) version is based on semantic versioning 2.0.0 of
|
||||
# the API. All changes in experimental modules are treated as
|
||||
# backwards-compatible and therefore at most increase the minor version.
|
||||
VERSION 0.7.2
|
||||
DESCRIPTION "Optimized C library for ECDSA signatures and secret/public key operations on curve secp256k1."
|
||||
HOMEPAGE_URL "https://github.com/bitcoin-core/secp256k1"
|
||||
LANGUAGES C
|
||||
)
|
||||
enable_testing()
|
||||
include(CTestUseLaunchers) # Allow users to set CTEST_USE_LAUNCHERS in custom `ctest -S` scripts.
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||
|
||||
# The library version is based on libtool versioning of the ABI. The set of
|
||||
# rules for updating the version can be found here:
|
||||
# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
||||
# All changes in experimental modules are treated as if they don't affect the
|
||||
# interface and therefore only increase the revision.
|
||||
set(${PROJECT_NAME}_LIB_VERSION_CURRENT 6)
|
||||
set(${PROJECT_NAME}_LIB_VERSION_REVISION 2)
|
||||
set(${PROJECT_NAME}_LIB_VERSION_AGE 0)
|
||||
|
||||
#=============================
|
||||
# Language setup
|
||||
#=============================
|
||||
set(CMAKE_C_STANDARD 90)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
#=============================
|
||||
# Configurable options
|
||||
#=============================
|
||||
if(libsecp256k1_IS_TOP_LEVEL)
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries." ON)
|
||||
endif()
|
||||
|
||||
option(SECP256K1_INSTALL "Enable installation." ${PROJECT_IS_TOP_LEVEL})
|
||||
|
||||
option(SECP256K1_ENABLE_API_VISIBILITY_ATTRIBUTES "Enable visibility attributes in the API." ON)
|
||||
|
||||
## Modules
|
||||
|
||||
# We declare all options before processing them, to make sure we can express
|
||||
# dependencies while processing.
|
||||
option(SECP256K1_ENABLE_MODULE_ECDH "Enable ECDH module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_RECOVERY "Enable ECDSA pubkey recovery module." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_EXTRAKEYS "Enable extrakeys module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_SCHNORRSIG "Enable schnorrsig module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_MUSIG "Enable musig module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_FROST "Enable FROST module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_CHILLDKG "Enable ChillDKG module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_ICEBERG "Enable Iceberg threshold-MuSig module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_PREFRACTAL "Enable Prefractal nested FROST+MuSig2 module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT "Enable FROST enrollment module (experimental)." OFF)
|
||||
option(SECP256K1_ENABLE_MODULE_ELLSWIFT "Enable ElligatorSwift module." ON)
|
||||
|
||||
option(SECP256K1_ENABLE_MODULE_GENERATOR "Enable NUMS generator module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_RANGEPROOF "Enable Range proof module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_SURJECTIONPROOF "Enable Surjection proof module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_WHITELIST "Enable key whitelist module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_ECDSA_ADAPTOR "Enable ecdsa adaptor signatures module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_ECDSA_S2C "Enable ECDSA sign-to-contract module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_BPPP "Enable Bulletproofs++ module." ON)
|
||||
option(SECP256K1_ENABLE_MODULE_SCHNORRSIG_HALFAGG "Enable schnorrsig half-aggregation module." ON)
|
||||
|
||||
option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF)
|
||||
if(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS)
|
||||
add_compile_definitions(USE_EXTERNAL_DEFAULT_CALLBACKS=1)
|
||||
endif()
|
||||
|
||||
set(SECP256K1_ECMULT_WINDOW_SIZE 15 CACHE STRING "Window size for ecmult precomputation for verification, specified as integer in range [2..24]. The default value is a reasonable setting for desktop machines (currently 15). [default=15]")
|
||||
set_property(CACHE SECP256K1_ECMULT_WINDOW_SIZE PROPERTY STRINGS 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24)
|
||||
include(CheckStringOptionValue)
|
||||
check_string_option_value(SECP256K1_ECMULT_WINDOW_SIZE)
|
||||
add_compile_definitions(ECMULT_WINDOW_SIZE=${SECP256K1_ECMULT_WINDOW_SIZE})
|
||||
|
||||
set(SECP256K1_ECMULT_GEN_KB 86 CACHE STRING "The size of the precomputed table for signing in multiples of 1024 bytes (on typical platforms). Larger values result in possibly better signing or key generation performance at the cost of a larger table. Valid choices are 2, 22, 86. The default value is a reasonable setting for desktop machines (currently 86). [default=86]")
|
||||
set_property(CACHE SECP256K1_ECMULT_GEN_KB PROPERTY STRINGS 2 22 86)
|
||||
check_string_option_value(SECP256K1_ECMULT_GEN_KB)
|
||||
if(SECP256K1_ECMULT_GEN_KB EQUAL 2)
|
||||
add_compile_definitions(COMB_BLOCKS=2)
|
||||
add_compile_definitions(COMB_TEETH=5)
|
||||
elseif(SECP256K1_ECMULT_GEN_KB EQUAL 22)
|
||||
add_compile_definitions(COMB_BLOCKS=11)
|
||||
add_compile_definitions(COMB_TEETH=6)
|
||||
elseif(SECP256K1_ECMULT_GEN_KB EQUAL 86)
|
||||
add_compile_definitions(COMB_BLOCKS=43)
|
||||
add_compile_definitions(COMB_TEETH=6)
|
||||
endif()
|
||||
|
||||
set(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY "OFF" CACHE STRING "Test-only override of the (autodetected by the C code) \"widemul\" setting. Legal values are: \"OFF\", \"int128_struct\", \"int128\" or \"int64\". [default=OFF]")
|
||||
set_property(CACHE SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY PROPERTY STRINGS "OFF" "int128_struct" "int128" "int64")
|
||||
check_string_option_value(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY)
|
||||
if(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY)
|
||||
string(TOUPPER "${SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY}" widemul_upper_value)
|
||||
add_compile_definitions(USE_FORCE_WIDEMUL_${widemul_upper_value}=1)
|
||||
endif()
|
||||
mark_as_advanced(FORCE SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY)
|
||||
|
||||
set(SECP256K1_ASM "AUTO" CACHE STRING "Assembly to use: \"AUTO\", \"OFF\", \"x86_64\" or \"arm32\" (experimental). [default=AUTO]")
|
||||
set_property(CACHE SECP256K1_ASM PROPERTY STRINGS "AUTO" "OFF" "x86_64" "arm32")
|
||||
check_string_option_value(SECP256K1_ASM)
|
||||
if(SECP256K1_ASM STREQUAL "arm32")
|
||||
enable_language(ASM)
|
||||
include(CheckArm32Assembly)
|
||||
check_arm32_assembly()
|
||||
if(HAVE_ARM32_ASM)
|
||||
add_compile_definitions(USE_EXTERNAL_ASM=1)
|
||||
else()
|
||||
message(FATAL_ERROR "ARM32 assembly requested but not available.")
|
||||
endif()
|
||||
elseif(SECP256K1_ASM)
|
||||
include(CheckX86_64Assembly)
|
||||
check_x86_64_assembly()
|
||||
if(HAVE_X86_64_ASM)
|
||||
set(SECP256K1_ASM "x86_64")
|
||||
add_compile_definitions(USE_ASM_X86_64=1)
|
||||
elseif(SECP256K1_ASM STREQUAL "AUTO")
|
||||
set(SECP256K1_ASM "OFF")
|
||||
else()
|
||||
message(FATAL_ERROR "x86_64 assembly requested but not available.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(SECP256K1_EXPERIMENTAL "Allow experimental configuration options." OFF)
|
||||
if(NOT SECP256K1_EXPERIMENTAL)
|
||||
if(SECP256K1_ASM STREQUAL "arm32")
|
||||
message(FATAL_ERROR "ARM32 assembly is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_FROST)
|
||||
message(FATAL_ERROR "FROST module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_CHILLDKG)
|
||||
message(FATAL_ERROR "ChillDKG module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_ICEBERG)
|
||||
message(FATAL_ERROR "Iceberg module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_PREFRACTAL)
|
||||
message(FATAL_ERROR "Prefractal module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
if(SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT)
|
||||
message(FATAL_ERROR "FROST enrollment module is experimental. Use -DSECP256K1_EXPERIMENTAL=ON to allow.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(SECP256K1_VALGRIND "AUTO" CACHE STRING "Build with extra checks for running inside Valgrind. [default=AUTO]")
|
||||
set_property(CACHE SECP256K1_VALGRIND PROPERTY STRINGS "AUTO" "OFF" "ON")
|
||||
check_string_option_value(SECP256K1_VALGRIND)
|
||||
if(SECP256K1_VALGRIND)
|
||||
find_package(Valgrind MODULE)
|
||||
if(Valgrind_FOUND)
|
||||
set(SECP256K1_VALGRIND ON)
|
||||
include_directories(${Valgrind_INCLUDE_DIR})
|
||||
add_compile_definitions(VALGRIND)
|
||||
elseif(SECP256K1_VALGRIND STREQUAL "AUTO")
|
||||
set(SECP256K1_VALGRIND OFF)
|
||||
else()
|
||||
message(FATAL_ERROR "Valgrind support requested but valgrind/memcheck.h header not available.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(SECP256K1_BUILD_BENCHMARK "Build benchmarks." ON)
|
||||
option(SECP256K1_BUILD_TESTS "Build tests." ON)
|
||||
option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." ON)
|
||||
option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." ${SECP256K1_VALGRIND})
|
||||
option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF)
|
||||
|
||||
# Redefine configuration flags.
|
||||
# We leave assertions on, because they are only used in the examples, and we want them always on there.
|
||||
if(MSVC)
|
||||
string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
|
||||
string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
string(REGEX REPLACE "/DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}")
|
||||
# Match GCC/Clang's size-optimization macro for the inline guard
|
||||
add_compile_definitions($<$<CONFIG:MinSizeRel>:__OPTIMIZE_SIZE__=1>)
|
||||
else()
|
||||
string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
|
||||
string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
string(REGEX REPLACE "-DNDEBUG[ \t\r\n]*" "" CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}")
|
||||
# Prefer -O2 optimization level. (-O3 is CMake's default for Release for many compilers.)
|
||||
string(REGEX REPLACE "-O3( |$)" "-O2\\1" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
|
||||
endif()
|
||||
|
||||
# Define custom "Coverage" build type.
|
||||
set(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_RELWITHDEBINFO} -O0 -DCOVERAGE=1 --coverage" CACHE STRING
|
||||
"Flags used by the C compiler during \"Coverage\" builds."
|
||||
FORCE
|
||||
)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} --coverage" CACHE STRING
|
||||
"Flags used for linking binaries during \"Coverage\" builds."
|
||||
FORCE
|
||||
)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} --coverage" CACHE STRING
|
||||
"Flags used by the shared libraries linker during \"Coverage\" builds."
|
||||
FORCE
|
||||
)
|
||||
mark_as_advanced(
|
||||
CMAKE_C_FLAGS_COVERAGE
|
||||
CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||
CMAKE_SHARED_LINKER_FLAGS_COVERAGE
|
||||
)
|
||||
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
set(default_build_type "RelWithDebInfo")
|
||||
if(is_multi_config)
|
||||
set(CMAKE_CONFIGURATION_TYPES "${default_build_type}" "Release" "Debug" "MinSizeRel" "Coverage" CACHE STRING
|
||||
"Supported configuration types."
|
||||
FORCE
|
||||
)
|
||||
else()
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
|
||||
STRINGS "${default_build_type}" "Release" "Debug" "MinSizeRel" "Coverage"
|
||||
)
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
message(STATUS "Setting build type to \"${default_build_type}\" as none was specified")
|
||||
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
|
||||
"Choose the type of build."
|
||||
FORCE
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(TryAppendCFlags)
|
||||
if(MSVC)
|
||||
# For both cl and clang-cl compilers.
|
||||
try_append_c_flags(/W3) # Production quality warning level.
|
||||
# Eliminate deprecation warnings for the older, less secure functions.
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
else()
|
||||
try_append_c_flags(-Wall) # GCC >= 2.95 and probably many other compilers.
|
||||
endif()
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
|
||||
# Keep the following commands ordered lexicographically.
|
||||
try_append_c_flags(/wd4146) # Disable warning C4146 "unary minus operator applied to unsigned type, result still unsigned".
|
||||
try_append_c_flags(/wd4244) # Disable warning C4244 "'conversion' conversion from 'type1' to 'type2', possible loss of data".
|
||||
try_append_c_flags(/wd4267) # Disable warning C4267 "'var' : conversion from 'size_t' to 'type', possible loss of data".
|
||||
else()
|
||||
# Keep the following commands ordered lexicographically.
|
||||
try_append_c_flags(-pedantic)
|
||||
try_append_c_flags(-Wcast-align) # GCC >= 2.95.
|
||||
try_append_c_flags(-Wcast-align=strict) # GCC >= 8.0.
|
||||
try_append_c_flags(-Wconditional-uninitialized) # Clang >= 3.0 only.
|
||||
try_append_c_flags(-Wextra) # GCC >= 3.4, this is the newer name of -W, which we don't use because older GCCs will warn about unused functions.
|
||||
try_append_c_flags(-Wleading-whitespace=spaces) # GCC >= 15.0
|
||||
try_append_c_flags(-Wnested-externs)
|
||||
try_append_c_flags(-Wno-long-long) # GCC >= 3.0, -Wlong-long is implied by -pedantic.
|
||||
try_append_c_flags(-Wno-overlength-strings) # GCC >= 4.2, -Woverlength-strings is implied by -pedantic.
|
||||
try_append_c_flags(-Wno-unused-function) # GCC >= 3.0, -Wunused-function is implied by -Wall.
|
||||
try_append_c_flags(-Wreserved-identifier) # Clang >= 13.0 only.
|
||||
try_append_c_flags(-Wshadow)
|
||||
try_append_c_flags(-Wstrict-prototypes)
|
||||
try_append_c_flags(-Wtrailing-whitespace=any) # GCC >= 15.0
|
||||
try_append_c_flags(-Wundef)
|
||||
endif()
|
||||
|
||||
set(print_msan_notice)
|
||||
if(SECP256K1_BUILD_CTIME_TESTS)
|
||||
include(CheckMemorySanitizer)
|
||||
check_memory_sanitizer(msan_enabled)
|
||||
if(msan_enabled)
|
||||
try_append_c_flags(-fno-sanitize-memory-param-retval)
|
||||
set(print_msan_notice YES)
|
||||
endif()
|
||||
unset(msan_enabled)
|
||||
endif()
|
||||
|
||||
set(SECP256K1_APPEND_CFLAGS "" CACHE STRING "Compiler flags that are appended to the command line after all other flags added by the build system. This variable is intended for debugging and special builds.")
|
||||
if(SECP256K1_APPEND_CFLAGS)
|
||||
# Appending to this low-level rule variable is the only way to
|
||||
# guarantee that the flags appear at the end of the command line.
|
||||
string(APPEND CMAKE_C_COMPILE_OBJECT " ${SECP256K1_APPEND_CFLAGS}")
|
||||
endif()
|
||||
|
||||
set(SECP256K1_APPEND_LDFLAGS "" CACHE STRING "Linker flags that are appended to the command line after all other flags added by the build system. This variable is intended for debugging and special builds.")
|
||||
if(SECP256K1_APPEND_LDFLAGS)
|
||||
# Appending to this low-level rule variable is the only way to
|
||||
# guarantee that the flags appear at the end of the command line.
|
||||
string(APPEND CMAKE_C_CREATE_SHARED_LIBRARY " ${SECP256K1_APPEND_LDFLAGS}")
|
||||
string(APPEND CMAKE_C_LINK_EXECUTABLE " ${SECP256K1_APPEND_LDFLAGS}")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
|
||||
endif()
|
||||
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
|
||||
endif()
|
||||
if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
|
||||
endif()
|
||||
add_subdirectory(src)
|
||||
if(SECP256K1_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
message("\n")
|
||||
message("secp256k1 configure summary")
|
||||
message("===========================")
|
||||
message("Build artifacts:")
|
||||
if(BUILD_SHARED_LIBS)
|
||||
set(library_type "Shared")
|
||||
else()
|
||||
set(library_type "Static")
|
||||
endif()
|
||||
|
||||
message(" library type ........................ ${library_type}")
|
||||
message("Optional modules:")
|
||||
message(" ECDH ................................ ${SECP256K1_ENABLE_MODULE_ECDH}")
|
||||
message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOVERY}")
|
||||
message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}")
|
||||
message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}")
|
||||
message(" musig ............................... ${SECP256K1_ENABLE_MODULE_MUSIG}")
|
||||
message(" frost ............................... ${SECP256K1_ENABLE_MODULE_FROST}")
|
||||
message(" chilldkg ............................ ${SECP256K1_ENABLE_MODULE_CHILLDKG}")
|
||||
message(" iceberg ............................. ${SECP256K1_ENABLE_MODULE_ICEBERG}")
|
||||
message(" prefractal .......................... ${SECP256K1_ENABLE_MODULE_PREFRACTAL}")
|
||||
message(" frost-enrollment .................... ${SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT}")
|
||||
message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}")
|
||||
message(" generator ........................... ${SECP256K1_ENABLE_MODULE_GENERATOR}")
|
||||
message(" rangeproof .......................... ${SECP256K1_ENABLE_MODULE_RANGEPROOF}")
|
||||
message(" surjectionproof ..................... ${SECP256K1_ENABLE_MODULE_SURJECTIONPROOF}")
|
||||
message(" whitelist ........................... ${SECP256K1_ENABLE_MODULE_WHITELIST}")
|
||||
message(" ecdsa-s2c ........................... ${SECP256K1_ENABLE_MODULE_ECDSA_S2C}")
|
||||
message(" ecdsa-adaptor ....................... ${SECP256K1_ENABLE_MODULE_ECDSA_ADAPTOR}")
|
||||
message(" bppp ................................ ${SECP256K1_ENABLE_MODULE_BPPP}")
|
||||
message(" schnorrsig-halfagg .................. ${SECP256K1_ENABLE_MODULE_SCHNORRSIG_HALFAGG}")
|
||||
message("Parameters:")
|
||||
message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}")
|
||||
message(" ecmult gen table size ............... ${SECP256K1_ECMULT_GEN_KB} KiB")
|
||||
message("Optional features:")
|
||||
message(" assembly ............................ ${SECP256K1_ASM}")
|
||||
message(" external callbacks .................. ${SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS}")
|
||||
if(SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY)
|
||||
message(" wide multiplication (test-only) ..... ${SECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY}")
|
||||
endif()
|
||||
message("Optional binaries:")
|
||||
message(" benchmark ........................... ${SECP256K1_BUILD_BENCHMARK}")
|
||||
message(" noverify_tests ...................... ${SECP256K1_BUILD_TESTS}")
|
||||
set(tests_status "${SECP256K1_BUILD_TESTS}")
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
|
||||
set(tests_status OFF)
|
||||
endif()
|
||||
message(" tests ............................... ${tests_status}")
|
||||
message(" exhaustive tests .................... ${SECP256K1_BUILD_EXHAUSTIVE_TESTS}")
|
||||
message(" ctime_tests ......................... ${SECP256K1_BUILD_CTIME_TESTS}")
|
||||
message(" examples ............................ ${SECP256K1_BUILD_EXAMPLES}")
|
||||
message("")
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
set(cross_status "TRUE, for ${CMAKE_SYSTEM_NAME}, ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
else()
|
||||
set(cross_status "FALSE")
|
||||
endif()
|
||||
message("Cross compiling ....................... ${cross_status}")
|
||||
message("API visibility attributes ............. ${SECP256K1_ENABLE_API_VISIBILITY_ATTRIBUTES}")
|
||||
message("Valgrind .............................. ${SECP256K1_VALGRIND}")
|
||||
get_directory_property(definitions COMPILE_DEFINITIONS)
|
||||
string(REPLACE ";" " " definitions "${definitions}")
|
||||
message("Preprocessor defined macros ........... ${definitions}")
|
||||
message("C compiler ............................ ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}, ${CMAKE_C_COMPILER}")
|
||||
message("CFLAGS ................................ ${CMAKE_C_FLAGS}")
|
||||
get_directory_property(compile_options COMPILE_OPTIONS)
|
||||
string(REPLACE ";" " " compile_options "${compile_options}")
|
||||
message("Compile options ....................... " ${compile_options})
|
||||
if(NOT is_multi_config)
|
||||
message("Build type:")
|
||||
message(" - CMAKE_BUILD_TYPE ................... ${CMAKE_BUILD_TYPE}")
|
||||
string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
|
||||
message(" - CFLAGS ............................. ${CMAKE_C_FLAGS_${build_type}}")
|
||||
message(" - LDFLAGS for executables ............ ${CMAKE_EXE_LINKER_FLAGS_${build_type}}")
|
||||
message(" - LDFLAGS for shared libraries ....... ${CMAKE_SHARED_LINKER_FLAGS_${build_type}}")
|
||||
else()
|
||||
message("Supported configurations .............. ${CMAKE_CONFIGURATION_TYPES}")
|
||||
message("RelWithDebInfo configuration:")
|
||||
message(" - CFLAGS ............................. ${CMAKE_C_FLAGS_RELWITHDEBINFO}")
|
||||
message(" - LDFLAGS for executables ............ ${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}")
|
||||
message(" - LDFLAGS for shared libraries ....... ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}")
|
||||
message("Debug configuration:")
|
||||
message(" - CFLAGS ............................. ${CMAKE_C_FLAGS_DEBUG}")
|
||||
message(" - LDFLAGS for executables ............ ${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
|
||||
message(" - LDFLAGS for shared libraries ....... ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}")
|
||||
endif()
|
||||
if(SECP256K1_APPEND_CFLAGS)
|
||||
message("SECP256K1_APPEND_CFLAGS ............... ${SECP256K1_APPEND_CFLAGS}")
|
||||
endif()
|
||||
if(SECP256K1_APPEND_LDFLAGS)
|
||||
message("SECP256K1_APPEND_LDFLAGS .............. ${SECP256K1_APPEND_LDFLAGS}")
|
||||
endif()
|
||||
message("")
|
||||
if(print_msan_notice)
|
||||
message(
|
||||
"Note:\n"
|
||||
" MemorySanitizer detected, tried to add -fno-sanitize-memory-param-retval to compile options\n"
|
||||
" to avoid false positives in ctime_tests. Pass -DSECP256K1_BUILD_CTIME_TESTS=OFF to avoid this.\n"
|
||||
)
|
||||
endif()
|
||||
if(SECP256K1_EXPERIMENTAL)
|
||||
message(
|
||||
" ******\n"
|
||||
" WARNING: experimental build\n"
|
||||
" Experimental features do not have stable APIs or properties, and may not be safe for production use.\n"
|
||||
" ******\n"
|
||||
)
|
||||
endif()
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "dev-mode",
|
||||
"displayName": "Development mode (intended only for developers of the library)",
|
||||
"cacheVariables": {
|
||||
"SECP256K1_EXPERIMENTAL": "ON",
|
||||
"SECP256K1_ENABLE_MODULE_RECOVERY": "ON",
|
||||
"SECP256K1_BUILD_EXAMPLES": "ON"
|
||||
},
|
||||
"warnings": {
|
||||
"dev": true,
|
||||
"uninitialized": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
111
CONTRIBUTING.md
111
CONTRIBUTING.md
@@ -1,111 +0,0 @@
|
||||
# Contributing to libsecp256k1
|
||||
|
||||
## Scope
|
||||
|
||||
libsecp256k1 is a library for elliptic curve cryptography on the curve secp256k1, not a general-purpose cryptography library.
|
||||
The library primarily serves the needs of the Bitcoin Core project but provides additional functionality for the benefit of the wider Bitcoin ecosystem.
|
||||
|
||||
## Adding new functionality or modules
|
||||
|
||||
The libsecp256k1 project welcomes contributions in the form of new functionality or modules, provided they are within the project's scope.
|
||||
|
||||
It is the responsibility of the contributors to convince the maintainers that the proposed functionality is within the project's scope, high-quality and maintainable.
|
||||
Contributors are recommended to provide the following in addition to the new code:
|
||||
|
||||
* **Specification:**
|
||||
A specification can help significantly in reviewing the new code as it provides documentation and context.
|
||||
It may justify various design decisions, give a motivation and outline security goals.
|
||||
If the specification contains pseudocode, a reference implementation or test vectors, these can be used to compare with the proposed libsecp256k1 code.
|
||||
* **Security Arguments:**
|
||||
In addition to a defining the security goals, it should be argued that the new functionality meets these goals.
|
||||
Depending on the nature of the new functionality, a wide range of security arguments are acceptable, ranging from being "obviously secure" to rigorous proofs of security.
|
||||
* **Relevance Arguments:**
|
||||
The relevance of the new functionality for the Bitcoin ecosystem should be argued by outlining clear use cases.
|
||||
|
||||
These are not the only factors taken into account when considering to add new functionality.
|
||||
The proposed new libsecp256k1 code must be of high quality, including API documentation and tests, as well as featuring a misuse-resistant API design.
|
||||
|
||||
We recommend reaching out to other contributors (see [Communication Channels](#communication-channels)) and get feedback before implementing new functionality.
|
||||
|
||||
## Communication channels
|
||||
|
||||
Most communication about libsecp256k1 occurs on the GitHub repository: in issues, pull request or on the discussion board.
|
||||
|
||||
Additionally, there is an IRC channel dedicated to libsecp256k1, with biweekly meetings (see channel topic).
|
||||
The channel is `#secp256k1` on Libera Chat.
|
||||
The easiest way to participate on IRC is with the web client, [web.libera.chat](https://web.libera.chat/#secp256k1).
|
||||
Chat history logs can be found at https://gnusha.org/secp256k1/.
|
||||
|
||||
## Contributor workflow & peer review
|
||||
|
||||
The Contributor Workflow & Peer Review in libsecp256k1 are similar to Bitcoin Core's workflow and review processes described in its [CONTRIBUTING.md](https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md).
|
||||
|
||||
### Coding conventions
|
||||
|
||||
In addition, libsecp256k1 tries to maintain the following coding conventions:
|
||||
|
||||
* No runtime heap allocation (e.g., no `malloc`) unless explicitly requested by the caller (via `secp256k1_context_create` or `secp256k1_scratch_space_create`, for example). Moreover, it should be possible to use the library without any heap allocations.
|
||||
* The tests should cover all lines and branches of the library (see [Test coverage](#coverage)).
|
||||
* Operations involving secret data should be tested for being constant time with respect to the secrets (see [src/ctime_tests.c](src/ctime_tests.c)).
|
||||
* Local variables containing secret data should be cleared explicitly to try to delete secrets from memory.
|
||||
* Use `secp256k1_memcmp_var` instead of `memcmp` (see [#823](https://github.com/bitcoin-core/secp256k1/issues/823)).
|
||||
* As a rule of thumb, the default values for configuration options should target standard desktop machines and align with Bitcoin Core's defaults, and the tests should mostly exercise the default configuration (see [#1549](https://github.com/bitcoin-core/secp256k1/issues/1549#issuecomment-2200559257)).
|
||||
|
||||
#### Style conventions
|
||||
|
||||
* Commits should be atomic and diffs should be easy to read. For this reason, do not mix any formatting fixes or code moves with actual code changes. Make sure each individual commit is hygienic: that it builds successfully on its own without warnings, errors, regressions, or test failures.
|
||||
* New code should adhere to the style of existing, in particular surrounding, code. Other than that, we do not enforce strict rules for code formatting.
|
||||
* The code conforms to C89. Most notably, that means that only `/* ... */` comments are allowed (no `//` line comments). Moreover, any declarations in a `{ ... }` block (e.g., a function) must appear at the beginning of the block before any statements. When you would like to declare a variable in the middle of a block, you can open a new block:
|
||||
```C
|
||||
void secp256k_foo(void) {
|
||||
unsigned int x; /* declaration */
|
||||
int y = 2*x; /* declaration */
|
||||
x = 17; /* statement */
|
||||
{
|
||||
int a, b; /* declaration */
|
||||
a = x + y; /* statement */
|
||||
secp256k_bar(x, &b); /* statement */
|
||||
}
|
||||
}
|
||||
```
|
||||
* Use `unsigned int` instead of just `unsigned`.
|
||||
* Use `void *ptr` instead of `void* ptr`.
|
||||
* Arguments of the publicly-facing API must have a specific order defined in [include/secp256k1.h](include/secp256k1.h).
|
||||
* User-facing comment lines in headers should be limited to 80 chars if possible.
|
||||
* All identifiers in file scope should start with `secp256k1_`.
|
||||
* Avoid trailing whitespace.
|
||||
* Use the constants `EXIT_SUCCESS`/`EXIT_FAILURE` (defined in `stdlib.h`) to indicate program execution status for examples and other binaries.
|
||||
|
||||
### Tests
|
||||
|
||||
#### Coverage
|
||||
|
||||
This library aims to have full coverage of reachable lines and branches.
|
||||
|
||||
To create a test coverage report, configure with `--enable-coverage` (use of GCC is necessary):
|
||||
|
||||
$ ./configure --enable-coverage
|
||||
|
||||
Run the tests:
|
||||
|
||||
$ make check
|
||||
|
||||
To create a report, `gcovr` is recommended, as it includes branch coverage reporting:
|
||||
|
||||
$ gcovr --gcov-ignore-parse-errors=all --merge-mode-functions=separate --exclude 'src/bench*' --exclude 'src/modules/.*/bench_impl.h' --print-summary
|
||||
|
||||
To create a HTML report with coloured and annotated source code:
|
||||
|
||||
$ mkdir -p coverage
|
||||
$ gcovr --gcov-ignore-parse-errors=all --merge-mode-functions=separate --exclude 'src/bench*' --exclude 'src/modules/.*/bench_impl.h' --html --html-details -o coverage/coverage.html
|
||||
|
||||
On `gcovr` >=8.3, `--gcov-ignore-parse-errors=all` can be replaced with `--gcov-suspicious-hits-threshold=140737488355330`.
|
||||
|
||||
#### Exhaustive tests
|
||||
|
||||
There are tests of several functions in which a small group replaces secp256k1.
|
||||
These tests are *exhaustive* since they provide all elements and scalars of the small group as input arguments (see [src/tests_exhaustive.c](src/tests_exhaustive.c)).
|
||||
|
||||
### Benchmarks
|
||||
|
||||
See `src/bench*.c` for examples of benchmarks.
|
||||
393
Makefile.am
393
Makefile.am
@@ -1,10 +1,12 @@
|
||||
ACLOCAL_AMFLAGS = -I autotools-aux/m4
|
||||
|
||||
# AM_CFLAGS will be automatically prepended to CFLAGS by Automake when compiling some foo
|
||||
# which does not have an explicit foo_CFLAGS variable set.
|
||||
AM_CFLAGS = $(SECP_CFLAGS)
|
||||
ACLOCAL_AMFLAGS = -I build-aux/m4
|
||||
|
||||
lib_LTLIBRARIES = libsecp256k1.la
|
||||
if USE_JNI
|
||||
JNI_LIB = libsecp256k1_jni.la
|
||||
noinst_LTLIBRARIES = $(JNI_LIB)
|
||||
else
|
||||
JNI_LIB =
|
||||
endif
|
||||
include_HEADERS = include/secp256k1.h
|
||||
include_HEADERS += include/secp256k1_preallocated.h
|
||||
noinst_HEADERS =
|
||||
@@ -18,50 +20,31 @@ noinst_HEADERS += src/scalar_8x32_impl.h
|
||||
noinst_HEADERS += src/scalar_low_impl.h
|
||||
noinst_HEADERS += src/group.h
|
||||
noinst_HEADERS += src/group_impl.h
|
||||
noinst_HEADERS += src/eccommit.h
|
||||
noinst_HEADERS += src/eccommit_impl.h
|
||||
noinst_HEADERS += src/num_gmp.h
|
||||
noinst_HEADERS += src/num_gmp_impl.h
|
||||
noinst_HEADERS += src/ecdsa.h
|
||||
noinst_HEADERS += src/ecdsa_impl.h
|
||||
noinst_HEADERS += src/eckey.h
|
||||
noinst_HEADERS += src/eckey_impl.h
|
||||
noinst_HEADERS += src/ecmult.h
|
||||
noinst_HEADERS += src/ecmult_impl.h
|
||||
noinst_HEADERS += src/ecmult_compute_table.h
|
||||
noinst_HEADERS += src/ecmult_compute_table_impl.h
|
||||
noinst_HEADERS += src/ecmult_const.h
|
||||
noinst_HEADERS += src/ecmult_const_impl.h
|
||||
noinst_HEADERS += src/ecmult_gen.h
|
||||
noinst_HEADERS += src/ecmult_gen_impl.h
|
||||
noinst_HEADERS += src/ecmult_gen_compute_table.h
|
||||
noinst_HEADERS += src/ecmult_gen_compute_table_impl.h
|
||||
noinst_HEADERS += src/num.h
|
||||
noinst_HEADERS += src/num_impl.h
|
||||
noinst_HEADERS += src/field_10x26.h
|
||||
noinst_HEADERS += src/field_10x26_impl.h
|
||||
noinst_HEADERS += src/field_5x52.h
|
||||
noinst_HEADERS += src/field_5x52_impl.h
|
||||
noinst_HEADERS += src/field_5x52_int128_impl.h
|
||||
noinst_HEADERS += src/modinv32.h
|
||||
noinst_HEADERS += src/modinv32_impl.h
|
||||
noinst_HEADERS += src/modinv64.h
|
||||
noinst_HEADERS += src/modinv64_impl.h
|
||||
noinst_HEADERS += src/precomputed_ecmult.h
|
||||
noinst_HEADERS += src/precomputed_ecmult_gen.h
|
||||
noinst_HEADERS += src/assumptions.h
|
||||
noinst_HEADERS += src/checkmem.h
|
||||
noinst_HEADERS += src/tests_common.h
|
||||
noinst_HEADERS += src/testutil.h
|
||||
noinst_HEADERS += src/unit_test.h
|
||||
noinst_HEADERS += src/unit_test.c
|
||||
noinst_HEADERS += src/field_5x52_asm_impl.h
|
||||
noinst_HEADERS += src/java/org_bitcoin_NativeSecp256k1.h
|
||||
noinst_HEADERS += src/java/org_bitcoin_Secp256k1Context.h
|
||||
noinst_HEADERS += src/util.h
|
||||
noinst_HEADERS += src/util_local_visibility.h
|
||||
noinst_HEADERS += src/int128.h
|
||||
noinst_HEADERS += src/int128_impl.h
|
||||
noinst_HEADERS += src/int128_native.h
|
||||
noinst_HEADERS += src/int128_native_impl.h
|
||||
noinst_HEADERS += src/int128_struct.h
|
||||
noinst_HEADERS += src/int128_struct_impl.h
|
||||
noinst_HEADERS += src/scratch.h
|
||||
noinst_HEADERS += src/scratch_impl.h
|
||||
noinst_HEADERS += src/selftest.h
|
||||
noinst_HEADERS += src/testrand.h
|
||||
noinst_HEADERS += src/testrand_impl.h
|
||||
noinst_HEADERS += src/hash.h
|
||||
@@ -69,28 +52,17 @@ noinst_HEADERS += src/hash_impl.h
|
||||
noinst_HEADERS += src/field.h
|
||||
noinst_HEADERS += src/field_impl.h
|
||||
noinst_HEADERS += src/bench.h
|
||||
noinst_HEADERS += src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h
|
||||
noinst_HEADERS += src/hsort.h
|
||||
noinst_HEADERS += src/hsort_impl.h
|
||||
noinst_HEADERS += contrib/lax_der_parsing.h
|
||||
noinst_HEADERS += contrib/lax_der_parsing.c
|
||||
noinst_HEADERS += contrib/lax_der_privatekey_parsing.h
|
||||
noinst_HEADERS += contrib/lax_der_privatekey_parsing.c
|
||||
noinst_HEADERS += examples/examples_util.h
|
||||
|
||||
PRECOMPUTED_LIB = libsecp256k1_precomputed.la
|
||||
noinst_LTLIBRARIES = $(PRECOMPUTED_LIB)
|
||||
libsecp256k1_precomputed_la_SOURCES = src/precomputed_ecmult.c src/precomputed_ecmult_gen.c
|
||||
# We need `-I$(top_srcdir)/src` in VPATH builds if libsecp256k1_precomputed_la_SOURCES have been recreated in the build tree.
|
||||
# This helps users and packagers who insist on recreating the precomputed files (e.g., Gentoo).
|
||||
libsecp256k1_precomputed_la_CPPFLAGS = -I$(top_srcdir)/src $(SECP_CONFIG_DEFINES)
|
||||
|
||||
if USE_EXTERNAL_ASM
|
||||
COMMON_LIB = libsecp256k1_common.la
|
||||
noinst_LTLIBRARIES = $(COMMON_LIB)
|
||||
else
|
||||
COMMON_LIB =
|
||||
endif
|
||||
noinst_LTLIBRARIES += $(COMMON_LIB)
|
||||
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
pkgconfig_DATA = libsecp256k1.pc
|
||||
@@ -102,261 +74,118 @@ endif
|
||||
endif
|
||||
|
||||
libsecp256k1_la_SOURCES = src/secp256k1.c
|
||||
libsecp256k1_la_CPPFLAGS = $(SECP_CONFIG_DEFINES)
|
||||
libsecp256k1_la_LIBADD = $(COMMON_LIB) $(PRECOMPUTED_LIB)
|
||||
libsecp256k1_la_LDFLAGS = -no-undefined -version-info $(LIB_VERSION_CURRENT):$(LIB_VERSION_REVISION):$(LIB_VERSION_AGE)
|
||||
libsecp256k1_la_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/include -I$(top_srcdir)/src $(SECP_INCLUDES)
|
||||
libsecp256k1_la_LIBADD = $(JNI_LIB) $(SECP_LIBS) $(COMMON_LIB)
|
||||
|
||||
libsecp256k1_jni_la_SOURCES = src/java/org_bitcoin_NativeSecp256k1.c src/java/org_bitcoin_Secp256k1Context.c
|
||||
libsecp256k1_jni_la_CPPFLAGS = -DSECP256K1_BUILD $(JNI_INCLUDES)
|
||||
|
||||
noinst_PROGRAMS =
|
||||
if USE_BENCHMARK
|
||||
noinst_PROGRAMS += bench bench_internal bench_ecmult
|
||||
bench_SOURCES = src/bench.c
|
||||
bench_LDADD = libsecp256k1.la
|
||||
bench_CPPFLAGS = $(SECP_CONFIG_DEFINES)
|
||||
noinst_PROGRAMS += bench_verify bench_sign bench_internal bench_ecmult
|
||||
bench_verify_SOURCES = src/bench_verify.c
|
||||
bench_verify_LDADD = libsecp256k1.la $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB)
|
||||
bench_sign_SOURCES = src/bench_sign.c
|
||||
bench_sign_LDADD = libsecp256k1.la $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB)
|
||||
bench_internal_SOURCES = src/bench_internal.c
|
||||
bench_internal_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB)
|
||||
bench_internal_CPPFLAGS = $(SECP_CONFIG_DEFINES)
|
||||
bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB)
|
||||
bench_internal_CPPFLAGS = -DSECP256K1_BUILD $(SECP_INCLUDES)
|
||||
bench_ecmult_SOURCES = src/bench_ecmult.c
|
||||
bench_ecmult_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB)
|
||||
bench_ecmult_CPPFLAGS = $(SECP_CONFIG_DEFINES)
|
||||
if ENABLE_MODULE_ICEBERG
|
||||
noinst_PROGRAMS += bench_iceberg
|
||||
bench_iceberg_SOURCES = src/bench_iceberg.c
|
||||
bench_iceberg_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB)
|
||||
bench_iceberg_CPPFLAGS = $(SECP_CONFIG_DEFINES)
|
||||
endif
|
||||
bench_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB)
|
||||
bench_ecmult_CPPFLAGS = -DSECP256K1_BUILD $(SECP_INCLUDES)
|
||||
endif
|
||||
|
||||
TESTS =
|
||||
if USE_TESTS
|
||||
TESTS += noverify_tests
|
||||
noinst_PROGRAMS += noverify_tests
|
||||
noverify_tests_SOURCES = src/tests.c
|
||||
noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) $(TEST_DEFINES)
|
||||
noverify_tests_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB)
|
||||
noverify_tests_LDFLAGS = -static
|
||||
if !ENABLE_COVERAGE
|
||||
TESTS += tests
|
||||
noinst_PROGRAMS += tests
|
||||
tests_SOURCES = $(noverify_tests_SOURCES)
|
||||
tests_CPPFLAGS = $(noverify_tests_CPPFLAGS) -DVERIFY
|
||||
tests_LDADD = $(noverify_tests_LDADD)
|
||||
tests_LDFLAGS = $(noverify_tests_LDFLAGS)
|
||||
tests_SOURCES = src/tests.c
|
||||
tests_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/src -I$(top_srcdir)/include $(SECP_INCLUDES) $(SECP_TEST_INCLUDES)
|
||||
if !ENABLE_COVERAGE
|
||||
tests_CPPFLAGS += -DVERIFY
|
||||
endif
|
||||
endif
|
||||
|
||||
if USE_CTIME_TESTS
|
||||
noinst_PROGRAMS += ctime_tests
|
||||
ctime_tests_SOURCES = src/ctime_tests.c
|
||||
ctime_tests_LDADD = libsecp256k1.la
|
||||
ctime_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES)
|
||||
tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB)
|
||||
tests_LDFLAGS = -static
|
||||
TESTS += tests
|
||||
endif
|
||||
|
||||
if USE_EXHAUSTIVE_TESTS
|
||||
noinst_PROGRAMS += exhaustive_tests
|
||||
exhaustive_tests_SOURCES = src/tests_exhaustive.c
|
||||
exhaustive_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES)
|
||||
exhaustive_tests_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/src $(SECP_INCLUDES)
|
||||
if !ENABLE_COVERAGE
|
||||
exhaustive_tests_CPPFLAGS += -DVERIFY
|
||||
endif
|
||||
# Note: do not include $(PRECOMPUTED_LIB) in exhaustive_tests (it uses runtime-generated tables).
|
||||
exhaustive_tests_LDADD = $(COMMON_LIB)
|
||||
exhaustive_tests_LDADD = $(SECP_LIBS) $(COMMON_LIB)
|
||||
exhaustive_tests_LDFLAGS = -static
|
||||
TESTS += exhaustive_tests
|
||||
endif
|
||||
|
||||
if USE_EXAMPLES
|
||||
noinst_PROGRAMS += ecdsa_example
|
||||
ecdsa_example_SOURCES = examples/ecdsa.c
|
||||
ecdsa_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
ecdsa_example_LDADD = libsecp256k1.la
|
||||
ecdsa_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
ecdsa_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += ecdsa_example
|
||||
if ENABLE_MODULE_ECDH
|
||||
noinst_PROGRAMS += ecdh_example
|
||||
ecdh_example_SOURCES = examples/ecdh.c
|
||||
ecdh_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
ecdh_example_LDADD = libsecp256k1.la
|
||||
ecdh_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
ecdh_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += ecdh_example
|
||||
endif
|
||||
if ENABLE_MODULE_SCHNORRSIG
|
||||
noinst_PROGRAMS += schnorr_example
|
||||
schnorr_example_SOURCES = examples/schnorr.c
|
||||
schnorr_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
schnorr_example_LDADD = libsecp256k1.la
|
||||
schnorr_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
schnorr_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += schnorr_example
|
||||
endif
|
||||
if ENABLE_MODULE_ELLSWIFT
|
||||
noinst_PROGRAMS += ellswift_example
|
||||
ellswift_example_SOURCES = examples/ellswift.c
|
||||
ellswift_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
ellswift_example_LDADD = libsecp256k1.la
|
||||
ellswift_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
ellswift_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += ellswift_example
|
||||
endif
|
||||
if ENABLE_MODULE_MUSIG
|
||||
noinst_PROGRAMS += musig_example
|
||||
musig_example_SOURCES = examples/musig.c
|
||||
musig_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
musig_example_LDADD = libsecp256k1.la
|
||||
musig_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
musig_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += musig_example
|
||||
endif
|
||||
if ENABLE_MODULE_FROST
|
||||
noinst_PROGRAMS += frost_example
|
||||
frost_example_SOURCES = examples/frost.c
|
||||
frost_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
frost_example_LDADD = libsecp256k1.la
|
||||
frost_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
frost_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += frost_example
|
||||
endif
|
||||
if ENABLE_MODULE_FROST_ENROLLMENT
|
||||
noinst_PROGRAMS += frost_enrollment_example
|
||||
frost_enrollment_example_SOURCES = examples/frost_enrollment.c
|
||||
frost_enrollment_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
frost_enrollment_example_LDADD = libsecp256k1.la
|
||||
frost_enrollment_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
frost_enrollment_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += frost_enrollment_example
|
||||
endif
|
||||
if ENABLE_MODULE_CHILLDKG
|
||||
noinst_PROGRAMS += chilldkg_example
|
||||
chilldkg_example_SOURCES = examples/chilldkg.c
|
||||
chilldkg_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
chilldkg_example_LDADD = libsecp256k1.la
|
||||
chilldkg_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
chilldkg_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += chilldkg_example
|
||||
endif
|
||||
if ENABLE_MODULE_ICEBERG
|
||||
noinst_PROGRAMS += iceberg_example
|
||||
iceberg_example_SOURCES = examples/iceberg.c
|
||||
iceberg_example_CPPFLAGS = -I$(top_srcdir)/include -DSECP256K1_STATIC
|
||||
iceberg_example_LDADD = libsecp256k1.la
|
||||
iceberg_example_LDFLAGS = -static
|
||||
if BUILD_WINDOWS
|
||||
iceberg_example_LDFLAGS += -lbcrypt
|
||||
endif
|
||||
TESTS += iceberg_example
|
||||
JAVAROOT=src/java
|
||||
JAVAORG=org/bitcoin
|
||||
JAVA_GUAVA=$(srcdir)/$(JAVAROOT)/guava/guava-18.0.jar
|
||||
CLASSPATH_ENV=CLASSPATH=$(JAVA_GUAVA)
|
||||
JAVA_FILES= \
|
||||
$(JAVAROOT)/$(JAVAORG)/NativeSecp256k1.java \
|
||||
$(JAVAROOT)/$(JAVAORG)/NativeSecp256k1Test.java \
|
||||
$(JAVAROOT)/$(JAVAORG)/NativeSecp256k1Util.java \
|
||||
$(JAVAROOT)/$(JAVAORG)/Secp256k1Context.java
|
||||
|
||||
if USE_JNI
|
||||
|
||||
$(JAVA_GUAVA):
|
||||
@echo Guava is missing. Fetch it via: \
|
||||
wget https://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar -O $(@)
|
||||
@false
|
||||
|
||||
.stamp-java: $(JAVA_FILES)
|
||||
@echo Compiling $^
|
||||
$(AM_V_at)$(CLASSPATH_ENV) javac $^
|
||||
@touch $@
|
||||
|
||||
if USE_TESTS
|
||||
|
||||
check-java: libsecp256k1.la $(JAVA_GUAVA) .stamp-java
|
||||
$(AM_V_at)java -Djava.library.path="./:./src:./src/.libs:.libs/" -cp "$(JAVA_GUAVA):$(JAVAROOT)" $(JAVAORG)/NativeSecp256k1Test
|
||||
|
||||
endif
|
||||
endif
|
||||
|
||||
### Precomputed tables
|
||||
EXTRA_PROGRAMS = precompute_ecmult precompute_ecmult_gen
|
||||
CLEANFILES = $(EXTRA_PROGRAMS)
|
||||
if USE_ECMULT_STATIC_PRECOMPUTATION
|
||||
CPPFLAGS_FOR_BUILD +=-I$(top_srcdir)
|
||||
|
||||
precompute_ecmult_SOURCES = src/precompute_ecmult.c
|
||||
precompute_ecmult_CPPFLAGS = $(SECP_CONFIG_DEFINES) -DVERIFY
|
||||
precompute_ecmult_LDADD = $(COMMON_LIB)
|
||||
gen_context_OBJECTS = gen_context.o
|
||||
gen_context_BIN = gen_context$(BUILD_EXEEXT)
|
||||
gen_%.o: src/gen_%.c
|
||||
$(CC_FOR_BUILD) $(CPPFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD) -c $< -o $@
|
||||
|
||||
precompute_ecmult_gen_SOURCES = src/precompute_ecmult_gen.c
|
||||
precompute_ecmult_gen_CPPFLAGS = $(SECP_CONFIG_DEFINES) -DVERIFY
|
||||
precompute_ecmult_gen_LDADD = $(COMMON_LIB)
|
||||
$(gen_context_BIN): $(gen_context_OBJECTS)
|
||||
$(CC_FOR_BUILD) $(CFLAGS_FOR_BUILD) $(LDFLAGS_FOR_BUILD) $^ -o $@
|
||||
|
||||
# See Automake manual, Section "Errors with distclean".
|
||||
# We don't list any dependencies for the prebuilt files here because
|
||||
# otherwise make's decision whether to rebuild them (even in the first
|
||||
# build by a normal user) depends on mtimes, and thus is very fragile.
|
||||
# This means that rebuilds of the prebuilt files always need to be
|
||||
# forced by deleting them.
|
||||
src/precomputed_ecmult.c:
|
||||
$(MAKE) $(AM_MAKEFLAGS) precompute_ecmult$(EXEEXT)
|
||||
./precompute_ecmult$(EXEEXT)
|
||||
src/precomputed_ecmult_gen.c:
|
||||
$(MAKE) $(AM_MAKEFLAGS) precompute_ecmult_gen$(EXEEXT)
|
||||
./precompute_ecmult_gen$(EXEEXT)
|
||||
$(libsecp256k1_la_OBJECTS): src/ecmult_static_context.h
|
||||
$(tests_OBJECTS): src/ecmult_static_context.h
|
||||
$(bench_internal_OBJECTS): src/ecmult_static_context.h
|
||||
$(bench_ecmult_OBJECTS): src/ecmult_static_context.h
|
||||
|
||||
PRECOMP = src/precomputed_ecmult_gen.c src/precomputed_ecmult.c
|
||||
precomp: $(PRECOMP)
|
||||
src/ecmult_static_context.h: $(gen_context_BIN)
|
||||
./$(gen_context_BIN)
|
||||
|
||||
# Ensure the prebuilt files will be build first (only if they don't exist,
|
||||
# e.g., after `make maintainer-clean`).
|
||||
BUILT_SOURCES = $(PRECOMP)
|
||||
|
||||
.PHONY: clean-precomp
|
||||
clean-precomp:
|
||||
rm -f $(PRECOMP)
|
||||
maintainer-clean-local: clean-precomp
|
||||
|
||||
### Pregenerated test vectors
|
||||
### (see the comments in the previous section for detailed rationale)
|
||||
TESTVECTORS = src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h
|
||||
|
||||
if ENABLE_MODULE_ECDH
|
||||
TESTVECTORS += src/wycheproof/ecdh_secp256k1_test.h
|
||||
CLEANFILES = $(gen_context_BIN) src/ecmult_static_context.h $(JAVAROOT)/$(JAVAORG)/*.class .stamp-java
|
||||
endif
|
||||
|
||||
src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.h:
|
||||
mkdir -p $(@D)
|
||||
python3 $(top_srcdir)/tools/tests_wycheproof_generate_ecdsa.py $(top_srcdir)/src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.json > $@
|
||||
|
||||
src/wycheproof/ecdh_secp256k1_test.h:
|
||||
mkdir -p $(@D)
|
||||
python3 $(top_srcdir)/tools/tests_wycheproof_generate_ecdh.py $(top_srcdir)/src/wycheproof/ecdh_secp256k1_test.json > $@
|
||||
|
||||
testvectors: $(TESTVECTORS)
|
||||
|
||||
BUILT_SOURCES += $(TESTVECTORS)
|
||||
|
||||
.PHONY: clean-testvectors
|
||||
clean-testvectors:
|
||||
rm -f $(TESTVECTORS)
|
||||
maintainer-clean-local: clean-testvectors
|
||||
|
||||
### Additional files to distribute
|
||||
EXTRA_DIST = autogen.sh CHANGELOG.md SECURITY.md
|
||||
EXTRA_DIST += doc/release-process.md doc/safegcd_implementation.md
|
||||
EXTRA_DIST += doc/ellswift.md doc/musig.md doc/iceberg.md doc/prefractal.md
|
||||
EXTRA_DIST += src/modules/frost/frost.md src/modules/chilldkg/chilldkg.md
|
||||
EXTRA_DIST += src/modules/frost_enrollment/frost_enrollment.md
|
||||
EXTRA_DIST += examples/EXAMPLES_COPYING
|
||||
EXTRA_DIST += sage/gen_exhaustive_groups.sage
|
||||
EXTRA_DIST += sage/gen_split_lambda_constants.sage
|
||||
EXTRA_DIST += sage/group_prover.sage
|
||||
EXTRA_DIST += sage/prove_group_implementations.sage
|
||||
EXTRA_DIST += sage/secp256k1_params.sage
|
||||
EXTRA_DIST += sage/weierstrass_prover.sage
|
||||
EXTRA_DIST += src/wycheproof/WYCHEPROOF_COPYING
|
||||
EXTRA_DIST += src/wycheproof/ecdsa_secp256k1_sha256_bitcoin_test.json
|
||||
EXTRA_DIST += src/wycheproof/ecdh_secp256k1_test.json
|
||||
EXTRA_DIST += tools/tests_wycheproof_generate_ecdsa.py
|
||||
EXTRA_DIST += tools/tests_wycheproof_generate_ecdh.py
|
||||
|
||||
if ENABLE_MODULE_SCHNORRSIG_HALFAGG
|
||||
include src/modules/schnorrsig_halfagg/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_BPPP
|
||||
include src/modules/bppp/Makefile.am.include
|
||||
endif
|
||||
EXTRA_DIST = autogen.sh src/gen_context.c src/basic-config.h $(JAVA_FILES)
|
||||
|
||||
if ENABLE_MODULE_ECDH
|
||||
include src/modules/ecdh/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_SCHNORRSIG
|
||||
include src/modules/schnorrsig/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_MUSIG
|
||||
include src/modules/musig/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_RECOVERY
|
||||
include src/modules/recovery/Makefile.am.include
|
||||
endif
|
||||
@@ -376,47 +205,3 @@ endif
|
||||
if ENABLE_MODULE_SURJECTIONPROOF
|
||||
include src/modules/surjection/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_EXTRAKEYS
|
||||
include src/modules/extrakeys/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_SCHNORRSIG
|
||||
include src/modules/schnorrsig/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_MUSIG
|
||||
include src/modules/musig/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_ELLSWIFT
|
||||
include src/modules/ellswift/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_ECDSA_S2C
|
||||
include src/modules/ecdsa_s2c/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_ECDSA_ADAPTOR
|
||||
include src/modules/ecdsa_adaptor/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_FROST
|
||||
include src/modules/frost/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_CHILLDKG
|
||||
include src/modules/chilldkg/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_PREFRACTAL
|
||||
include src/modules/prefractal/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_FROST_ENROLLMENT
|
||||
include src/modules/frost_enrollment/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_ICEBERG
|
||||
include src/modules/iceberg/Makefile.am.include
|
||||
endif
|
||||
|
||||
184
README.md
184
README.md
@@ -1,139 +1,63 @@
|
||||
libsecp256k1-zkp
|
||||
================
|
||||
libsecp256k1
|
||||
============
|
||||
|
||||

|
||||
[](https://travis-ci.org/bitcoin-core/secp256k1)
|
||||
|
||||
A fork of [libsecp256k1](https://github.com/bitcoin-core/secp256k1) with support for advanced and experimental features
|
||||
Optimized C library for EC operations on curve secp256k1.
|
||||
|
||||
Added features:
|
||||
* Experimental module for ECDSA adaptor signatures.
|
||||
* Experimental module for ECDSA sign-to-contract.
|
||||
* Experimental modules for Confidential Assets (Pedersen commitments, range proofs, and [surjection proofs](src/modules/surjection/surjection.md)).
|
||||
* Experimental module for [address whitelisting](src/modules/whitelist/whitelist.md).
|
||||
* Experimental module for Schnorr signature half-aggregation.
|
||||
* Experimental module for [FROST (BIP 445)](src/modules/frost/frost.md).
|
||||
* Experimental module for [ChillDKG](src/modules/chilldkg/chilldkg.md), distributed key generation for FROST (bip-frost-dkg draft).
|
||||
* Experimental module for [Iceberg](doc/iceberg.md), a threshold scheme that lets a group of parties stand in for a single MuSig2 (BIP 327) participant.
|
||||
* Experimental module for [Prefractal](doc/prefractal.md), a nested FROST+MuSig2 signer that lets a FROST group occupy one participant slot of an ordinary MuSig2 (BIP 327) session.
|
||||
* Experimental module for [FROST enrollment](src/modules/frost_enrollment/frost_enrollment.md), which grows a (t, n) FROST group into a (t, n+1) one, and repairs a lost share, without re-running key generation.
|
||||
This library is a work in progress and is being used to research best practices. Use at your own risk.
|
||||
|
||||
Experimental features are made available for testing and review by the community. The APIs of these features should not be considered stable.
|
||||
Features:
|
||||
* secp256k1 ECDSA signing/verification and key generation.
|
||||
* Adding/multiplying private/public keys.
|
||||
* Serialization/parsing of private keys, public keys, signatures.
|
||||
* Constant time, constant memory access signing and pubkey generation.
|
||||
* Derandomized DSA (via RFC6979 or with a caller provided function.)
|
||||
* Very efficient implementation.
|
||||
|
||||
Implementation details
|
||||
----------------------
|
||||
|
||||
* General
|
||||
* No runtime heap allocation.
|
||||
* Extensive testing infrastructure.
|
||||
* Structured to facilitate review and analysis.
|
||||
* Intended to be portable to any system with a C89 compiler and uint64_t support.
|
||||
* Expose only higher level interfaces to minimize the API surface and improve application security. ("Be difficult to use insecurely.")
|
||||
* Field operations
|
||||
* Optimized implementation of arithmetic modulo the curve's field size (2^256 - 0x1000003D1).
|
||||
* Using 5 52-bit limbs (including hand-optimized assembly for x86_64, by Diederik Huys).
|
||||
* Using 10 26-bit limbs.
|
||||
* Field inverses and square roots using a sliding window over blocks of 1s (by Peter Dettman).
|
||||
* Scalar operations
|
||||
* Optimized implementation without data-dependent branches of arithmetic modulo the curve's order.
|
||||
* Using 4 64-bit limbs (relying on __int128 support in the compiler).
|
||||
* Using 8 32-bit limbs.
|
||||
* Group operations
|
||||
* Point addition formula specifically simplified for the curve equation (y^2 = x^3 + 7).
|
||||
* Use addition between points in Jacobian and affine coordinates where possible.
|
||||
* Use a unified addition/doubling formula where necessary to avoid data-dependent branches.
|
||||
* Point/x comparison without a field inversion by comparison in the Jacobian coordinate space.
|
||||
* Point multiplication for verification (a*P + b*G).
|
||||
* Use wNAF notation for point multiplicands.
|
||||
* Use a much larger window for multiples of G, using precomputed multiples.
|
||||
* Use Shamir's trick to do the multiplication with the public key and the generator simultaneously.
|
||||
* Optionally (off by default) use secp256k1's efficiently-computable endomorphism to split the P multiplicand into 2 half-sized ones.
|
||||
* Point multiplication for signing
|
||||
* Use a precomputed table of multiples of powers of 16 multiplied with the generator, so general multiplication becomes a series of additions.
|
||||
* Intended to be completely free of timing sidechannels for secret-key operations (on reasonable hardware/toolchains)
|
||||
* Access the table with branch-free conditional moves so memory access is uniform.
|
||||
* No data-dependent branches
|
||||
* Optional runtime blinding which attempts to frustrate differential power analysis.
|
||||
* The precomputed tables add and eventually subtract points for which no known scalar (private key) is known, preventing even an attacker with control over the private key used to control the data internally.
|
||||
|
||||
Build steps
|
||||
-----------
|
||||
|
||||
Obtaining and verifying
|
||||
-----------------------
|
||||
libsecp256k1 is built using autotools:
|
||||
|
||||
The git tag for each release (e.g. `v0.6.0`) is GPG-signed by one of the maintainers.
|
||||
For a fully verified build of this project, it is recommended to obtain this repository
|
||||
via git, obtain the GPG keys of the signing maintainer(s), and then verify the release
|
||||
tag's signature using git.
|
||||
|
||||
This can be done with the following steps:
|
||||
|
||||
1. Obtain the GPG keys listed in [SECURITY.md](./SECURITY.md).
|
||||
2. If possible, cross-reference these key IDs with another source controlled by its owner (e.g.
|
||||
social media, personal website). This is to mitigate the unlikely case that incorrect
|
||||
content is being presented by this repository.
|
||||
3. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/bitcoin-core/secp256k1
|
||||
```
|
||||
4. Check out the latest release tag, e.g.
|
||||
```
|
||||
git checkout v0.7.1
|
||||
```
|
||||
5. Use git to verify the GPG signature:
|
||||
```
|
||||
% git tag -v v0.7.1 | grep -C 3 'Good signature'
|
||||
|
||||
gpg: Signature made Mon 26 Jan 2026 07:42:46 PM UTC
|
||||
gpg: using RSA key 2840EAABF4BC9F0FFD716AFAFBAFCC46DE2D3FE2
|
||||
gpg: Good signature from "Pieter Wuille <pieter@wuille.net>" [unknown]
|
||||
gpg: aka "Pieter Wuille <pieter.wuille@gmail.com>" [full]
|
||||
gpg: aka "[jpeg image of size 5996]" [undefined]
|
||||
gpg: WARNING: This key is not certified with a trusted signature!
|
||||
gpg: There is no indication that the signature belongs to the owner.
|
||||
Primary key fingerprint: 133E AC17 9436 F14A 5CF1 B794 860F EB80 4E66 9320
|
||||
Subkey fingerprint: 2840 EAAB F4BC 9F0F FD71 6AFA FBAF CC46 DE2D 3FE2
|
||||
```
|
||||
|
||||
Building with Autotools
|
||||
-----------------------
|
||||
|
||||
$ ./autogen.sh # Generate a ./configure script
|
||||
$ ./configure # Generate a build system
|
||||
$ make # Run the actual build process
|
||||
$ make check # Run the test suite
|
||||
$ sudo make install # Install the library into the system (optional)
|
||||
|
||||
To compile optional modules (such as Schnorr signatures), you need to run `./configure` with additional flags (such as `--enable-module-schnorrsig`). Run `./configure --help` to see the full list of available flags. For experimental modules, you will also need `--enable-experimental` as well as a flag for each individual module, e.g. `--enable-module-rangeproof`.
|
||||
|
||||
Building with CMake
|
||||
-------------------
|
||||
|
||||
To maintain a pristine source tree, CMake encourages to perform an out-of-source build by using a separate dedicated build tree.
|
||||
|
||||
### Building on POSIX systems
|
||||
|
||||
$ cmake -B build # Generate a build system in subdirectory "build"
|
||||
$ cmake --build build # Run the actual build process
|
||||
$ ctest --test-dir build # Run the test suite
|
||||
$ sudo cmake --install build # Install the library into the system (optional)
|
||||
|
||||
To compile optional modules (such as Schnorr signatures), you need to run `cmake` with additional flags (such as `-DSECP256K1_ENABLE_MODULE_SCHNORRSIG=ON`). Run `cmake -B build -LH` or `ccmake -B build` to see the full list of available flags.
|
||||
|
||||
### Cross compiling
|
||||
|
||||
To alleviate issues with cross compiling, preconfigured toolchain files are available in the `cmake` directory.
|
||||
For example, to cross compile for Windows:
|
||||
|
||||
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/x86_64-w64-mingw32.toolchain.cmake
|
||||
|
||||
To cross compile for Android with [NDK](https://developer.android.com/ndk/guides/cmake) (using NDK's toolchain file, and assuming the `ANDROID_NDK_ROOT` environment variable has been set):
|
||||
|
||||
$ cmake -B build -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake" -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=28
|
||||
|
||||
### Building on Windows
|
||||
|
||||
The following example assumes Visual Studio 2022. Using clang-cl is recommended.
|
||||
|
||||
In "Developer Command Prompt for VS 2022":
|
||||
|
||||
>cmake -B build -T ClangCL
|
||||
>cmake --build build --config RelWithDebInfo
|
||||
|
||||
Usage examples
|
||||
-----------
|
||||
|
||||
Usage examples can be found in the [examples](examples) directory. To compile them you need to configure with `--enable-examples`.
|
||||
* [ECDSA example](examples/ecdsa.c)
|
||||
* [Schnorr signatures example](examples/schnorr.c)
|
||||
* [Deriving a shared secret (ECDH) example](examples/ecdh.c)
|
||||
* [ElligatorSwift key exchange example](examples/ellswift.c)
|
||||
* [MuSig2 Schnorr multi-signatures example](examples/musig.c)
|
||||
|
||||
To compile the examples, make sure the corresponding modules are enabled.
|
||||
|
||||
Benchmark
|
||||
------------
|
||||
If configured with `--enable-benchmark` (which is the default), binaries for benchmarking the libsecp256k1-zkp functions will be present in the root directory after the build.
|
||||
|
||||
To print the benchmark result to the command line:
|
||||
|
||||
$ ./bench_name
|
||||
|
||||
To create a CSV file for the benchmark result :
|
||||
|
||||
$ ./bench_name | sed '2d;s/ \{1,\}//g' > bench_name.csv
|
||||
|
||||
Reporting a vulnerability
|
||||
------------
|
||||
|
||||
See [SECURITY.md](SECURITY.md)
|
||||
|
||||
Contributing to libsecp256k1
|
||||
------------
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
$ ./autogen.sh
|
||||
$ ./configure
|
||||
$ make
|
||||
$ ./tests
|
||||
$ sudo make install # optional
|
||||
|
||||
14
SECURITY.md
14
SECURITY.md
@@ -1,14 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report security issues send an email to secp256k1-security@bitcoincore.org (not for support).
|
||||
|
||||
The following keys may be used to communicate sensitive information to developers:
|
||||
|
||||
| Name | Fingerprint |
|
||||
|------|-------------|
|
||||
| Pieter Wuille | 133E AC17 9436 F14A 5CF1 B794 860F EB80 4E66 9320 |
|
||||
| Tim Ruffing | 09E0 3F87 1092 E40E 106E 902B 33BC 86AB 80FF 5516 |
|
||||
|
||||
You can import a key by running the following command with that individual’s fingerprint: `gpg --keyserver hkps://keys.openpgp.org --recv-keys "<fingerprint>"` Ensure that you put quotes around fingerprints containing spaces.
|
||||
3
TODO
Normal file
3
TODO
Normal file
@@ -0,0 +1,3 @@
|
||||
* Unit tests for fieldelem/groupelem, including ones intended to
|
||||
trigger fieldelem's boundary cases.
|
||||
* Complete constant-time operations for signing/keygen
|
||||
@@ -1,91 +0,0 @@
|
||||
dnl escape "$0x" below using the m4 quadrigaph @S|@, and escape it again with a \ for the shell.
|
||||
AC_DEFUN([SECP_X86_64_ASM_CHECK],[
|
||||
AC_MSG_CHECKING(for x86_64 assembly availability)
|
||||
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
|
||||
#include <stdint.h>]],[[
|
||||
uint64_t a = 11, tmp = 0;
|
||||
__asm__ __volatile__("movq \@S|@0x100000000,%1; mulq %%rsi" : "+a"(a) : "S"(tmp) : "cc", "%rdx");
|
||||
]])], [has_x86_64_asm=yes], [has_x86_64_asm=no])
|
||||
AC_MSG_RESULT([$has_x86_64_asm])
|
||||
])
|
||||
|
||||
AC_DEFUN([SECP_ARM32_ASM_CHECK], [
|
||||
AC_MSG_CHECKING(for ARM32 assembly availability)
|
||||
SECP_ARM32_ASM_CHECK_CFLAGS_saved_CFLAGS="$CFLAGS"
|
||||
CFLAGS="-x assembler"
|
||||
AC_LINK_IFELSE([AC_LANG_SOURCE([[
|
||||
.syntax unified
|
||||
.eabi_attribute 24, 1
|
||||
.eabi_attribute 25, 1
|
||||
.text
|
||||
.global main
|
||||
main:
|
||||
ldr r0, =0x002A
|
||||
mov r7, #1
|
||||
swi 0
|
||||
]])], [has_arm32_asm=yes], [has_arm32_asm=no])
|
||||
AC_MSG_RESULT([$has_arm32_asm])
|
||||
CFLAGS="$SECP_ARM32_ASM_CHECK_CFLAGS_saved_CFLAGS"
|
||||
])
|
||||
|
||||
AC_DEFUN([SECP_VALGRIND_CHECK],[
|
||||
AC_MSG_CHECKING([for valgrind support])
|
||||
if test x"$has_valgrind" != x"yes"; then
|
||||
CPPFLAGS_TEMP="$CPPFLAGS"
|
||||
CPPFLAGS="$VALGRIND_CPPFLAGS $CPPFLAGS"
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
|
||||
#include <valgrind/memcheck.h>
|
||||
]], [[
|
||||
#if defined(NVALGRIND)
|
||||
# error "Valgrind does not support this platform."
|
||||
#endif
|
||||
]])], [has_valgrind=yes])
|
||||
CPPFLAGS="$CPPFLAGS_TEMP"
|
||||
fi
|
||||
AC_MSG_RESULT($has_valgrind)
|
||||
])
|
||||
|
||||
AC_DEFUN([SECP_MSAN_CHECK], [
|
||||
AC_MSG_CHECKING(whether MemorySanitizer is enabled)
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[
|
||||
#if defined(__has_feature)
|
||||
# if __has_feature(memory_sanitizer)
|
||||
/* MemorySanitizer is enabled. */
|
||||
# elif
|
||||
# error "MemorySanitizer is disabled."
|
||||
# endif
|
||||
#else
|
||||
# error "__has_feature is not defined."
|
||||
#endif
|
||||
]])], [msan_enabled=yes], [msan_enabled=no])
|
||||
AC_MSG_RESULT([$msan_enabled])
|
||||
])
|
||||
|
||||
dnl SECP_TRY_APPEND_CFLAGS(flags, VAR)
|
||||
dnl Append flags to VAR if CC accepts them.
|
||||
AC_DEFUN([SECP_TRY_APPEND_CFLAGS], [
|
||||
AC_MSG_CHECKING([if ${CC} supports $1])
|
||||
SECP_TRY_APPEND_CFLAGS_saved_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$1 $CFLAGS"
|
||||
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[char foo;]])], [flag_works=yes], [flag_works=no])
|
||||
AC_MSG_RESULT($flag_works)
|
||||
CFLAGS="$SECP_TRY_APPEND_CFLAGS_saved_CFLAGS"
|
||||
if test x"$flag_works" = x"yes"; then
|
||||
$2="$$2 $1"
|
||||
fi
|
||||
unset flag_works
|
||||
AC_SUBST($2)
|
||||
])
|
||||
|
||||
dnl SECP_SET_DEFAULT(VAR, default, default-dev-mode)
|
||||
dnl Set VAR to default or default-dev-mode, depending on whether dev mode is enabled
|
||||
AC_DEFUN([SECP_SET_DEFAULT], [
|
||||
if test "${enable_dev_mode+set}" != set; then
|
||||
AC_MSG_ERROR([[Set enable_dev_mode before calling SECP_SET_DEFAULT]])
|
||||
fi
|
||||
if test x"$enable_dev_mode" = x"yes"; then
|
||||
$1="$3"
|
||||
else
|
||||
$1="$2"
|
||||
fi
|
||||
])
|
||||
145
build-aux/m4/ax_jni_include_dir.m4
Normal file
145
build-aux/m4/ax_jni_include_dir.m4
Normal file
@@ -0,0 +1,145 @@
|
||||
# ===========================================================================
|
||||
# https://www.gnu.org/software/autoconf-archive/ax_jni_include_dir.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_JNI_INCLUDE_DIR
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# AX_JNI_INCLUDE_DIR finds include directories needed for compiling
|
||||
# programs using the JNI interface.
|
||||
#
|
||||
# JNI include directories are usually in the Java distribution. This is
|
||||
# deduced from the value of $JAVA_HOME, $JAVAC, or the path to "javac", in
|
||||
# that order. When this macro completes, a list of directories is left in
|
||||
# the variable JNI_INCLUDE_DIRS.
|
||||
#
|
||||
# Example usage follows:
|
||||
#
|
||||
# AX_JNI_INCLUDE_DIR
|
||||
#
|
||||
# for JNI_INCLUDE_DIR in $JNI_INCLUDE_DIRS
|
||||
# do
|
||||
# CPPFLAGS="$CPPFLAGS -I$JNI_INCLUDE_DIR"
|
||||
# done
|
||||
#
|
||||
# If you want to force a specific compiler:
|
||||
#
|
||||
# - at the configure.in level, set JAVAC=yourcompiler before calling
|
||||
# AX_JNI_INCLUDE_DIR
|
||||
#
|
||||
# - at the configure level, setenv JAVAC
|
||||
#
|
||||
# Note: This macro can work with the autoconf M4 macros for Java programs.
|
||||
# This particular macro is not part of the original set of macros.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2008 Don Anderson <dda@sleepycat.com>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty provided the copyright notice
|
||||
# and this notice are preserved. This file is offered as-is, without any
|
||||
# warranty.
|
||||
|
||||
#serial 14
|
||||
|
||||
AU_ALIAS([AC_JNI_INCLUDE_DIR], [AX_JNI_INCLUDE_DIR])
|
||||
AC_DEFUN([AX_JNI_INCLUDE_DIR],[
|
||||
|
||||
JNI_INCLUDE_DIRS=""
|
||||
|
||||
if test "x$JAVA_HOME" != x; then
|
||||
_JTOPDIR="$JAVA_HOME"
|
||||
else
|
||||
if test "x$JAVAC" = x; then
|
||||
JAVAC=javac
|
||||
fi
|
||||
AC_PATH_PROG([_ACJNI_JAVAC], [$JAVAC], [no])
|
||||
if test "x$_ACJNI_JAVAC" = xno; then
|
||||
AC_MSG_WARN([cannot find JDK; try setting \$JAVAC or \$JAVA_HOME])
|
||||
fi
|
||||
_ACJNI_FOLLOW_SYMLINKS("$_ACJNI_JAVAC")
|
||||
_JTOPDIR=`echo "$_ACJNI_FOLLOWED" | sed -e 's://*:/:g' -e 's:/[[^/]]*$::'`
|
||||
fi
|
||||
|
||||
case "$host_os" in
|
||||
darwin*) # Apple Java headers are inside the Xcode bundle.
|
||||
macos_version=$(sw_vers -productVersion | sed -n -e 's/^@<:@0-9@:>@*.\(@<:@0-9@:>@*\).@<:@0-9@:>@*/\1/p')
|
||||
if @<:@ "$macos_version" -gt "7" @:>@; then
|
||||
_JTOPDIR="$(xcrun --show-sdk-path)/System/Library/Frameworks/JavaVM.framework"
|
||||
_JINC="$_JTOPDIR/Headers"
|
||||
else
|
||||
_JTOPDIR="/System/Library/Frameworks/JavaVM.framework"
|
||||
_JINC="$_JTOPDIR/Headers"
|
||||
fi
|
||||
;;
|
||||
*) _JINC="$_JTOPDIR/include";;
|
||||
esac
|
||||
_AS_ECHO_LOG([_JTOPDIR=$_JTOPDIR])
|
||||
_AS_ECHO_LOG([_JINC=$_JINC])
|
||||
|
||||
# On Mac OS X 10.6.4, jni.h is a symlink:
|
||||
# /System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers/jni.h
|
||||
# -> ../../CurrentJDK/Headers/jni.h.
|
||||
AC_CACHE_CHECK(jni headers, ac_cv_jni_header_path,
|
||||
[
|
||||
if test -f "$_JINC/jni.h"; then
|
||||
ac_cv_jni_header_path="$_JINC"
|
||||
JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path"
|
||||
else
|
||||
_JTOPDIR=`echo "$_JTOPDIR" | sed -e 's:/[[^/]]*$::'`
|
||||
if test -f "$_JTOPDIR/include/jni.h"; then
|
||||
ac_cv_jni_header_path="$_JTOPDIR/include"
|
||||
JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $ac_cv_jni_header_path"
|
||||
else
|
||||
ac_cv_jni_header_path=none
|
||||
fi
|
||||
fi
|
||||
])
|
||||
|
||||
# get the likely subdirectories for system specific java includes
|
||||
case "$host_os" in
|
||||
bsdi*) _JNI_INC_SUBDIRS="bsdos";;
|
||||
freebsd*) _JNI_INC_SUBDIRS="freebsd";;
|
||||
darwin*) _JNI_INC_SUBDIRS="darwin";;
|
||||
linux*) _JNI_INC_SUBDIRS="linux genunix";;
|
||||
osf*) _JNI_INC_SUBDIRS="alpha";;
|
||||
solaris*) _JNI_INC_SUBDIRS="solaris";;
|
||||
mingw*) _JNI_INC_SUBDIRS="win32";;
|
||||
cygwin*) _JNI_INC_SUBDIRS="win32";;
|
||||
*) _JNI_INC_SUBDIRS="genunix";;
|
||||
esac
|
||||
|
||||
if test "x$ac_cv_jni_header_path" != "xnone"; then
|
||||
# add any subdirectories that are present
|
||||
for JINCSUBDIR in $_JNI_INC_SUBDIRS
|
||||
do
|
||||
if test -d "$_JTOPDIR/include/$JINCSUBDIR"; then
|
||||
JNI_INCLUDE_DIRS="$JNI_INCLUDE_DIRS $_JTOPDIR/include/$JINCSUBDIR"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
])
|
||||
|
||||
# _ACJNI_FOLLOW_SYMLINKS <path>
|
||||
# Follows symbolic links on <path>,
|
||||
# finally setting variable _ACJNI_FOLLOWED
|
||||
# ----------------------------------------
|
||||
AC_DEFUN([_ACJNI_FOLLOW_SYMLINKS],[
|
||||
# find the include directory relative to the javac executable
|
||||
_cur="$1"
|
||||
while ls -ld "$_cur" 2>/dev/null | grep " -> " >/dev/null; do
|
||||
AC_MSG_CHECKING([symlink for $_cur])
|
||||
_slink=`ls -ld "$_cur" | sed 's/.* -> //'`
|
||||
case "$_slink" in
|
||||
/*) _cur="$_slink";;
|
||||
# 'X' avoids triggering unwanted echo options.
|
||||
*) _cur=`echo "X$_cur" | sed -e 's/^X//' -e 's:[[^/]]*$::'`"$_slink";;
|
||||
esac
|
||||
AC_MSG_RESULT([$_cur])
|
||||
done
|
||||
_ACJNI_FOLLOWED="$_cur"
|
||||
])# _ACJNI
|
||||
125
build-aux/m4/ax_prog_cc_for_build.m4
Normal file
125
build-aux/m4/ax_prog_cc_for_build.m4
Normal file
@@ -0,0 +1,125 @@
|
||||
# ===========================================================================
|
||||
# http://www.gnu.org/software/autoconf-archive/ax_prog_cc_for_build.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_PROG_CC_FOR_BUILD
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# This macro searches for a C compiler that generates native executables,
|
||||
# that is a C compiler that surely is not a cross-compiler. This can be
|
||||
# useful if you have to generate source code at compile-time like for
|
||||
# example GCC does.
|
||||
#
|
||||
# The macro sets the CC_FOR_BUILD and CPP_FOR_BUILD macros to anything
|
||||
# needed to compile or link (CC_FOR_BUILD) and preprocess (CPP_FOR_BUILD).
|
||||
# The value of these variables can be overridden by the user by specifying
|
||||
# a compiler with an environment variable (like you do for standard CC).
|
||||
#
|
||||
# It also sets BUILD_EXEEXT and BUILD_OBJEXT to the executable and object
|
||||
# file extensions for the build platform, and GCC_FOR_BUILD to `yes' if
|
||||
# the compiler we found is GCC. All these variables but GCC_FOR_BUILD are
|
||||
# substituted in the Makefile.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2008 Paolo Bonzini <bonzini@gnu.org>
|
||||
#
|
||||
# Copying and distribution of this file, with or without modification, are
|
||||
# permitted in any medium without royalty provided the copyright notice
|
||||
# and this notice are preserved. This file is offered as-is, without any
|
||||
# warranty.
|
||||
|
||||
#serial 8
|
||||
|
||||
AU_ALIAS([AC_PROG_CC_FOR_BUILD], [AX_PROG_CC_FOR_BUILD])
|
||||
AC_DEFUN([AX_PROG_CC_FOR_BUILD], [dnl
|
||||
AC_REQUIRE([AC_PROG_CC])dnl
|
||||
AC_REQUIRE([AC_PROG_CPP])dnl
|
||||
AC_REQUIRE([AC_EXEEXT])dnl
|
||||
AC_REQUIRE([AC_CANONICAL_HOST])dnl
|
||||
|
||||
dnl Use the standard macros, but make them use other variable names
|
||||
dnl
|
||||
pushdef([ac_cv_prog_CPP], ac_cv_build_prog_CPP)dnl
|
||||
pushdef([ac_cv_prog_gcc], ac_cv_build_prog_gcc)dnl
|
||||
pushdef([ac_cv_prog_cc_works], ac_cv_build_prog_cc_works)dnl
|
||||
pushdef([ac_cv_prog_cc_cross], ac_cv_build_prog_cc_cross)dnl
|
||||
pushdef([ac_cv_prog_cc_g], ac_cv_build_prog_cc_g)dnl
|
||||
pushdef([ac_cv_exeext], ac_cv_build_exeext)dnl
|
||||
pushdef([ac_cv_objext], ac_cv_build_objext)dnl
|
||||
pushdef([ac_exeext], ac_build_exeext)dnl
|
||||
pushdef([ac_objext], ac_build_objext)dnl
|
||||
pushdef([CC], CC_FOR_BUILD)dnl
|
||||
pushdef([CPP], CPP_FOR_BUILD)dnl
|
||||
pushdef([CFLAGS], CFLAGS_FOR_BUILD)dnl
|
||||
pushdef([CPPFLAGS], CPPFLAGS_FOR_BUILD)dnl
|
||||
pushdef([LDFLAGS], LDFLAGS_FOR_BUILD)dnl
|
||||
pushdef([host], build)dnl
|
||||
pushdef([host_alias], build_alias)dnl
|
||||
pushdef([host_cpu], build_cpu)dnl
|
||||
pushdef([host_vendor], build_vendor)dnl
|
||||
pushdef([host_os], build_os)dnl
|
||||
pushdef([ac_cv_host], ac_cv_build)dnl
|
||||
pushdef([ac_cv_host_alias], ac_cv_build_alias)dnl
|
||||
pushdef([ac_cv_host_cpu], ac_cv_build_cpu)dnl
|
||||
pushdef([ac_cv_host_vendor], ac_cv_build_vendor)dnl
|
||||
pushdef([ac_cv_host_os], ac_cv_build_os)dnl
|
||||
pushdef([ac_cpp], ac_build_cpp)dnl
|
||||
pushdef([ac_compile], ac_build_compile)dnl
|
||||
pushdef([ac_link], ac_build_link)dnl
|
||||
|
||||
save_cross_compiling=$cross_compiling
|
||||
save_ac_tool_prefix=$ac_tool_prefix
|
||||
cross_compiling=no
|
||||
ac_tool_prefix=
|
||||
|
||||
AC_PROG_CC
|
||||
AC_PROG_CPP
|
||||
AC_EXEEXT
|
||||
|
||||
ac_tool_prefix=$save_ac_tool_prefix
|
||||
cross_compiling=$save_cross_compiling
|
||||
|
||||
dnl Restore the old definitions
|
||||
dnl
|
||||
popdef([ac_link])dnl
|
||||
popdef([ac_compile])dnl
|
||||
popdef([ac_cpp])dnl
|
||||
popdef([ac_cv_host_os])dnl
|
||||
popdef([ac_cv_host_vendor])dnl
|
||||
popdef([ac_cv_host_cpu])dnl
|
||||
popdef([ac_cv_host_alias])dnl
|
||||
popdef([ac_cv_host])dnl
|
||||
popdef([host_os])dnl
|
||||
popdef([host_vendor])dnl
|
||||
popdef([host_cpu])dnl
|
||||
popdef([host_alias])dnl
|
||||
popdef([host])dnl
|
||||
popdef([LDFLAGS])dnl
|
||||
popdef([CPPFLAGS])dnl
|
||||
popdef([CFLAGS])dnl
|
||||
popdef([CPP])dnl
|
||||
popdef([CC])dnl
|
||||
popdef([ac_objext])dnl
|
||||
popdef([ac_exeext])dnl
|
||||
popdef([ac_cv_objext])dnl
|
||||
popdef([ac_cv_exeext])dnl
|
||||
popdef([ac_cv_prog_cc_g])dnl
|
||||
popdef([ac_cv_prog_cc_cross])dnl
|
||||
popdef([ac_cv_prog_cc_works])dnl
|
||||
popdef([ac_cv_prog_gcc])dnl
|
||||
popdef([ac_cv_prog_CPP])dnl
|
||||
|
||||
dnl Finally, set Makefile variables
|
||||
dnl
|
||||
BUILD_EXEEXT=$ac_build_exeext
|
||||
BUILD_OBJEXT=$ac_build_objext
|
||||
AC_SUBST(BUILD_EXEEXT)dnl
|
||||
AC_SUBST(BUILD_OBJEXT)dnl
|
||||
AC_SUBST([CFLAGS_FOR_BUILD])dnl
|
||||
AC_SUBST([CPPFLAGS_FOR_BUILD])dnl
|
||||
AC_SUBST([LDFLAGS_FOR_BUILD])dnl
|
||||
])
|
||||
68
build-aux/m4/bitcoin_secp.m4
Normal file
68
build-aux/m4/bitcoin_secp.m4
Normal file
@@ -0,0 +1,68 @@
|
||||
dnl libsecp25k1 helper checks
|
||||
AC_DEFUN([SECP_INT128_CHECK],[
|
||||
has_int128=$ac_cv_type___int128
|
||||
])
|
||||
|
||||
dnl escape "$0x" below using the m4 quadrigaph @S|@, and escape it again with a \ for the shell.
|
||||
AC_DEFUN([SECP_64BIT_ASM_CHECK],[
|
||||
AC_MSG_CHECKING(for x86_64 assembly availability)
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
|
||||
#include <stdint.h>]],[[
|
||||
uint64_t a = 11, tmp;
|
||||
__asm__ __volatile__("movq \@S|@0x100000000,%1; mulq %%rsi" : "+a"(a) : "S"(tmp) : "cc", "%rdx");
|
||||
]])],[has_64bit_asm=yes],[has_64bit_asm=no])
|
||||
AC_MSG_RESULT([$has_64bit_asm])
|
||||
])
|
||||
|
||||
dnl
|
||||
AC_DEFUN([SECP_OPENSSL_CHECK],[
|
||||
has_libcrypto=no
|
||||
m4_ifdef([PKG_CHECK_MODULES],[
|
||||
PKG_CHECK_MODULES([CRYPTO], [libcrypto], [has_libcrypto=yes],[has_libcrypto=no])
|
||||
if test x"$has_libcrypto" = x"yes"; then
|
||||
TEMP_LIBS="$LIBS"
|
||||
LIBS="$LIBS $CRYPTO_LIBS"
|
||||
AC_CHECK_LIB(crypto, main,[AC_DEFINE(HAVE_LIBCRYPTO,1,[Define this symbol if libcrypto is installed])],[has_libcrypto=no])
|
||||
LIBS="$TEMP_LIBS"
|
||||
fi
|
||||
])
|
||||
if test x$has_libcrypto = xno; then
|
||||
AC_CHECK_HEADER(openssl/crypto.h,[
|
||||
AC_CHECK_LIB(crypto, main,[
|
||||
has_libcrypto=yes
|
||||
CRYPTO_LIBS=-lcrypto
|
||||
AC_DEFINE(HAVE_LIBCRYPTO,1,[Define this symbol if libcrypto is installed])
|
||||
])
|
||||
])
|
||||
LIBS=
|
||||
fi
|
||||
if test x"$has_libcrypto" = x"yes" && test x"$has_openssl_ec" = x; then
|
||||
AC_MSG_CHECKING(for EC functions in libcrypto)
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/obj_mac.h>]],[[
|
||||
EC_KEY *eckey = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
ECDSA_sign(0, NULL, 0, NULL, NULL, eckey);
|
||||
ECDSA_verify(0, NULL, 0, NULL, 0, eckey);
|
||||
EC_KEY_free(eckey);
|
||||
ECDSA_SIG *sig_openssl;
|
||||
sig_openssl = ECDSA_SIG_new();
|
||||
ECDSA_SIG_free(sig_openssl);
|
||||
]])],[has_openssl_ec=yes],[has_openssl_ec=no])
|
||||
AC_MSG_RESULT([$has_openssl_ec])
|
||||
fi
|
||||
])
|
||||
|
||||
dnl
|
||||
AC_DEFUN([SECP_GMP_CHECK],[
|
||||
if test x"$has_gmp" != x"yes"; then
|
||||
CPPFLAGS_TEMP="$CPPFLAGS"
|
||||
CPPFLAGS="$GMP_CPPFLAGS $CPPFLAGS"
|
||||
LIBS_TEMP="$LIBS"
|
||||
LIBS="$GMP_LIBS $LIBS"
|
||||
AC_CHECK_HEADER(gmp.h,[AC_CHECK_LIB(gmp, __gmpz_init,[has_gmp=yes; GMP_LIBS="$GMP_LIBS -lgmp"; AC_DEFINE(HAVE_LIBGMP,1,[Define this symbol if libgmp is installed])])])
|
||||
CPPFLAGS="$CPPFLAGS_TEMP"
|
||||
LIBS="$LIBS_TEMP"
|
||||
fi
|
||||
])
|
||||
165
ci/ci.sh
165
ci/ci.sh
@@ -1,165 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eux
|
||||
|
||||
export LC_ALL=C
|
||||
|
||||
# Print commit and relevant CI environment to allow reproducing the job outside of CI.
|
||||
git show --no-patch
|
||||
print_environment() {
|
||||
# Turn off -x because it messes up the output
|
||||
set +x
|
||||
# There are many ways to print variable names and their content. This one
|
||||
# does not rely on bash.
|
||||
for var in WERROR_CFLAGS MAKEFLAGS BUILD \
|
||||
ECMULTWINDOW ECMULTGENKB ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
|
||||
EXPERIMENTAL ECDH RECOVERY EXTRAKEYS SCHNORRSIG MUSIG SCHNORRSIG_HALFAGG ELLSWIFT \
|
||||
ECDSA_S2C GENERATOR RANGEPROOF SURJECTIONPROOF WHITELIST ECDSAADAPTOR BPPP \
|
||||
FROST CHILLDKG ICEBERG PREFRACTAL FROST_ENROLLMENT SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS SYMBOL_CHECK \
|
||||
EXAMPLES \
|
||||
HOST WRAPPER_CMD \
|
||||
CC CFLAGS CPPFLAGS AR NM \
|
||||
UBSAN_OPTIONS ASAN_OPTIONS LSAN_OPTIONS
|
||||
do
|
||||
eval "isset=\${$var+x}"
|
||||
if [ -n "$isset" ]; then
|
||||
eval "val=\${$var}"
|
||||
# shellcheck disable=SC2154
|
||||
printf '%s="%s" ' "$var" "$val"
|
||||
fi
|
||||
done
|
||||
echo "$0"
|
||||
set -x
|
||||
}
|
||||
print_environment
|
||||
|
||||
env >> test_env.log
|
||||
|
||||
# If gcc is requested, assert that it's in fact gcc (and not some symlinked Apple clang).
|
||||
case "${CC:-undefined}" in
|
||||
*gcc*)
|
||||
$CC -v 2>&1 | grep -q "gcc version" || exit 1;
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -n "${CC+x}" ]; then
|
||||
# The MSVC compiler "cl" doesn't understand "-v"
|
||||
$CC -v || true
|
||||
fi
|
||||
if [ "$WITH_VALGRIND" = "yes" ]; then
|
||||
valgrind --version
|
||||
fi
|
||||
if [ -n "$WRAPPER_CMD" ]; then
|
||||
$WRAPPER_CMD --version
|
||||
fi
|
||||
|
||||
./autogen.sh
|
||||
|
||||
./configure \
|
||||
--enable-experimental="$EXPERIMENTAL" \
|
||||
--with-test-override-wide-multiply="$WIDEMUL" --with-asm="$ASM" \
|
||||
--with-ecmult-window="$ECMULTWINDOW" \
|
||||
--with-ecmult-gen-kb="$ECMULTGENKB" \
|
||||
--enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \
|
||||
--enable-module-ellswift="$ELLSWIFT" \
|
||||
--enable-module-extrakeys="$EXTRAKEYS" \
|
||||
--enable-module-ecdsa-s2c="$ECDSA_S2C" \
|
||||
--enable-module-bppp="$BPPP" \
|
||||
--enable-module-rangeproof="$RANGEPROOF" --enable-module-surjectionproof="$SURJECTIONPROOF" --enable-module-whitelist="$WHITELIST" --enable-module-generator="$GENERATOR" \
|
||||
--enable-module-schnorrsig="$SCHNORRSIG" --enable-module-ecdsa-adaptor="$ECDSAADAPTOR" \
|
||||
--enable-module-musig="$MUSIG" \
|
||||
--enable-module-schnorrsig-halfagg="$SCHNORRSIG_HALFAGG" \
|
||||
--enable-module-frost="$FROST" \
|
||||
--enable-module-chilldkg="$CHILLDKG" \
|
||||
--enable-module-iceberg="$ICEBERG" \
|
||||
--enable-module-prefractal="$PREFRACTAL" \
|
||||
--enable-module-frost-enrollment="$FROST_ENROLLMENT" \
|
||||
--enable-examples="$EXAMPLES" \
|
||||
--enable-ctime-tests="$CTIMETESTS" \
|
||||
--with-valgrind="$WITH_VALGRIND" \
|
||||
--host="$HOST" $EXTRAFLAGS
|
||||
|
||||
# We have set "-j<n>" in MAKEFLAGS.
|
||||
build_exit_code=0
|
||||
make > make.log 2>&1 || build_exit_code=$?
|
||||
cat make.log
|
||||
if [ $build_exit_code -ne 0 ]; then
|
||||
case "${CC:-undefined}" in
|
||||
*snapshot*)
|
||||
# Ignore internal compiler errors in gcc-snapshot and clang-snapshot
|
||||
grep -e "internal compiler error:" -e "PLEASE submit a bug report" make.log
|
||||
exit $?
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Print information about binaries so that we can see that the architecture is correct
|
||||
file *tests* || true
|
||||
file bench* || true
|
||||
file .libs/* || true
|
||||
|
||||
if [ "$SYMBOL_CHECK" = "yes" ]
|
||||
then
|
||||
python3 --version
|
||||
case "$HOST" in
|
||||
*mingw*)
|
||||
ls -l .libs
|
||||
python3 ./tools/symbol-check.py .libs/libsecp256k1-*.dll
|
||||
;;
|
||||
*)
|
||||
python3 ./tools/symbol-check.py .libs/libsecp256k1.so
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# This tells `make check` to wrap test invocations.
|
||||
export LOG_COMPILER="$WRAPPER_CMD"
|
||||
|
||||
make "$BUILD"
|
||||
|
||||
# Using the local `libtool` because on macOS the system's libtool has nothing to do with GNU libtool
|
||||
EXEC='./libtool --mode=execute'
|
||||
if [ -n "$WRAPPER_CMD" ]
|
||||
then
|
||||
EXEC="$EXEC $WRAPPER_CMD"
|
||||
fi
|
||||
|
||||
if [ "$BENCH" = "yes" ]
|
||||
then
|
||||
{
|
||||
$EXEC ./bench_ecmult
|
||||
$EXEC ./bench_internal
|
||||
$EXEC ./bench
|
||||
if [ "$BPPP" = "yes" ]
|
||||
then
|
||||
$EXEC ./bench_bppp
|
||||
fi
|
||||
if [ "$ICEBERG" = "yes" ]
|
||||
then
|
||||
$EXEC ./bench_iceberg
|
||||
fi
|
||||
} >> bench.log 2>&1
|
||||
fi
|
||||
|
||||
if [ "$CTIMETESTS" = "yes" ]
|
||||
then
|
||||
if [ "$WITH_VALGRIND" = "yes" ]; then
|
||||
./libtool --mode=execute valgrind --error-exitcode=42 ./ctime_tests > ctime_tests.log 2>&1
|
||||
else
|
||||
$EXEC ./ctime_tests > ctime_tests.log 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Rebuild precomputed files (if not cross-compiling).
|
||||
if [ -z "$HOST" ]
|
||||
then
|
||||
make clean-precomp clean-testvectors
|
||||
make precomp testvectors
|
||||
fi
|
||||
|
||||
# Check that no repo files have been modified by the build.
|
||||
# (This fails for example if the precomp files need to be updated in the repo.)
|
||||
git diff --exit-code
|
||||
@@ -1,87 +0,0 @@
|
||||
FROM debian:stable-slim
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
# A too high maximum number of file descriptors (with the default value
|
||||
# inherited from the docker host) can cause issues with some of our tools:
|
||||
# - sanitizers hanging: https://github.com/google/sanitizers/issues/1662
|
||||
# - valgrind crashing: https://stackoverflow.com/a/75293014
|
||||
# This is not be a problem on our CI hosts, but developers who run the image
|
||||
# on their machines may run into this (e.g., on Arch Linux), so warn them.
|
||||
# (Note that .bashrc is only executed in interactive bash shells.)
|
||||
RUN echo 'if [[ $(ulimit -n) -gt 200000 ]]; then echo "WARNING: Very high value reported by \"ulimit -n\". Consider passing \"--ulimit nofile=32768\" to \"docker run\"."; fi' >> /root/.bashrc
|
||||
|
||||
RUN dpkg --add-architecture i386 && \
|
||||
dpkg --add-architecture s390x && \
|
||||
dpkg --add-architecture armhf && \
|
||||
dpkg --add-architecture arm64 && \
|
||||
dpkg --add-architecture ppc64el
|
||||
|
||||
# dpkg-dev: to make pkg-config work in cross-builds
|
||||
# llvm: for llvm-symbolizer, which is used by clang's UBSan for symbolized stack traces
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
git ca-certificates \
|
||||
make automake libtool pkg-config dpkg-dev valgrind qemu-user \
|
||||
gcc clang llvm libclang-rt-dev libc6-dbg \
|
||||
g++ \
|
||||
gcc-i686-linux-gnu libc6-dev-i386-cross libc6-dbg:i386 libubsan1:i386 libasan8:i386 \
|
||||
gcc-s390x-linux-gnu libc6-dev-s390x-cross libc6-dbg:s390x \
|
||||
gcc-arm-linux-gnueabihf libc6-dev-armhf-cross libc6-dbg:armhf \
|
||||
gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross libc6-dbg:ppc64el \
|
||||
gcc-mingw-w64-x86-64-win32 wine64 wine \
|
||||
gcc-mingw-w64-i686-win32 wine32 \
|
||||
python3-full && \
|
||||
if ! ( dpkg --print-architecture | grep --quiet "arm64" ) ; then \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dbg:arm64 ;\
|
||||
fi && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Build and install gcc snapshot
|
||||
ARG GCC_SNAPSHOT_MAJOR=17
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
|
||||
wget libgmp-dev libmpfr-dev libmpc-dev flex && \
|
||||
mkdir gcc && cd gcc && \
|
||||
wget --progress=dot:giga --https-only --recursive --accept '*.tar.xz' --level 1 --no-directories "https://gcc.gnu.org/pub/gcc/snapshots/LATEST-${GCC_SNAPSHOT_MAJOR}" && \
|
||||
wget "https://gcc.gnu.org/pub/gcc/snapshots/LATEST-${GCC_SNAPSHOT_MAJOR}/sha512.sum" && \
|
||||
sha512sum --check --ignore-missing sha512.sum && \
|
||||
# We should have downloaded exactly one tar.xz file
|
||||
ls && \
|
||||
[ $(ls *.tar.xz | wc -l) -eq "1" ] && \
|
||||
tar xf *.tar.xz && \
|
||||
mkdir gcc-build && cd gcc-build && \
|
||||
../*/configure --prefix=/opt/gcc-snapshot --enable-languages=c --disable-bootstrap --disable-multilib --without-isl && \
|
||||
make -j $(nproc) && \
|
||||
make install && \
|
||||
cd ../.. && rm -rf gcc && \
|
||||
ln -s /opt/gcc-snapshot/bin/gcc /usr/bin/gcc-snapshot && \
|
||||
apt-get autoremove -y wget libgmp-dev libmpfr-dev libmpc-dev flex && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install clang snapshot, see https://apt.llvm.org/
|
||||
RUN \
|
||||
# Setup GPG keys of LLVM repository
|
||||
apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y wget && \
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && \
|
||||
# Add repository for this Debian release
|
||||
. /etc/os-release && echo "deb http://apt.llvm.org/${VERSION_CODENAME} llvm-toolchain-${VERSION_CODENAME} main" >> /etc/apt/sources.list && \
|
||||
# Temporarily work around Sequoia PGP policy deadline for legacy repositories.
|
||||
# See https://github.com/llvm/llvm-project/issues/153385.
|
||||
sed -i 's/\(sha1\.second_preimage_resistance =\).*/\1 9999-01-01/' /usr/share/apt/default-sequoia.config && \
|
||||
apt-get update && \
|
||||
# Determine the version number of the LLVM development branch
|
||||
LLVM_VERSION=$(apt-cache search --names-only '^clang-[0-9]+$' | sort -V | tail -1 | cut -f1 -d" " | cut -f2 -d"-" ) && \
|
||||
# Install
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y "clang-${LLVM_VERSION}" "libclang-rt-${LLVM_VERSION}-dev" && \
|
||||
# Create symlink
|
||||
ln -s "/usr/bin/clang-${LLVM_VERSION}" /usr/bin/clang-snapshot && \
|
||||
# Clean up
|
||||
apt-get autoremove -y wget && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV VIRTUAL_ENV=/root/venv
|
||||
RUN python3 -m venv $VIRTUAL_ENV
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN pip install lief
|
||||
@@ -1,6 +0,0 @@
|
||||
function(check_arm32_assembly)
|
||||
try_compile(HAVE_ARM32_ASM
|
||||
${PROJECT_BINARY_DIR}/check_arm32_assembly
|
||||
SOURCES ${PROJECT_SOURCE_DIR}/cmake/source_arm32.s
|
||||
)
|
||||
endfunction()
|
||||
@@ -1,18 +0,0 @@
|
||||
include_guard(GLOBAL)
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
function(check_memory_sanitizer output)
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
check_c_source_compiles("
|
||||
#if defined(__has_feature)
|
||||
# if __has_feature(memory_sanitizer)
|
||||
/* MemorySanitizer is enabled. */
|
||||
# elif
|
||||
# error \"MemorySanitizer is disabled.\"
|
||||
# endif
|
||||
#else
|
||||
# error \"__has_feature is not defined.\"
|
||||
#endif
|
||||
" HAVE_MSAN)
|
||||
set(${output} ${HAVE_MSAN} PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -1,10 +0,0 @@
|
||||
function(check_string_option_value option)
|
||||
get_property(expected_values CACHE ${option} PROPERTY STRINGS)
|
||||
if(expected_values)
|
||||
if(${option} IN_LIST expected_values)
|
||||
return()
|
||||
endif()
|
||||
message(FATAL_ERROR "${option} value is \"${${option}}\", but must be one of ${expected_values}.")
|
||||
endif()
|
||||
message(AUTHOR_WARNING "The STRINGS property must be set before invoking `check_string_option_value' function.")
|
||||
endfunction()
|
||||
@@ -1,15 +0,0 @@
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
function(check_x86_64_assembly)
|
||||
check_c_source_compiles("
|
||||
#include <stdint.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint64_t a = 11, tmp = 0;
|
||||
__asm__ __volatile__(\"movq $0x100000000,%1; mulq %%rsi\" : \"+a\"(a) : \"S\"(tmp) : \"cc\", \"%rdx\");
|
||||
return 0;
|
||||
}
|
||||
" HAVE_X86_64_ASM)
|
||||
set(HAVE_X86_64_ASM ${HAVE_X86_64_ASM} PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -1,71 +0,0 @@
|
||||
# TODO: rework/remove once test discovery is implemented upstream:
|
||||
# https://gitlab.kitware.com/cmake/cmake/-/issues/26920
|
||||
function(discover_tests target)
|
||||
set(options "")
|
||||
set(oneValueArgs DISCOVERY_MATCH TEST_NAME_REPLACEMENT TEST_ARGS_REPLACEMENT)
|
||||
set(multiValueArgs DISCOVERY_ARGS PROPERTIES)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${oneValueArgs}" "${multiValueArgs}")
|
||||
|
||||
set(file_base ${CMAKE_CURRENT_BINARY_DIR}/${target})
|
||||
set(include_file ${file_base}_include.cmake)
|
||||
|
||||
set(properties_content)
|
||||
list(LENGTH arg_PROPERTIES properties_len)
|
||||
if(properties_len GREATER "0")
|
||||
set(properties_content " set_tests_properties(\"\${test_name}\" PROPERTIES\n")
|
||||
math(EXPR num_properties "${properties_len} / 2")
|
||||
foreach(i RANGE 0 ${num_properties} 2)
|
||||
math(EXPR value_index "${i} + 1")
|
||||
list(GET arg_PROPERTIES ${i} name)
|
||||
list(GET arg_PROPERTIES ${value_index} value)
|
||||
string(APPEND properties_content " \"${name}\" \"${value}\"\n")
|
||||
endforeach()
|
||||
string(APPEND properties_content " )\n")
|
||||
endif()
|
||||
|
||||
string(CONCAT include_content
|
||||
"set(runner [[$<TARGET_FILE:${target}>]])\n"
|
||||
"set(launcher [[$<TARGET_PROPERTY:${target},TEST_LAUNCHER>]])\n"
|
||||
"set(emulator [[$<$<BOOL:${CMAKE_CROSSCOMPILING}>:$<TARGET_PROPERTY:${target},CROSSCOMPILING_EMULATOR>>]])\n"
|
||||
"\n"
|
||||
"execute_process(\n"
|
||||
" COMMAND \${launcher} \${emulator} \${runner} ${arg_DISCOVERY_ARGS}\n"
|
||||
" OUTPUT_VARIABLE output OUTPUT_STRIP_TRAILING_WHITESPACE\n"
|
||||
" ERROR_VARIABLE output ERROR_STRIP_TRAILING_WHITESPACE\n"
|
||||
" RESULT_VARIABLE result\n"
|
||||
")\n"
|
||||
"\n"
|
||||
"if(NOT result EQUAL 0)\n"
|
||||
" add_test([[${target}_DISCOVERY_FAILURE]] \${launcher} \${emulator} \${runner} ${arg_DISCOVERY_ARGS})\n"
|
||||
"else()\n"
|
||||
" string(REPLACE \"\\n\" \";\" lines \"\${output}\")\n"
|
||||
" foreach(line IN LISTS lines)\n"
|
||||
" if(line MATCHES \"${arg_DISCOVERY_MATCH}\")\n"
|
||||
" string(REGEX REPLACE \"${arg_DISCOVERY_MATCH}\" \"${arg_TEST_NAME_REPLACEMENT}\" test_name \"\${line}\")\n"
|
||||
" string(REGEX REPLACE \"${arg_DISCOVERY_MATCH}\" \"${arg_TEST_ARGS_REPLACEMENT}\" test_args \"\${line}\")\n"
|
||||
" separate_arguments(test_args)\n"
|
||||
" add_test(\"\${test_name}\" \${launcher} \${emulator} \${runner} \${test_args})\n"
|
||||
${properties_content}
|
||||
" endif()\n"
|
||||
" endforeach()\n"
|
||||
"endif()\n"
|
||||
)
|
||||
|
||||
get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if(is_multi_config)
|
||||
file(GENERATE
|
||||
OUTPUT ${file_base}_include-$<CONFIG>.cmake
|
||||
CONTENT "${include_content}"
|
||||
)
|
||||
file(WRITE ${include_file}
|
||||
"include(\"${file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")"
|
||||
)
|
||||
else()
|
||||
file(GENERATE
|
||||
OUTPUT ${include_file}
|
||||
CONTENT "${include_content}"
|
||||
)
|
||||
endif()
|
||||
|
||||
set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES ${include_file})
|
||||
endfunction()
|
||||
@@ -1,41 +0,0 @@
|
||||
if(CMAKE_HOST_APPLE)
|
||||
find_program(BREW_COMMAND brew)
|
||||
execute_process(
|
||||
COMMAND ${BREW_COMMAND} --prefix valgrind
|
||||
OUTPUT_VARIABLE valgrind_brew_prefix
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
|
||||
set(hints_paths)
|
||||
if(valgrind_brew_prefix)
|
||||
set(hints_paths ${valgrind_brew_prefix}/include)
|
||||
endif()
|
||||
|
||||
find_path(Valgrind_INCLUDE_DIR
|
||||
NAMES valgrind/memcheck.h
|
||||
HINTS ${hints_paths}
|
||||
)
|
||||
|
||||
if(Valgrind_INCLUDE_DIR)
|
||||
include(CheckCSourceCompiles)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${Valgrind_INCLUDE_DIR})
|
||||
check_c_source_compiles("
|
||||
#include <valgrind/memcheck.h>
|
||||
#if defined(NVALGRIND)
|
||||
# error \"Valgrind does not support this platform.\"
|
||||
#endif
|
||||
|
||||
int main() {}
|
||||
" Valgrind_WORKS)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Valgrind
|
||||
REQUIRED_VARS Valgrind_INCLUDE_DIR Valgrind_WORKS
|
||||
)
|
||||
|
||||
mark_as_advanced(
|
||||
Valgrind_INCLUDE_DIR
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
function(generate_pkg_config_file in_file)
|
||||
set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||
set(exec_prefix \${prefix})
|
||||
set(libdir \${exec_prefix}/${CMAKE_INSTALL_LIBDIR})
|
||||
set(includedir \${prefix}/${CMAKE_INSTALL_INCLUDEDIR})
|
||||
set(PACKAGE_VERSION ${PROJECT_VERSION})
|
||||
configure_file(${in_file} ${PROJECT_NAME}.pc @ONLY)
|
||||
endfunction()
|
||||
@@ -1,61 +0,0 @@
|
||||
#[=[
|
||||
This emulates Libtool to make sure Libtool and CMake agree on
|
||||
the ABI version and file naming for shared libraries.
|
||||
|
||||
The `version_type` variable is set in `libtool.m4` (installed
|
||||
by autoreconf into autotools-aux/m4/).
|
||||
For the `major` and `versuffix` variables, see below "Calculate
|
||||
the version variables" in `ltmain.sh` (installed by autoreconf
|
||||
into autotools-aux/).
|
||||
]=]
|
||||
function(set_libtool_abi_version target current revision age)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|FreeBSD)$")
|
||||
# version_type = linux | freebsd-elf
|
||||
# major = $current - $age
|
||||
# versuffix = $major.$age.$revision
|
||||
math(EXPR _major "${current} - ${age}")
|
||||
set_target_properties(${target} PROPERTIES
|
||||
SOVERSION ${_major}
|
||||
VERSION ${_major}.${age}.${revision}
|
||||
)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
|
||||
# version_type = sunos
|
||||
# major = $current
|
||||
# versuffix = $current.$revision
|
||||
set_target_properties(${target} PROPERTIES
|
||||
SOVERSION ${current}
|
||||
VERSION ${current}.${revision}
|
||||
)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
# version_type = sunos
|
||||
# major = $current
|
||||
# versuffix = $current.$revision
|
||||
set_target_properties(${target} PROPERTIES
|
||||
# OpenBSD has no `soname_spec` defined in `libtool.m4`.
|
||||
VERSION ${current}.${revision}
|
||||
)
|
||||
elseif(APPLE)
|
||||
# version_type = darwin
|
||||
# major = $current - $age
|
||||
math(EXPR _major "${current} - ${age}")
|
||||
math(EXPR _compatibility "${current} + 1")
|
||||
set_target_properties(${target} PROPERTIES
|
||||
SOVERSION ${_major}
|
||||
MACHO_COMPATIBILITY_VERSION ${_compatibility}
|
||||
MACHO_CURRENT_VERSION ${_compatibility}.${revision}
|
||||
)
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
# version_type = windows
|
||||
# major = $current - $age
|
||||
# versuffix = $major
|
||||
math(EXPR _major "${current} - ${age}")
|
||||
set(_windows_name "secp256k1")
|
||||
if(MSVC)
|
||||
set(_windows_name "${PROJECT_NAME}")
|
||||
endif()
|
||||
set_target_properties(${target} PROPERTIES
|
||||
ARCHIVE_OUTPUT_NAME "${_windows_name}"
|
||||
RUNTIME_OUTPUT_NAME "${_windows_name}-${_major}"
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -1,24 +0,0 @@
|
||||
include(CheckCCompilerFlag)
|
||||
|
||||
function(secp256k1_check_c_flags_internal flags output)
|
||||
string(MAKE_C_IDENTIFIER "${flags}" result)
|
||||
string(TOUPPER "${result}" result)
|
||||
set(result "C_SUPPORTS_${result}")
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-Werror")
|
||||
endif()
|
||||
|
||||
# This avoids running a linker.
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
check_c_compiler_flag("${flags}" ${result})
|
||||
|
||||
set(${output} ${${result}} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Append flags to the COMPILE_OPTIONS directory property if CC accepts them.
|
||||
macro(try_append_c_flags)
|
||||
secp256k1_check_c_flags_internal("${ARGV}" result)
|
||||
if(result)
|
||||
add_compile_options(${ARGV})
|
||||
endif()
|
||||
endmacro()
|
||||
@@ -1,3 +0,0 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
|
||||
@@ -1,5 +0,0 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake")
|
||||
|
||||
check_required_components(@PROJECT_NAME@)
|
||||
@@ -1,9 +0,0 @@
|
||||
.syntax unified
|
||||
.eabi_attribute 24, 1
|
||||
.eabi_attribute 25, 1
|
||||
.text
|
||||
.global main
|
||||
main:
|
||||
ldr r0, =0x002A
|
||||
mov r7, #1
|
||||
swi 0
|
||||
@@ -1,3 +0,0 @@
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
||||
1100
configure.ac
1100
configure.ac
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,11 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
#include <secp256k1.h>
|
||||
|
||||
#include "lax_der_parsing.h"
|
||||
|
||||
@@ -111,6 +112,7 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
|
||||
return 0;
|
||||
}
|
||||
spos = pos;
|
||||
pos += slen;
|
||||
|
||||
/* Ignore leading zeroes in R */
|
||||
while (rlen > 0 && input[rpos] == 0) {
|
||||
@@ -120,7 +122,7 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
|
||||
/* Copy R value */
|
||||
if (rlen > 32) {
|
||||
overflow = 1;
|
||||
} else if (rlen) {
|
||||
} else {
|
||||
memcpy(tmpsig + 32 - rlen, input + rpos, rlen);
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
|
||||
/* Copy S value */
|
||||
if (slen > 32) {
|
||||
overflow = 1;
|
||||
} else if (slen) {
|
||||
} else {
|
||||
memcpy(tmpsig + 64 - slen, input + spos, slen);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
/****
|
||||
* Please do not link this file directly. It is not part of the libsecp256k1
|
||||
@@ -51,13 +51,7 @@
|
||||
#ifndef SECP256K1_CONTRIB_LAX_DER_PARSING_H
|
||||
#define SECP256K1_CONTRIB_LAX_DER_PARSING_H
|
||||
|
||||
/* #include secp256k1.h only when it hasn't been included yet.
|
||||
This enables this file to be #included directly in other project
|
||||
files (such as tests.c) without the need to set an explicit -I flag,
|
||||
which would be necessary to locate secp256k1.h. */
|
||||
#ifndef SECP256K1_H
|
||||
#include <secp256k1.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -67,8 +61,8 @@ extern "C" {
|
||||
*
|
||||
* Returns: 1 when the signature could be parsed, 0 otherwise.
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: sig: pointer to a signature object
|
||||
* In: input: pointer to the signature to be parsed
|
||||
* Out: sig: a pointer to a signature object
|
||||
* In: input: a pointer to the signature to be parsed
|
||||
* inputlen: the length of the array pointed to be input
|
||||
*
|
||||
* This function will accept any valid DER encoded signature, even if the
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2014, 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2014, 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
#include <secp256k1.h>
|
||||
|
||||
#include "lax_der_privatekey_parsing.h"
|
||||
|
||||
@@ -44,7 +45,7 @@ int ec_privkey_import_der(const secp256k1_context* ctx, unsigned char *out32, co
|
||||
if (end < privkey+2 || privkey[0] != 0x04 || privkey[1] > 0x20 || end < privkey+2+privkey[1]) {
|
||||
return 0;
|
||||
}
|
||||
if (privkey[1]) memcpy(out32 + 32 - privkey[1], privkey + 2, privkey[1]);
|
||||
memcpy(out32 + 32 - privkey[1], privkey + 2, privkey[1]);
|
||||
if (!secp256k1_ec_seckey_verify(ctx, out32)) {
|
||||
memset(out32, 0, 32);
|
||||
return 0;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2014, 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2014, 2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
/****
|
||||
* Please do not link this file directly. It is not part of the libsecp256k1
|
||||
@@ -28,13 +28,7 @@
|
||||
#ifndef SECP256K1_CONTRIB_BER_PRIVATEKEY_H
|
||||
#define SECP256K1_CONTRIB_BER_PRIVATEKEY_H
|
||||
|
||||
/* #include secp256k1.h only when it hasn't been included yet.
|
||||
This enables this file to be #included directly in other project
|
||||
files (such as tests.c) without the need to set an explicit -I flag,
|
||||
which would be necessary to locate secp256k1.h. */
|
||||
#ifndef SECP256K1_H
|
||||
#include <secp256k1.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -43,7 +37,8 @@ extern "C" {
|
||||
/** Export a private key in DER format.
|
||||
*
|
||||
* Returns: 1 if the private key was valid.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static).
|
||||
* Args: ctx: pointer to a context object, initialized for signing (cannot
|
||||
* be NULL)
|
||||
* Out: privkey: pointer to an array for storing the private key in BER.
|
||||
* Should have space for 279 bytes, and cannot be NULL.
|
||||
* privkeylen: Pointer to an int where the length of the private key in
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eou pipefail
|
||||
|
||||
help() {
|
||||
cat <<EOT
|
||||
$0: Prepare a pull request that syncs a branch with upstream
|
||||
|
||||
Usage:
|
||||
$0 [--switch] <base-branch> <upstream-ref>
|
||||
|
||||
This script creates a sync local branch pointing to <upstream-ref>. Moreover, it
|
||||
generates a helper script for opening a pull request (PR) merging the created
|
||||
local branch into <base-branch>.
|
||||
|
||||
The synced upstream PRs are listed in the title and the description of the PR.
|
||||
(This relies on upstream merging PRs using merge commits with titles of the form
|
||||
"Merge <repo>#<prnum>: ...".)
|
||||
|
||||
Arguments:
|
||||
--switch: Try to switch to the created sync branch
|
||||
<base-branch>: The branch to sync with upstream
|
||||
<upstream-ref>: The upstream ref to merge into <base-branch>
|
||||
|
||||
Usage examples:
|
||||
$0 --switch master upstream/master
|
||||
$0 master abc1234
|
||||
|
||||
To find candidate merge commits from <upstream-ref> (oldest first), use:
|
||||
git log --oneline --topo-order --reverse --merges \$(git merge-base <upstream-ref> <base-branch>)..<upstream-ref>
|
||||
EOT
|
||||
}
|
||||
|
||||
### Parse arguments
|
||||
SWITCH=false
|
||||
if [ "$#" -ge 1 ] && [ "$1" = "--switch" ]; then
|
||||
SWITCH=true
|
||||
shift
|
||||
fi
|
||||
if [ "$#" -ne 2 ]; then
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
BASE_BRANCH="$1"
|
||||
UPSTREAM_REF="$2"
|
||||
|
||||
### Create PR metadata
|
||||
TITLE="Upstream PRs"
|
||||
RANGESTART_COMMIT=$(git merge-base "$UPSTREAM_REF" "$BASE_BRANCH")
|
||||
RANGEEND_COMMIT=$(git rev-parse "$UPSTREAM_REF")
|
||||
COMMITS=$(git --no-pager log --pretty=format:%H --topo-order --reverse --merges "$RANGESTART_COMMIT".."$RANGEEND_COMMIT")
|
||||
# If there are no commits, exit successfully
|
||||
if [ -z "$COMMITS" ]; then
|
||||
echo "No merge commits in range ${RANGESTART_COMMIT}..${RANGEEND_COMMIT}" >&2
|
||||
exit 0
|
||||
fi
|
||||
BODY="${GITHUB_ACTIONS+*Note: This PR has been created by a GitHub Actions workflow without human involvement.*
|
||||
|
||||
}"
|
||||
BODY+="This PR syncs the following upstream PRs:"
|
||||
for COMMIT in $COMMITS; do
|
||||
PRNUM=$(git log -1 "$COMMIT" --pretty=format:%s | sed s/'Merge .*#\([0-9]*\):.*'/'\1'/)
|
||||
TITLE="$TITLE $PRNUM,"
|
||||
BODY=$(printf "%s\n * %s" "$BODY" "$(git log -1 "$COMMIT" --pretty=format:%s | sed s/'Merge '//)")
|
||||
done
|
||||
# Remove trailing ","
|
||||
TITLE=${TITLE%?}
|
||||
BODY+=$(cat <<'EOF'
|
||||
|
||||
|
||||
Usage hints:
|
||||
* If this PR has merge conflicts, resolve these by switching to the PR branch and merging the base branch into it using `git merge <base-branch>`.
|
||||
* To show the conflict resolution diff from an existing merge commit, use `git show --remerge-diff <merge-commit>`.
|
||||
* In case you are recreating the PR branch locally, you can (during the conflict resolution state) replay this conflict resolution diff using `git read-tree --reset -u <merge-commit>`.
|
||||
Be aware that this may discard your index as well as the uncommitted changes and untracked files in your worktree.
|
||||
EOF
|
||||
)
|
||||
|
||||
### Create a sync branch locally.
|
||||
SYNC_BRANCH="sync-$(git rev-parse --short "$UPSTREAM_REF")"
|
||||
# This will error out if the branch already exists, which is what we want.
|
||||
git branch --no-track "$SYNC_BRANCH" "$UPSTREAM_REF"
|
||||
|
||||
### Print the PR metadata
|
||||
echo "-----------------------------------"
|
||||
echo "$TITLE"
|
||||
echo "-----------------------------------"
|
||||
echo "$BODY"
|
||||
echo "-----------------------------------"
|
||||
|
||||
### Generate the helper script for creating the PR
|
||||
FNAME="gh-pr-create.sh"
|
||||
# Escape single quote ' -> '\''
|
||||
quote() {
|
||||
local quoted=${1//\'/\'\\\'\'}
|
||||
printf "%s" "$quoted"
|
||||
}
|
||||
TITLE=$(quote "$TITLE")
|
||||
BODY=$(quote "$BODY")
|
||||
cat <<EOT > "$FNAME"
|
||||
#!/bin/sh
|
||||
TITLE='$TITLE'
|
||||
BODY='$BODY'
|
||||
SYNC_BRANCH='$SYNC_BRANCH'
|
||||
BASE_BRANCH='$BASE_BRANCH'
|
||||
|
||||
gh pr create --base "\$BASE_BRANCH" --head "\$SYNC_BRANCH" --title "\$TITLE" --body "\$BODY" "\$@"
|
||||
EOT
|
||||
chmod +x "$FNAME"
|
||||
|
||||
echo "Successfully created local sync branch $SYNC_BRANCH starting at $UPSTREAM_REF."
|
||||
echo
|
||||
echo "You can now:"
|
||||
echo " 1. Optionally resolve merge conflicts by merging $BASE_BRANCH into $SYNC_BRANCH."
|
||||
echo " 2. Push $SYNC_BRANCH to some GitHub remote."
|
||||
echo " 3. Run ./$FNAME to create a pull request. (Tip: Pass --dry-run first.)"
|
||||
|
||||
if [ "${SWITCH:-false}" = true ]; then
|
||||
echo
|
||||
echo "Trying to switch to the sync branch..."
|
||||
echo
|
||||
git switch "$SYNC_BRANCH"
|
||||
fi
|
||||
483
doc/ellswift.md
483
doc/ellswift.md
@@ -1,483 +0,0 @@
|
||||
# ElligatorSwift for secp256k1 explained
|
||||
|
||||
In this document we explain how the `ellswift` module implementation is related to the
|
||||
construction in the
|
||||
["SwiftEC: Shallue–van de Woestijne Indifferentiable Function To Elliptic Curves"](https://eprint.iacr.org/2022/759)
|
||||
paper by Jorge Chávez-Saab, Francisco Rodríguez-Henríquez, and Mehdi Tibouchi.
|
||||
|
||||
* [1. Introduction](#1-introduction)
|
||||
* [2. The decoding function](#2-the-decoding-function)
|
||||
+ [2.1 Decoding for `secp256k1`](#21-decoding-for-secp256k1)
|
||||
* [3. The encoding function](#3-the-encoding-function)
|
||||
+ [3.1 Switching to *v, w* coordinates](#31-switching-to-v-w-coordinates)
|
||||
+ [3.2 Avoiding computing all inverses](#32-avoiding-computing-all-inverses)
|
||||
+ [3.3 Finding the inverse](#33-finding-the-inverse)
|
||||
+ [3.4 Dealing with special cases](#34-dealing-with-special-cases)
|
||||
+ [3.5 Encoding for `secp256k1`](#35-encoding-for-secp256k1)
|
||||
* [4. Encoding and decoding full *(x, y)* coordinates](#4-encoding-and-decoding-full-x-y-coordinates)
|
||||
+ [4.1 Full *(x, y)* coordinates for `secp256k1`](#41-full-x-y-coordinates-for-secp256k1)
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
The `ellswift` module effectively introduces a new 64-byte public key format, with the property
|
||||
that (uniformly random) public keys can be encoded as 64-byte arrays which are computationally
|
||||
indistinguishable from uniform byte arrays. The module provides functions to convert public keys
|
||||
from and to this format, as well as convenience functions for key generation and ECDH that operate
|
||||
directly on ellswift-encoded keys.
|
||||
|
||||
The encoding consists of the concatenation of two (32-byte big endian) encoded field elements $u$
|
||||
and $t.$ Together they encode an x-coordinate on the curve $x$, or (see further) a full point $(x, y)$ on
|
||||
the curve.
|
||||
|
||||
**Decoding** consists of decoding the field elements $u$ and $t$ (values above the field size $p$
|
||||
are taken modulo $p$), and then evaluating $F_u(t)$, which for every $u$ and $t$ results in a valid
|
||||
x-coordinate on the curve. The functions $F_u$ will be defined in [Section 2](#2-the-decoding-function).
|
||||
|
||||
**Encoding** a given $x$ coordinate is conceptually done as follows:
|
||||
* Loop:
|
||||
* Pick a uniformly random field element $u.$
|
||||
* Compute the set $L = F_u^{-1}(x)$ of $t$ values for which $F_u(t) = x$, which may have up to *8* elements.
|
||||
* With probability $1 - \dfrac{\\#L}{8}$, restart the loop.
|
||||
* Select a uniformly random $t \in L$ and return $(u, t).$
|
||||
|
||||
This is the *ElligatorSwift* algorithm, here given for just x-coordinates. An extension to full
|
||||
$(x, y)$ points will be given in [Section 4](#4-encoding-and-decoding-full-x-y-coordinates).
|
||||
The algorithm finds a uniformly random $(u, t)$ among (almost all) those
|
||||
for which $F_u(t) = x.$ Section 3.2 in the paper proves that the number of such encodings for
|
||||
almost all x-coordinates on the curve (all but at most 39) is close to two times the field size
|
||||
(specifically, it lies in the range $2q \pm (22\sqrt{q} + O(1))$, where $q$ is the size of the field).
|
||||
|
||||
## 2. The decoding function
|
||||
|
||||
First some definitions:
|
||||
* $\mathbb{F}$ is the finite field of size $q$, of characteristic 5 or more, and $q \equiv 1 \mod 3.$
|
||||
* For `secp256k1`, $q = 2^{256} - 2^{32} - 977$, which satisfies that requirement.
|
||||
* Let $E$ be the elliptic curve of points $(x, y) \in \mathbb{F}^2$ for which $y^2 = x^3 + ax + b$, with $a$ and $b$
|
||||
public constants, for which $\Delta_E = -16(4a^3 + 27b^2)$ is a square, and at least one of $(-b \pm \sqrt{-3 \Delta_E} / 36)/2$ is a square.
|
||||
This implies that the order of $E$ is either odd, or a multiple of *4*.
|
||||
If $a=0$, this condition is always fulfilled.
|
||||
* For `secp256k1`, $a=0$ and $b=7.$
|
||||
* Let the function $g(x) = x^3 + ax + b$, so the $E$ curve equation is also $y^2 = g(x).$
|
||||
* Let the function $h(x) = 3x^3 + 4a.$
|
||||
* Define $V$ as the set of solutions $(x_1, x_2, x_3, z)$ to $z^2 = g(x_1)g(x_2)g(x_3).$
|
||||
* Define $S_u$ as the set of solutions $(X, Y)$ to $X^2 + h(u)Y^2 = -g(u)$ and $Y \neq 0.$
|
||||
* $P_u$ is a function from $\mathbb{F}$ to $S_u$ that will be defined below.
|
||||
* $\psi_u$ is a function from $S_u$ to $V$ that will be defined below.
|
||||
|
||||
**Note**: In the paper:
|
||||
* $F_u$ corresponds to $F_{0,u}$ there.
|
||||
* $P_u(t)$ is called $P$ there.
|
||||
* All $S_u$ sets together correspond to $S$ there.
|
||||
* All $\psi_u$ functions together (operating on elements of $S$) correspond to $\psi$ there.
|
||||
|
||||
Note that for $V$, the left hand side of the equation $z^2$ is square, and thus the right
|
||||
hand must also be square. As multiplying non-squares results in a square in $\mathbb{F}$,
|
||||
out of the three right-hand side factors an even number must be non-squares.
|
||||
This implies that exactly *1* or exactly *3* out of
|
||||
$\\{g(x_1), g(x_2), g(x_3)\\}$ must be square, and thus that for any $(x_1,x_2,x_3,z) \in V$,
|
||||
at least one of $\\{x_1, x_2, x_3\\}$ must be a valid x-coordinate on $E.$ There is one exception
|
||||
to this, namely when $z=0$, but even then one of the three values is a valid x-coordinate.
|
||||
|
||||
**Define** the decoding function $F_u(t)$ as:
|
||||
* Let $(x_1, x_2, x_3, z) = \psi_u(P_u(t)).$
|
||||
* Return the first element $x$ of $(x_3, x_2, x_1)$ which is a valid x-coordinate on $E$ (i.e., $g(x)$ is square).
|
||||
|
||||
$P_u(t) = (X(u, t), Y(u, t))$, where:
|
||||
|
||||
$$
|
||||
\begin{array}{lcl}
|
||||
X(u, t) & = & \left\\{\begin{array}{ll}
|
||||
\dfrac{g(u) - t^2}{2t} & a = 0 \\
|
||||
\dfrac{g(u) + h(u)(Y_0(u) - X_0(u)t)^2}{X_0(u)(1 + h(u)t^2)} & a \neq 0
|
||||
\end{array}\right. \\
|
||||
Y(u, t) & = & \left\\{\begin{array}{ll}
|
||||
\dfrac{X(u, t) + t}{u \sqrt{-3}} = \dfrac{g(u) + t^2}{2tu\sqrt{-3}} & a = 0 \\
|
||||
Y_0(u) + t(X(u, t) - X_0(u)) & a \neq 0
|
||||
\end{array}\right.
|
||||
\end{array}
|
||||
$$
|
||||
|
||||
$P_u(t)$ is defined:
|
||||
* For $a=0$, unless:
|
||||
* $u = 0$ or $t = 0$ (division by zero)
|
||||
* $g(u) = -t^2$ (would give $Y=0$).
|
||||
* For $a \neq 0$, unless:
|
||||
* $X_0(u) = 0$ or $h(u)t^2 = -1$ (division by zero)
|
||||
* $Y_0(u) (1 - h(u)t^2) = 2X_0(u)t$ (would give $Y=0$).
|
||||
|
||||
The functions $X_0(u)$ and $Y_0(u)$ are defined in Appendix A of the paper, and depend on various properties of $E.$
|
||||
|
||||
The function $\psi_u$ is the same for all curves: $\psi_u(X, Y) = (x_1, x_2, x_3, z)$, where:
|
||||
|
||||
$$
|
||||
\begin{array}{lcl}
|
||||
x_1 & = & \dfrac{X}{2Y} - \dfrac{u}{2} && \\
|
||||
x_2 & = & -\dfrac{X}{2Y} - \dfrac{u}{2} && \\
|
||||
x_3 & = & u + 4Y^2 && \\
|
||||
z & = & \dfrac{g(x_3)}{2Y}(u^2 + ux_1 + x_1^2 + a) = \dfrac{-g(u)g(x_3)}{8Y^3}
|
||||
\end{array}
|
||||
$$
|
||||
|
||||
### 2.1 Decoding for `secp256k1`
|
||||
|
||||
Put together and specialized for $a=0$ curves, decoding $(u, t)$ to an x-coordinate is:
|
||||
|
||||
**Define** $F_u(t)$ as:
|
||||
* Let $X = \dfrac{u^3 + b - t^2}{2t}.$
|
||||
* Let $Y = \dfrac{X + t}{u\sqrt{-3}}.$
|
||||
* Return the first $x$ in $(u + 4Y^2, \dfrac{-X}{2Y} - \dfrac{u}{2}, \dfrac{X}{2Y} - \dfrac{u}{2})$ for which $g(x)$ is square.
|
||||
|
||||
To make sure that every input decodes to a valid x-coordinate, we remap the inputs in case
|
||||
$P_u$ is not defined (when $u=0$, $t=0$, or $g(u) = -t^2$):
|
||||
|
||||
**Define** $F_u(t)$ as:
|
||||
* Let $u'=u$ if $u \neq 0$; $1$ otherwise (guaranteeing $u' \neq 0$).
|
||||
* Let $t'=t$ if $t \neq 0$; $1$ otherwise (guaranteeing $t' \neq 0$).
|
||||
* Let $t''=t'$ if $g(u') \neq -t'^2$; $2t'$ otherwise (guaranteeing $t'' \neq 0$ and $g(u') \neq -t''^2$).
|
||||
* Let $X = \dfrac{u'^3 + b - t''^2}{2t''}.$
|
||||
* Let $Y = \dfrac{X + t''}{u'\sqrt{-3}}.$
|
||||
* Return the first $x$ in $(u' + 4Y^2, \dfrac{-X}{2Y} - \dfrac{u'}{2}, \dfrac{X}{2Y} - \dfrac{u'}{2})$ for which $x^3 + b$ is square.
|
||||
|
||||
The choices here are not strictly necessary. Just returning a fixed constant in any of the undefined cases would suffice,
|
||||
but the approach here is simple enough and gives fairly uniform output even in these cases.
|
||||
|
||||
**Note**: in the paper these conditions result in $\infty$ as output, due to the use of projective coordinates there.
|
||||
We wish to avoid the need for callers to deal with this special case.
|
||||
|
||||
This is implemented in `secp256k1_ellswift_xswiftec_frac_var` (which decodes to an x-coordinate represented as a fraction), and
|
||||
in `secp256k1_ellswift_xswiftec_var` (which outputs the actual x-coordinate).
|
||||
|
||||
## 3. The encoding function
|
||||
|
||||
To implement $F_u^{-1}(x)$, the function to find the set of inverses $t$ for which $F_u(t) = x$, we have to reverse the process:
|
||||
* Find all the $(X, Y) \in S_u$ that could have given rise to $x$, through the $x_1$, $x_2$, or $x_3$ formulas in $\psi_u.$
|
||||
* Map those $(X, Y)$ solutions to $t$ values using $P_u^{-1}(X, Y).$
|
||||
* For each of the found $t$ values, verify that $F_u(t) = x.$
|
||||
* Return the remaining $t$ values.
|
||||
|
||||
The function $P_u^{-1}$, which finds $t$ given $(X, Y) \in S_u$, is significantly simpler than $P_u:$
|
||||
|
||||
$$
|
||||
P_u^{-1}(X, Y) = \left\\{\begin{array}{ll}
|
||||
Yu\sqrt{-3} - X & a = 0 \\
|
||||
\dfrac{Y-Y_0(u)}{X-X_0(u)} & a \neq 0 \land X \neq X_0(u) \\
|
||||
\dfrac{-X_0(u)}{h(u)Y_0(u)} & a \neq 0 \land X = X_0(u) \land Y = Y_0(u)
|
||||
\end{array}\right.
|
||||
$$
|
||||
|
||||
The third step above, verifying that $F_u(t) = x$, is necessary because for the $(X, Y)$ values found through the $x_1$ and $x_2$ expressions,
|
||||
it is possible that decoding through $\psi_u(X, Y)$ yields a valid $x_3$ on the curve, which would take precedence over the
|
||||
$x_1$ or $x_2$ decoding. These $(X, Y)$ solutions must be rejected.
|
||||
|
||||
Since we know that exactly one or exactly three out of $\\{x_1, x_2, x_3\\}$ are valid x-coordinates for any $t$,
|
||||
the case where either $x_1$ or $x_2$ is valid and in addition also $x_3$ is valid must mean that all three are valid.
|
||||
This means that instead of checking whether $x_3$ is on the curve, it is also possible to check whether the other one out of
|
||||
$x_1$ and $x_2$ is on the curve. This is significantly simpler, as it turns out.
|
||||
|
||||
Observe that $\psi_u$ guarantees that $x_1 + x_2 = -u.$ So given either $x = x_1$ or $x = x_2$, the other one of the two can be computed as
|
||||
$-u - x.$ Thus, when encoding $x$ through the $x_1$ or $x_2$ expressions, one can simply check whether $g(-u-x)$ is a square,
|
||||
and if so, not include the corresponding $t$ values in the returned set. As this does not need $X$, $Y$, or $t$, this condition can be determined
|
||||
before those values are computed.
|
||||
|
||||
It is not possible that an encoding found through the $x_1$ expression decodes to a different valid x-coordinate using $x_2$ (which would
|
||||
take precedence), for the same reason: if both $x_1$ and $x_2$ decodings were valid, $x_3$ would be valid as well, and thus take
|
||||
precedence over both. Because of this, the $g(-u-x)$ being square test for $x_1$ and $x_2$ is the only test necessary to guarantee the found $t$
|
||||
values round-trip back to the input $x$ correctly. This is the reason for choosing the $(x_3, x_2, x_1)$ precedence order in the decoder;
|
||||
any order which does not place $x_3$ first requires more complicated round-trip checks in the encoder.
|
||||
|
||||
### 3.1 Switching to *v, w* coordinates
|
||||
|
||||
Before working out the formulas for all this, we switch to different variables for $S_u.$ Let $v = (X/Y - u)/2$, and
|
||||
$w = 2Y.$ Or in the other direction, $X = w(u/2 + v)$ and $Y = w/2:$
|
||||
* $S_u'$ becomes the set of $(v, w)$ for which $w^2 (u^2 + uv + v^2 + a) = -g(u)$ and $w \neq 0.$
|
||||
* For $a=0$ curves, $P_u^{-1}$ can be stated for $(v,w)$ as $P_u^{'-1}(v, w) = w\left(\frac{\sqrt{-3}-1}{2}u - v\right).$
|
||||
* $\psi_u$ can be stated for $(v, w)$ as $\psi_u'(v, w) = (x_1, x_2, x_3, z)$, where
|
||||
|
||||
$$
|
||||
\begin{array}{lcl}
|
||||
x_1 & = & v \\
|
||||
x_2 & = & -u - v \\
|
||||
x_3 & = & u + w^2 \\
|
||||
z & = & \dfrac{g(x_3)}{w}(u^2 + uv + v^2 + a) = \dfrac{-g(u)g(x_3)}{w^3}
|
||||
\end{array}
|
||||
$$
|
||||
|
||||
We can now write the expressions for finding $(v, w)$ given $x$ explicitly, by solving each of the $\\{x_1, x_2, x_3\\}$
|
||||
expressions for $v$ or $w$, and using the $S_u'$ equation to find the other variable:
|
||||
* Assuming $x = x_1$, we find $v = x$ and $w = \pm\sqrt{-g(u)/(u^2 + uv + v^2 + a)}$ (two solutions).
|
||||
* Assuming $x = x_2$, we find $v = -u-x$ and $w = \pm\sqrt{-g(u)/(u^2 + uv + v^2 + a)}$ (two solutions).
|
||||
* Assuming $x = x_3$, we find $w = \pm\sqrt{x-u}$ and $v = -u/2 \pm \sqrt{-w^2(4g(u) + w^2h(u))}/(2w^2)$ (four solutions).
|
||||
|
||||
### 3.2 Avoiding computing all inverses
|
||||
|
||||
The *ElligatorSwift* algorithm as stated in Section 1 requires the computation of $L = F_u^{-1}(x)$ (the
|
||||
set of all $t$ such that $(u, t)$ decode to $x$) in full. This is unnecessary.
|
||||
|
||||
Observe that the procedure of restarting with probability $(1 - \frac{\\#L}{8})$ and otherwise returning a
|
||||
uniformly random element from $L$ is actually equivalent to always padding $L$ with $\bot$ values up to length 8,
|
||||
picking a uniformly random element from that, restarting whenever $\bot$ is picked:
|
||||
|
||||
**Define** *ElligatorSwift(x)* as:
|
||||
* Loop:
|
||||
* Pick a uniformly random field element $u.$
|
||||
* Compute the set $L = F_u^{-1}(x).$
|
||||
* Let $T$ be the 8-element vector consisting of the elements of $L$, plus $8 - \\#L$ times $\\{\bot\\}.$
|
||||
* Select a uniformly random $t \in T.$
|
||||
* If $t \neq \bot$, return $(u, t)$; restart loop otherwise.
|
||||
|
||||
Now notice that the order of elements in $T$ does not matter, as all we do is pick a uniformly
|
||||
random element in it, so we do not need to have all $\bot$ values at the end.
|
||||
As we have 8 distinct formulas for finding $(v, w)$ (taking the variants due to $\pm$ into account),
|
||||
we can associate every index in $T$ with exactly one of those formulas, making sure that:
|
||||
* Formulas that yield no solutions (due to division by zero or non-existing square roots) or invalid solutions are made to return $\bot.$
|
||||
* For the $x_1$ and $x_2$ cases, if $g(-u-x)$ is a square, $\bot$ is returned instead (the round-trip check).
|
||||
* In case multiple formulas would return the same non- $\bot$ result, all but one of those must be turned into $\bot$ to avoid biasing those.
|
||||
|
||||
The last condition above only occurs with negligible probability for cryptographically-sized curves, but is interesting
|
||||
to take into account as it allows exhaustive testing in small groups. See [Section 3.4](#34-dealing-with-special-cases)
|
||||
for an analysis of all the negligible cases.
|
||||
|
||||
If we define $T = (G_{0,u}(x), G_{1,u}(x), \ldots, G_{7,u}(x))$, with each $G_{i,u}$ matching one of the formulas,
|
||||
the loop can be simplified to only compute one of the inverses instead of all of them:
|
||||
|
||||
**Define** *ElligatorSwift(x)* as:
|
||||
* Loop:
|
||||
* Pick a uniformly random field element $u.$
|
||||
* Pick a uniformly random integer $c$ in $[0,8).$
|
||||
* Let $t = G_{c,u}(x).$
|
||||
* If $t \neq \bot$, return $(u, t)$; restart loop otherwise.
|
||||
|
||||
This is implemented in `secp256k1_ellswift_xelligatorswift_var`.
|
||||
|
||||
### 3.3 Finding the inverse
|
||||
|
||||
To implement $G_{c,u}$, we map $c=0$ to the $x_1$ formula, $c=1$ to the $x_2$ formula, and $c=2$ and $c=3$ to the $x_3$ formula.
|
||||
Those are then repeated as $c=4$ through $c=7$ for the other sign of $w$ (noting that in each formula, $w$ is a square root of some expression).
|
||||
Ignoring the negligible cases, we get:
|
||||
|
||||
**Define** $G_{c,u}(x)$ as:
|
||||
* If $c \in \\{0, 1, 4, 5\\}$ (for $x_1$ and $x_2$ formulas):
|
||||
* If $g(-u-x)$ is square, return $\bot$ (as $x_3$ would be valid and take precedence).
|
||||
* If $c \in \\{0, 4\\}$ (the $x_1$ formula) let $v = x$, otherwise let $v = -u-x$ (the $x_2$ formula)
|
||||
* Let $s = -g(u)/(u^2 + uv + v^2 + a)$ (using $s = w^2$ in what follows).
|
||||
* Otherwise, when $c \in \\{2, 3, 6, 7\\}$ (for $x_3$ formulas):
|
||||
* Let $s = x-u.$
|
||||
* Let $r = \sqrt{-s(4g(u) + sh(u))}.$
|
||||
* Let $v = (r/s - u)/2$ if $c \in \\{3, 7\\}$; $(-r/s - u)/2$ otherwise.
|
||||
* Let $w = \sqrt{s}.$
|
||||
* Depending on $c:$
|
||||
* If $c \in \\{0, 1, 2, 3\\}:$ return $P_u^{'-1}(v, w).$
|
||||
* If $c \in \\{4, 5, 6, 7\\}:$ return $P_u^{'-1}(v, -w).$
|
||||
|
||||
Whenever a square root of a non-square is taken, $\bot$ is returned; for both square roots this happens with roughly
|
||||
50% on random inputs. Similarly, when a division by 0 would occur, $\bot$ is returned as well; this will only happen
|
||||
with negligible probability. A division by 0 in the first branch in fact cannot occur at all, because $u^2 + uv + v^2 + a = 0$
|
||||
implies $g(-u-x) = g(x)$ which would mean the $g(-u-x)$ is square condition has triggered
|
||||
and $\bot$ would have been returned already.
|
||||
|
||||
**Note**: In the paper, the $case$ variable corresponds roughly to the $c$ above, but only takes on 4 possible values (1 to 4).
|
||||
The conditional negation of $w$ at the end is done randomly, which is equivalent, but makes testing harder. We choose to
|
||||
have the $G_{c,u}$ be deterministic, and capture all choices in $c.$
|
||||
|
||||
Now observe that the $c \in \\{1, 5\\}$ and $c \in \\{3, 7\\}$ conditions effectively perform the same $v \rightarrow -u-v$
|
||||
transformation. Furthermore, that transformation has no effect on $s$ in the first branch
|
||||
as $u^2 + ux + x^2 + a = u^2 + u(-u-x) + (-u-x)^2 + a.$ Thus we can extract it out and move it down:
|
||||
|
||||
**Define** $G_{c,u}(x)$ as:
|
||||
* If $c \in \\{0, 1, 4, 5\\}:$
|
||||
* If $g(-u-x)$ is square, return $\bot.$
|
||||
* Let $s = -g(u)/(u^2 + ux + x^2 + a).$
|
||||
* Let $v = x.$
|
||||
* Otherwise, when $c \in \\{2, 3, 6, 7\\}:$
|
||||
* Let $s = x-u.$
|
||||
* Let $r = \sqrt{-s(4g(u) + sh(u))}.$
|
||||
* Let $v = (r/s - u)/2.$
|
||||
* Let $w = \sqrt{s}.$
|
||||
* Depending on $c:$
|
||||
* If $c \in \\{0, 2\\}:$ return $P_u^{'-1}(v, w).$
|
||||
* If $c \in \\{1, 3\\}:$ return $P_u^{'-1}(-u-v, w).$
|
||||
* If $c \in \\{4, 6\\}:$ return $P_u^{'-1}(v, -w).$
|
||||
* If $c \in \\{5, 7\\}:$ return $P_u^{'-1}(-u-v, -w).$
|
||||
|
||||
This shows there will always be exactly 0, 4, or 8 $t$ values for a given $(u, x)$ input.
|
||||
There can be 0, 1, or 2 $(v, w)$ pairs before invoking $P_u^{'-1}$, and each results in 4 distinct $t$ values.
|
||||
|
||||
### 3.4 Dealing with special cases
|
||||
|
||||
As mentioned before there are a few cases to deal with which only happen in a negligibly small subset of inputs.
|
||||
For cryptographically sized fields, if only random inputs are going to be considered, it is unnecessary to deal with these. Still, for completeness
|
||||
we analyse them here. They generally fall into two categories: cases in which the encoder would produce $t$ values that
|
||||
do not decode back to $x$ (or at least cannot guarantee that they do), and cases in which the encoder might produce the same
|
||||
$t$ value for multiple $c$ inputs (thereby biasing that encoding):
|
||||
|
||||
* In the branch for $x_1$ and $x_2$ (where $c \in \\{0, 1, 4, 5\\}$):
|
||||
* When $g(u) = 0$, we would have $s=w=Y=0$, which is not on $S_u.$ This is only possible on even-ordered curves.
|
||||
Excluding this also removes the one condition under which the simplified check for $x_3$ on the curve
|
||||
fails (namely when $g(x_1)=g(x_2)=0$ but $g(x_3)$ is not square).
|
||||
This does exclude some valid encodings: when both $g(u)=0$ and $u^2+ux+x^2+a=0$ (also implying $g(x)=0$),
|
||||
the $S_u'$ equation degenerates to $0 = 0$, and many valid $t$ values may exist. Yet, these cannot be targeted uniformly by the
|
||||
encoder anyway as there will generally be more than 8.
|
||||
* When $g(x) = 0$, the same $t$ would be produced as in the $x_3$ branch (where $c \in \\{2, 3, 6, 7\\}$) which we give precedence
|
||||
as it can deal with $g(u)=0$.
|
||||
This is again only possible on even-ordered curves.
|
||||
* In the branch for $x_3$ (where $c \in \\{2, 3, 6, 7\\}$):
|
||||
* When $s=0$, a division by zero would occur.
|
||||
* When $v = -u-v$ and $c \in \\{3, 7\\}$, the same $t$ would be returned as in the $c \in \\{2, 6\\}$ cases.
|
||||
It is equivalent to checking whether $r=0$.
|
||||
This cannot occur in the $x_1$ or $x_2$ branches, as it would trigger the $g(-u-x)$ is square condition.
|
||||
A similar concern for $w = -w$ does not exist, as $w=0$ is already impossible in both branches: in the first
|
||||
it requires $g(u)=0$ which is already outlawed on even-ordered curves and impossible on others; in the second it would trigger division by zero.
|
||||
* Curve-specific special cases also exist that need to be rejected, because they result in $(u,t)$ which is invalid to the decoder, or because of division by zero in the encoder:
|
||||
* For $a=0$ curves, when $u=0$ or when $t=0$. The latter can only be reached by the encoder when $g(u)=0$, which requires an even-ordered curve.
|
||||
* For $a \neq 0$ curves, when $X_0(u)=0$, when $h(u)t^2 = -1$, or when $w(u + 2v) = 2X_0(u)$ while also either $w \neq 2Y_0(u)$ or $h(u)=0$.
|
||||
|
||||
**Define** a version of $G_{c,u}(x)$ which deals with all these cases:
|
||||
* If $a=0$ and $u=0$, return $\bot.$
|
||||
* If $a \neq 0$ and $X_0(u)=0$, return $\bot.$
|
||||
* If $c \in \\{0, 1, 4, 5\\}:$
|
||||
* If $g(u) = 0$ or $g(x) = 0$, return $\bot$ (even curves only).
|
||||
* If $g(-u-x)$ is square, return $\bot.$
|
||||
* Let $s = -g(u)/(u^2 + ux + x^2 + a)$ (cannot cause division by zero).
|
||||
* Let $v = x.$
|
||||
* Otherwise, when $c \in \\{2, 3, 6, 7\\}:$
|
||||
* Let $s = x-u.$
|
||||
* Let $r = \sqrt{-s(4g(u) + sh(u))}$; return $\bot$ if not square.
|
||||
* If $c \in \\{3, 7\\}$ and $r=0$, return $\bot.$
|
||||
* If $s = 0$, return $\bot.$
|
||||
* Let $v = (r/s - u)/2.$
|
||||
* Let $w = \sqrt{s}$; return $\bot$ if not square.
|
||||
* If $a \neq 0$ and $w(u+2v) = 2X_0(u)$ and either $w \neq 2Y_0(u)$ or $h(u) = 0$, return $\bot.$
|
||||
* Depending on $c:$
|
||||
* If $c \in \\{0, 2\\}$, let $t = P_u^{'-1}(v, w).$
|
||||
* If $c \in \\{1, 3\\}$, let $t = P_u^{'-1}(-u-v, w).$
|
||||
* If $c \in \\{4, 6\\}$, let $t = P_u^{'-1}(v, -w).$
|
||||
* If $c \in \\{5, 7\\}$, let $t = P_u^{'-1}(-u-v, -w).$
|
||||
* If $a=0$ and $t=0$, return $\bot$ (even curves only).
|
||||
* If $a \neq 0$ and $h(u)t^2 = -1$, return $\bot.$
|
||||
* Return $t.$
|
||||
|
||||
Given any $u$, using this algorithm over all $x$ and $c$ values, every $t$ value will be reached exactly once,
|
||||
for an $x$ for which $F_u(t) = x$ holds, except for these cases that will not be reached:
|
||||
* All cases where $P_u(t)$ is not defined:
|
||||
* For $a=0$ curves, when $u=0$, $t=0$, or $g(u) = -t^2.$
|
||||
* For $a \neq 0$ curves, when $h(u)t^2 = -1$, $X_0(u) = 0$, or $Y_0(u) (1 - h(u) t^2) = 2X_0(u)t.$
|
||||
* When $g(u)=0$, the potentially many $t$ values that decode to an $x$ satisfying $g(x)=0$ using the $x_2$ formula. These were excluded by the $g(u)=0$ condition in the $c \in \\{0, 1, 4, 5\\}$ branch.
|
||||
|
||||
These cases form a negligible subset of all $(u, t)$ for cryptographically sized curves.
|
||||
|
||||
### 3.5 Encoding for `secp256k1`
|
||||
|
||||
Specialized for odd-ordered $a=0$ curves:
|
||||
|
||||
**Define** $G_{c,u}(x)$ as:
|
||||
* If $u=0$, return $\bot.$
|
||||
* If $c \in \\{0, 1, 4, 5\\}:$
|
||||
* If $(-u-x)^3 + b$ is square, return $\bot$
|
||||
* Let $s = -(u^3 + b)/(u^2 + ux + x^2)$ (cannot cause division by 0).
|
||||
* Let $v = x.$
|
||||
* Otherwise, when $c \in \\{2, 3, 6, 7\\}:$
|
||||
* Let $s = x-u.$
|
||||
* Let $r = \sqrt{-s(4(u^3 + b) + 3su^2)}$; return $\bot$ if not square.
|
||||
* If $c \in \\{3, 7\\}$ and $r=0$, return $\bot.$
|
||||
* If $s = 0$, return $\bot.$
|
||||
* Let $v = (r/s - u)/2.$
|
||||
* Let $w = \sqrt{s}$; return $\bot$ if not square.
|
||||
* Depending on $c:$
|
||||
* If $c \in \\{0, 2\\}:$ return $w(\frac{\sqrt{-3}-1}{2}u - v).$
|
||||
* If $c \in \\{1, 3\\}:$ return $w(\frac{\sqrt{-3}+1}{2}u + v).$
|
||||
* If $c \in \\{4, 6\\}:$ return $w(\frac{-\sqrt{-3}+1}{2}u + v).$
|
||||
* If $c \in \\{5, 7\\}:$ return $w(\frac{-\sqrt{-3}-1}{2}u - v).$
|
||||
|
||||
This is implemented in `secp256k1_ellswift_xswiftec_inv_var`.
|
||||
|
||||
And the x-only ElligatorSwift encoding algorithm is still:
|
||||
|
||||
**Define** *ElligatorSwift(x)* as:
|
||||
* Loop:
|
||||
* Pick a uniformly random field element $u.$
|
||||
* Pick a uniformly random integer $c$ in $[0,8).$
|
||||
* Let $t = G_{c,u}(x).$
|
||||
* If $t \neq \bot$, return $(u, t)$; restart loop otherwise.
|
||||
|
||||
Note that this logic does not take the remapped $u=0$, $t=0$, and $g(u) = -t^2$ cases into account; it just avoids them.
|
||||
While it is not impossible to make the encoder target them, this would increase the maximum number of $t$ values for a given $(u, x)$
|
||||
combination beyond 8, and thereby slow down the ElligatorSwift loop proportionally, for a negligible gain in uniformity.
|
||||
|
||||
## 4. Encoding and decoding full *(x, y)* coordinates
|
||||
|
||||
So far we have only addressed encoding and decoding x-coordinates, but in some cases an encoding
|
||||
for full points with $(x, y)$ coordinates is desirable. It is possible to encode this information
|
||||
in $t$ as well.
|
||||
|
||||
Note that for any $(X, Y) \in S_u$, $(\pm X, \pm Y)$ are all on $S_u.$ Moreover, all of these are
|
||||
mapped to the same x-coordinate. Negating $X$ or negating $Y$ just results in $x_1$ and $x_2$
|
||||
being swapped, and does not affect $x_3.$ This will not change the outcome x-coordinate as the order
|
||||
of $x_1$ and $x_2$ only matters if both were to be valid, and in that case $x_3$ would be used instead.
|
||||
|
||||
Still, these four $(X, Y)$ combinations all correspond to distinct $t$ values, so we can encode
|
||||
the sign of the y-coordinate in the sign of $X$ or the sign of $Y.$ They correspond to the
|
||||
four distinct $P_u^{'-1}$ calls in the definition of $G_{u,c}.$
|
||||
|
||||
**Note**: In the paper, the sign of the y coordinate is encoded in a separately-coded bit.
|
||||
|
||||
To encode the sign of $y$ in the sign of $Y:$
|
||||
|
||||
**Define** *Decode(u, t)* for full $(x, y)$ as:
|
||||
* Let $(X, Y) = P_u(t).$
|
||||
* Let $x$ be the first value in $(u + 4Y^2, \frac{-X}{2Y} - \frac{u}{2}, \frac{X}{2Y} - \frac{u}{2})$ for which $g(x)$ is square.
|
||||
* Let $y = \sqrt{g(x)}.$
|
||||
* If $sign(y) = sign(Y)$, return $(x, y)$; otherwise return $(x, -y).$
|
||||
|
||||
And encoding would be done using a $G_{c,u}(x, y)$ function defined as:
|
||||
|
||||
**Define** $G_{c,u}(x, y)$ as:
|
||||
* If $c \in \\{0, 1\\}:$
|
||||
* If $g(u) = 0$ or $g(x) = 0$, return $\bot$ (even curves only).
|
||||
* If $g(-u-x)$ is square, return $\bot.$
|
||||
* Let $s = -g(u)/(u^2 + ux + x^2 + a)$ (cannot cause division by zero).
|
||||
* Let $v = x.$
|
||||
* Otherwise, when $c \in \\{2, 3\\}:$
|
||||
* Let $s = x-u.$
|
||||
* Let $r = \sqrt{-s(4g(u) + sh(u))}$; return $\bot$ if not square.
|
||||
* If $c = 3$ and $r = 0$, return $\bot.$
|
||||
* Let $v = (r/s - u)/2.$
|
||||
* Let $w = \sqrt{s}$; return $\bot$ if not square.
|
||||
* Let $w' = w$ if $sign(w/2) = sign(y)$; $-w$ otherwise.
|
||||
* Depending on $c:$
|
||||
* If $c \in \\{0, 2\\}:$ return $P_u^{'-1}(v, w').$
|
||||
* If $c \in \\{1, 3\\}:$ return $P_u^{'-1}(-u-v, w').$
|
||||
|
||||
Note that $c$ now only ranges $[0,4)$, as the sign of $w'$ is decided based on that of $y$, rather than on $c.$
|
||||
This change makes some valid encodings unreachable: when $y = 0$ and $sign(Y) \neq sign(0)$.
|
||||
|
||||
In the above logic, $sign$ can be implemented in several ways, such as parity of the integer representation
|
||||
of the input field element (for prime-sized fields) or the quadratic residuosity (for fields where
|
||||
$-1$ is not square). The choice does not matter, as long as it only takes on two possible values, and for $x \neq 0$ it holds that $sign(x) \neq sign(-x)$.
|
||||
|
||||
### 4.1 Full *(x, y)* coordinates for `secp256k1`
|
||||
|
||||
For $a=0$ curves, there is another option. Note that for those,
|
||||
the $P_u(t)$ function translates negations of $t$ to negations of (both) $X$ and $Y.$ Thus, we can use $sign(t)$ to
|
||||
encode the y-coordinate directly. Combined with the earlier remapping to guarantee all inputs land on the curve, we get
|
||||
as decoder:
|
||||
|
||||
**Define** *Decode(u, t)* as:
|
||||
* Let $u'=u$ if $u \neq 0$; $1$ otherwise.
|
||||
* Let $t'=t$ if $t \neq 0$; $1$ otherwise.
|
||||
* Let $t''=t'$ if $u'^3 + b + t'^2 \neq 0$; $2t'$ otherwise.
|
||||
* Let $X = \dfrac{u'^3 + b - t''^2}{2t''}.$
|
||||
* Let $Y = \dfrac{X + t''}{u'\sqrt{-3}}.$
|
||||
* Let $x$ be the first element of $(u' + 4Y^2, \frac{-X}{2Y} - \frac{u'}{2}, \frac{X}{2Y} - \frac{u'}{2})$ for which $g(x)$ is square.
|
||||
* Let $y = \sqrt{g(x)}.$
|
||||
* Return $(x, y)$ if $sign(y) = sign(t)$; $(x, -y)$ otherwise.
|
||||
|
||||
This is implemented in `secp256k1_ellswift_swiftec_var`. The used $sign(x)$ function is the parity of $x$ when represented as in integer in $[0,q).$
|
||||
|
||||
The corresponding encoder would invoke the x-only one, but negating the output $t$ if $sign(t) \neq sign(y).$
|
||||
|
||||
This is implemented in `secp256k1_ellswift_elligatorswift_var`.
|
||||
|
||||
Note that this is only intended for encoding points where both the x-coordinate and y-coordinate are unpredictable. When encoding x-only points
|
||||
where the y-coordinate is implicitly even (or implicitly square, or implicitly in $[0,q/2]$), the encoder in
|
||||
[Section 3.5](#35-encoding-for-secp256k1) must be used, or a bias is reintroduced that undoes all the benefit of using ElligatorSwift
|
||||
in the first place.
|
||||
441
doc/iceberg.md
441
doc/iceberg.md
@@ -1,441 +0,0 @@
|
||||
Notes on the iceberg module API
|
||||
===============================
|
||||
|
||||
The following sections contain additional notes on the API of the iceberg
|
||||
module (`include/secp256k1_iceberg.h`). A usage example can be found in
|
||||
`examples/iceberg.c`, which runs the whole flow and narrates it.
|
||||
|
||||
**This module is experimental.** It is off by default and on in dev mode, and
|
||||
both build systems refuse it outright without `--enable-experimental` or
|
||||
`-DSECP256K1_EXPERIMENTAL=ON`. Iceberg has a security proof, by reduction to
|
||||
NestedMuSig2's unforgeability, but it is in an anonymous conference submission
|
||||
that is still a working draft, and at the two nonces BIP-327 fixes that
|
||||
reduction holds in the algebraic group model rather than the plain random
|
||||
oracle model. The proof also assumes a property no library can provide (that a
|
||||
session label is used once, group-wide) and the known ways to lose a key all
|
||||
live in exactly that assumption. Do not put money behind this module.
|
||||
|
||||
Iceberg lets a *t*-of-*n* group act as a single MuSig2 participant. From outside,
|
||||
the result is an ordinary BIP-340 signature: nothing in it records that a group
|
||||
was involved, or how large the group was.
|
||||
|
||||
## The objects
|
||||
|
||||
Two APIs are in play and about a dozen nouns between them. MuSig2's, which this
|
||||
module does not replace:
|
||||
|
||||
| name | type | made by | secret? | size |
|
||||
|---|---|---|---|---|
|
||||
| key aggregation cache | `musig_keyagg_cache` | anyone, from the signers' public keys | no | opaque |
|
||||
| secret nonce | `musig_secnonce` | a signer, once per session | **yes, and it must survive between the rounds** | never serialized |
|
||||
| public nonce | `musig_pubnonce` | a signer | no | 66 B |
|
||||
| aggregate nonce | `musig_aggnonce` | anyone | no | 66 B |
|
||||
| partial signature | `musig_partial_sig` | a signer | no | 32 B |
|
||||
|
||||
And Iceberg's, which exist entirely inside the group:
|
||||
|
||||
| name | type | made by | secret? | size |
|
||||
|---|---|---|---|---|
|
||||
| share | `iceberg_share` | the dealer, once | **yes, and it is the only _secret_ anyone stores** | 4 + 32*C(n-1, t-1) B |
|
||||
| share cache | `iceberg_share_cache` | a participant | no: Lagrange weights, which depend only on which participant you are | opaque |
|
||||
| public share | `iceberg_pubshare` | a participant | no | 34 B |
|
||||
| nonce contribution | `iceberg_pubnonce` | a participant, per session | no | 67 B |
|
||||
| group nonce | `iceberg_aggnonce` | nobody has to: no call takes one as input, so pass `NULL` unless you want it for logging | no | 66 B |
|
||||
| signature share | `iceberg_partial_sig` | a participant, per session | no | 33 B |
|
||||
|
||||
Two names are close and the objects are not. The group produces a MuSig2 **partial signature**, built
|
||||
out of Iceberg **signature shares**, one per participant. Likewise a participant
|
||||
makes a **nonce contribution**, and the group turns `2t-1` of them into one
|
||||
ordinary MuSig2 **public nonce**.
|
||||
|
||||
Those two conversions are the seam. `iceberg_nonce_agg` emits a
|
||||
`musig_pubnonce` and `iceberg_partial_sig_agg` emits a `musig_partial_sig`;
|
||||
everything above them is ordinary MuSig2 that knows nothing about a group, and
|
||||
everything below is this module.
|
||||
|
||||
## Three roles
|
||||
|
||||
Three roles appear here, and they run different code:
|
||||
|
||||
| role | runs | trusted? |
|
||||
|---|---|---|
|
||||
| **participant** | `iceberg_*` calls. Holds a share, never the key. There are *n* of them | with its own share only |
|
||||
| **coordinator** | moves messages, calls the `_agg` functions | **no.** Every check in the module assumes it is hostile |
|
||||
| **cosigner** | plain `musig_*` calls, and knows nothing about the group | as any MuSig2 signer |
|
||||
|
||||
A participant may also act as the coordinator; nothing changes if it does,
|
||||
because the coordinator has no privileges to abuse.
|
||||
|
||||
## How many people, and when
|
||||
|
||||
Two different counts:
|
||||
|
||||
- **Round one needs `2t-1` participants.** Not because the secret needs that
|
||||
many, but because the group verifies its own nonce contributions, and that
|
||||
check is error detection.
|
||||
|
||||
A contribution is a point on a polynomial of degree `t-1`, and **any `t`
|
||||
points lie on some polynomial of that degree**, including `t` an adversary
|
||||
chose. So a quorum of `t` does not weaken the check, it empties it. Every
|
||||
point past `t` is one more constraint a liar has to satisfy, and outnumbering
|
||||
`t-1` liars takes `t-1` of them:
|
||||
|
||||
t + (t-1) = 2t-1
|
||||
|
||||
Read as coding theory it is the same statement: detecting `e` errors in a code
|
||||
of dimension `t` needs `t+e` symbols. The `3t-2` under "Not implemented" is
|
||||
this formula with correction, `t+2e`, in place of detection. Note that the
|
||||
`n >= 3t-2` deployment bound below is a *different* result that happens to be
|
||||
the same number: one is Reed-Solomon correction, the other is Byzantine
|
||||
agreement, and neither implies the other.
|
||||
- **Round two needs `2t-1` too**, and they need not be the ones who took part in
|
||||
round one. The paper's Table II gives the signing quorum as `2t-1` online
|
||||
members for every threshold, and the honest majority that number represents
|
||||
does not stop applying halfway through a session.
|
||||
|
||||
A member that was offline for round one can still take part: it holds the
|
||||
share that determines what its contribution would have been, so it can verify
|
||||
the set it is handed and sign against it. That is the property the
|
||||
deterministic nonces exist to buy, and it is why `partial_sign` does not
|
||||
insist on finding your own contribution in the set; see "API misuse".
|
||||
|
||||
`t` appears in round two only as the interpolation degree; the arithmetic
|
||||
that turns signature shares back into one signature needs `t` points. Reading
|
||||
that as "round two needs `t` people" is the mistake to avoid.
|
||||
|
||||
Since `2t-1` participants must exist, `2t-1 <= n`, so:
|
||||
|
||||
t <= (n+1)/2
|
||||
|
||||
This is a hard structural limit, not a performance note. **2-of-2, 3-of-3,
|
||||
3-of-4, 4-of-5, 4-of-6 and 6-of-10 cannot be expressed at all.**
|
||||
`secp256k1_iceberg_shares_gen` refuses them at setup rather than mishandling
|
||||
them later; see the failure table under "API misuse" for what refusing means.
|
||||
|
||||
Sizes, since a share is a bundle of seeds and grows quickly. Both rounds want
|
||||
`2t-1` members online; the third column is the smaller number the *arithmetic* of
|
||||
step 10 needs, which is a degree and not a quorum.
|
||||
|
||||
**These are expressible configurations, not recommended ones.** A separate bound
|
||||
applies to deployment: agreeing on the live state with up to `t-1` faulty members
|
||||
is Byzantine agreement, which needs `n >= 3(t-1)+1 = 3t-2`. That gives 2-of-4,
|
||||
3-of-7, 4-of-10, 5-of-13 as the smallest deployable groups, and **five of the
|
||||
seven rows below fall short of it**. The signature scheme is correct at all of
|
||||
them, since the arithmetic does not know how many faults the surrounding
|
||||
agreement survives, but a group sized from this table alone will be too small for the
|
||||
consensus the scheme assumes. See the deployment constraints in
|
||||
`include/secp256k1_iceberg.h`.
|
||||
|
||||
Note where the two bounds meet. 5-of-13 needs more participants than
|
||||
`SECP256K1_ICEBERG_MAX_PARTICIPANTS` allows, so at the maximum of 10 the
|
||||
largest threshold that is both expressible and deployable is **4-of-10**. The
|
||||
table below goes past that line, and so do the benchmark and the tests: 5-of-9
|
||||
and 5-of-10 are there to show the cost curve, not because a group should be
|
||||
sized that way.
|
||||
|
||||
`SECP256K1_ICEBERG_MAX_PARTICIPANTS` is 10. It sizes two of the opaque types and
|
||||
the largest stack frames in the module, all of which grow as `C(n-1, t-1)`, so it
|
||||
is part of the ABI and not something one member of a group changes on its own. A
|
||||
build that will only ever run small groups can lower it, and gets a 200-byte share
|
||||
instead of a 4040-byte one at five participants; `src/modules/iceberg/rss.h`
|
||||
beside the `#error` says which three derived values have to be lowered with it,
|
||||
and `run_iceberg_binom_test` recomputes all three and names the one that is wrong.
|
||||
Raising it is refused at compile time. `rss.h` also carries the command that
|
||||
measures the stack frames, rather than a number, because the number moves with the
|
||||
compiler. None of that is in the public header: a caller of the installed library
|
||||
has no `rss.h`, no test suite and no `src/` to run it against.
|
||||
|
||||
| config | quorum `2t-1` | shares that interpolate | seeds in the group | seeds per participant | serialized share |
|
||||
|---|---|---|---|---|---|
|
||||
| 2-of-3 | 3 | 2 | 3 | 2 | 68 B |
|
||||
| 2-of-4 | 3 | 2 | 4 | 3 | 100 B |
|
||||
| 3-of-5 | 5 | 3 | 10 | 6 | 196 B |
|
||||
| 3-of-7 | 5 | 3 | 21 | 15 | 484 B |
|
||||
| 4-of-7 | 7 | 4 | 35 | 20 | 644 B |
|
||||
| 5-of-9 | 9 | 5 | 126 | 70 | 2244 B |
|
||||
| 5-of-10 | 9 | 5 | 210 | 126 | 4036 B |
|
||||
|
||||
Everything that crosses the network serializes: a share to `4 + 32*C(n-1, t-1)`
|
||||
bytes, a public share to 34, a nonce contribution to 67 and a signature share to
|
||||
33. The group's aggregate nonce serializes to 66 as well, but it is not on that
|
||||
list: nothing receives one. Those bytes are the start of the b1 preimage, and
|
||||
they are there for an implementation checking its arithmetic against this one.
|
||||
|
||||
The public share, the nonce contribution and the signature share each carry the
|
||||
participant index they belong to, which is the extra byte in each. The group's
|
||||
aggregate nonce does not, since it belongs to the group rather than to a member,
|
||||
which is exactly why it is 66 bytes and a contribution is 67.
|
||||
|
||||
The group's seed count is `C(n, t-1)` and each participant holds
|
||||
`C(n-1, t-1)` of them. The bounds derived from
|
||||
`SECP256K1_ICEBERG_MAX_PARTICIPANTS` (`SECP256K1_ICEBERG_MAX_SEEDS` here, and
|
||||
`MAX_T` and `MAX_SUBSETS` in `rss.h`) are written out by hand because C89
|
||||
cannot evaluate a binomial at preprocessing time. The test suite recomputes each
|
||||
of them and fails on a wrong line.
|
||||
|
||||
## The flow
|
||||
|
||||
participant k coordinator cosigner
|
||||
(one of n) (untrusted) (plain musig2)
|
||||
------------- ----------- --------------
|
||||
|
||||
SETUP, once. A dealer runs shares_gen and is then not needed again.
|
||||
iceberg_shares_gen -> one share each [_dealer.h, not installed]
|
||||
iceberg_pubshare_gen -> iceberg_pubkey_agg -> the group's public key
|
||||
|
||||
KEY AGGREGATION. The group is now one public key among several.
|
||||
musig_pubkey_agg([group_pk, cosigner_pk, ...]) -> keyagg_cache
|
||||
optionally musig_pubkey_xonly_tweak_add / _ec_tweak_add
|
||||
|
||||
=== ROUND ONE ================================ needs 2t-1 participants ===
|
||||
|
||||
Neither side waits on the other. iceberg_nonce_gen needs only the share
|
||||
and the label; musig_nonce_gen is an ordinary MuSig2 call and takes
|
||||
whatever it usually takes. Drawn side by side for that reason.
|
||||
|
||||
iceberg_nonce_gen(share, sid32) musig_nonce_gen
|
||||
--- pubnonce (67 B) -->
|
||||
<-- pubnonce ------
|
||||
iceberg_nonce_agg
|
||||
verifies 2t-1 of them, interpolates
|
||||
-> one ordinary musig pubnonce
|
||||
musig_nonce_agg([group, cosigners])
|
||||
--- aggnonce -->
|
||||
musig_nonce_process
|
||||
|
||||
------------------------------------------------------------------------
|
||||
THE GAP. No secret nonce survives this line. A participant's nonces
|
||||
are a function of (share, sid), and it is handed the sid again, so a
|
||||
crash here costs it nothing and there is no secret to lose, leak or
|
||||
duplicate. That is narrower than "stores nothing": it must still
|
||||
remember which labels it has answered under, and restoring a backup
|
||||
from before that record is exactly the exploitable case.
|
||||
------------------------------------------------------------------------
|
||||
|
||||
=== ROUND TWO ============================ needs 2t-1 again, any of n ===
|
||||
|
||||
The message appears here for the first time, which is the point: the
|
||||
group commits to a nonce before knowing what it will sign.
|
||||
|
||||
--- msg, the round-one pubnonces -->
|
||||
iceberg_partial_sign(share, sid32, msg, the contributions, ...)
|
||||
derives the group's aggregate from the contributions rather than
|
||||
accepting one, checks they are a single sharing of degree t-1, and
|
||||
compares their value at its own index against the contribution it
|
||||
derives for itself. So it signs against the aggregate the group
|
||||
really formed. A member that sat round one out can still sign.
|
||||
--- partial sig (33 B) -->
|
||||
iceberg_partial_sig_agg
|
||||
-> one ordinary musig partial sig
|
||||
musig_partial_sign
|
||||
<-- partial sig ---
|
||||
musig_partial_sig_agg -> 64-byte signature
|
||||
|
||||
schnorrsig_verify accepts it under the (possibly tweaked) aggregate key.
|
||||
|
||||
## Signing, as a list
|
||||
|
||||
1. **Dealer**: `secp256k1_iceberg_shares_gen`, from
|
||||
`include/secp256k1_iceberg_dealer.h`, then hand each participant its share
|
||||
and forget the seed. That header is separate and is not installed, because a
|
||||
trusted dealer is not something the library offers for deployment; see
|
||||
"API misuse" below. A distributed key generation replaces this step and
|
||||
nothing downstream changes.
|
||||
2. **Each participant**: `secp256k1_iceberg_pubshare_gen`, publish the result.
|
||||
3. **Anyone**: `secp256k1_iceberg_pubkey_agg` over any `2t-1` public shares.
|
||||
This checks they agree, so a participant that published a wrong one is
|
||||
caught here rather than at signing time.
|
||||
4. **Anyone**: `secp256k1_musig_pubkey_agg` with the group's public key and the
|
||||
cosigners', then optionally `secp256k1_musig_pubkey_xonly_tweak_add` and
|
||||
`secp256k1_musig_pubkey_ec_tweak_add`.
|
||||
5. **Each of `2t-1` participants**: `secp256k1_iceberg_nonce_gen`, over the
|
||||
session label alone. Publish the result. Nothing here depends on the
|
||||
message or on the cosigners, so this can run before either exists.
|
||||
6. **Cosigners**, independently and in either order: `secp256k1_musig_nonce_gen`,
|
||||
then `secp256k1_musig_nonce_agg` over the cosigners' nonces alone.
|
||||
7. **Coordinator**: `secp256k1_iceberg_nonce_agg`, which verifies and
|
||||
interpolates them into one ordinary MuSig2 public nonce.
|
||||
8. **Coordinator**: `secp256k1_musig_nonce_agg` over that and the cosigners',
|
||||
then `secp256k1_musig_nonce_process`.
|
||||
9. **Participants**, not necessarily the ones from step 5:
|
||||
`secp256k1_iceberg_partial_sign`, given the group's own round-one
|
||||
contributions, not an aggregate of them and not the cosigners', the same
|
||||
label, and now the message. This is where the message enters and the only
|
||||
place it appears in the API. Each call needs all `2t-1` contributions from
|
||||
step 5 and returns 0 with fewer, and the scheme wants `2t-1` members online
|
||||
here as in round one; step 10 then interpolates from as few as `t` of the
|
||||
resulting shares. Before calling it, each signer checks its own record that it
|
||||
has not answered under this label already; the library cannot do that for it,
|
||||
because it holds nothing between calls.
|
||||
10. **Coordinator**: `secp256k1_iceberg_partial_sig_agg`, giving one MuSig2
|
||||
partial signature. Hand it more than `t` shares and it degree-checks them,
|
||||
which above the threshold catches a set that contradicts itself; at exactly
|
||||
`t` there is nothing to disagree with.
|
||||
11. **Cosigners**: `secp256k1_musig_partial_sign` as usual.
|
||||
12. **Coordinator**: `secp256k1_musig_partial_sig_agg`, then
|
||||
`secp256k1_schnorrsig_verify`.
|
||||
|
||||
Steps 1 to 4 happen once per group, and `secp256k1_iceberg_keyagg_check` belongs
|
||||
with step 4: it confirms the outer cache aggregates the key list you think it
|
||||
does, which is a fact about the channel rather than about this attempt. Steps 5
|
||||
to 12 are one signing session.
|
||||
|
||||
## API misuse
|
||||
|
||||
The musig module's three rules apply here too: unique nonces, never copy or
|
||||
serialize a `secp256k1_musig_secnonce`, and never read or write an opaque struct
|
||||
directly. Iceberg adds its own, and the reasons are specific.
|
||||
|
||||
**How a call refuses, before anything else.** A function returns 0 when the
|
||||
values it was handed do not work together: an inconsistent set of shares, a
|
||||
malformed encoding, a contribution derived under some other label. The illegal
|
||||
callback, which aborts the process unless the caller has installed its own with
|
||||
`secp256k1_context_set_illegal_callback`, is reserved for a bug in the calling
|
||||
code: a null pointer, an uninitialized opaque struct, a group shape the scheme
|
||||
cannot express.
|
||||
|
||||
How many contributions turned up is neither. It is a fact about the group,
|
||||
influenced by whichever peers answered, so every call that takes a count returns
|
||||
0 rather than aborting:
|
||||
|
||||
| call | a bad count does what |
|
||||
|---|---|
|
||||
| `iceberg_shares_gen` | aborts: `n` outside `1..10`, or `t` outside `1..(n+1)/2`. These are the group's shape, not a count |
|
||||
| `iceberg_pubkey_agg` | returns 0: fewer than `2t-1` public shares, or more than `n` |
|
||||
| `iceberg_nonce_agg` | returns 0: fewer than `2t-1` contributions, or more than `n` |
|
||||
| `iceberg_partial_sig_agg` | returns 0: fewer than `t` shares or more than `n`. A share that never came from `partial_sign` still aborts, that one being an uninitialized struct |
|
||||
| `iceberg_partial_sign` | returns 0: it checks the same `2t-1` bound while deriving the aggregate |
|
||||
| `iceberg_partial_sig_verify` | returns 0: same reason, and it derives the same aggregate |
|
||||
|
||||
A member that waits for a deadline and aggregates whatever arrived is therefore
|
||||
doing something the API supports, rather than something that kills its process
|
||||
the first time a peer is asleep.
|
||||
|
||||
**Where n and t come from.** `iceberg_partial_sign` reads them off the caller's
|
||||
own share and never asks. The four calls that take them as arguments should be
|
||||
given the same values; a member that keeps only a serialized share can read them
|
||||
from it, since the encoding is `version | n | t | index | seeds`. It matters most
|
||||
for `t`: understate it and the degree check still runs, against a lower degree,
|
||||
and proves less. Nothing cross-checks the four calls against each other.
|
||||
|
||||
**You supply the session label, and the rule about it is yours to enforce.** Every participant's secret nonces are a
|
||||
deterministic function of its seeds and the label, and the seeds never change,
|
||||
so a label reused under two different messages produces two answers in which
|
||||
`k1`, `k2` and `d` are identical while `b0` and `e` have moved. Three such
|
||||
answers are three linear equations in those three unknowns, and solving them
|
||||
recovers a key share.
|
||||
|
||||
Concretely, the caller must guarantee both halves:
|
||||
|
||||
- **one answer per member per label.** A member can enforce this alone, but not
|
||||
from anything the library holds; it keeps nothing between calls. It needs
|
||||
durable storage of its own: one 32-byte field per participant holding the last
|
||||
label it signed under, and a rule that a new label must be strictly greater.
|
||||
`may_sign_under` in `examples/iceberg.c` does exactly that.
|
||||
Restore it with the share; a record rolled back to an old backup is a member
|
||||
that will answer twice.
|
||||
- **one message per label across the whole group**: the members must agree on
|
||||
what they are signing before any of them answers.
|
||||
|
||||
The library sees one call at a time and holds nothing between them, so it can
|
||||
enforce neither. Neither can any
|
||||
individual participant: a coordinator can show three *different* members three
|
||||
*different* messages under one label, and each one signs exactly once, refuses
|
||||
nothing, and detects nothing, because nothing in the protocol tells a member
|
||||
that somebody else saw this label too. **Per-participant discipline is therefore
|
||||
not a substitute for group agreement**, which is why the scheme's security model
|
||||
assumes a consensus its deployment already runs.
|
||||
|
||||
*Why the label cannot simply be derived from the message, which would close all
|
||||
of this:* round one has to run before the message exists. In Lightning the
|
||||
nonce is exchanged a full round-trip before the commitment transaction is
|
||||
assembled, so a label binding the message could not be computed when it is
|
||||
needed. The intended label is the channel's commitment number, plus a counter
|
||||
for retries under it. A label bound to the message is possible where the message
|
||||
*is* known early, but it costs the message-independent first round, which is a different setting from the one this
|
||||
module assumes. The module takes any 32 bytes and offers no derivation, because
|
||||
the right label depends on the deployment, and the wrong one costs a key share.
|
||||
|
||||
**`secp256k1_iceberg_partial_sign` takes the group's own round-one
|
||||
contributions, not an aggregate of them.** This is not an optimisation
|
||||
opportunity. The nesting coefficient is a hash of the group's aggregate nonce,
|
||||
so a coordinator free to invent that aggregate gets a coefficient it can vary at
|
||||
will: three invented aggregates under one correctly-bound label again give
|
||||
three equations in the same three unknowns. The aggregate is therefore derived
|
||||
from contributions that must pass the degree check together, and then the
|
||||
interpolated polynomial is evaluated at the signer's own index to get the nonce
|
||||
shares it signs with. It signs against the aggregate the group actually formed,
|
||||
whether or not it was one of the members who helped form it.
|
||||
|
||||
It does *not* require the signer's own round-one contribution to be present and
|
||||
unaltered in the set. That would be strictly stronger against an unauthenticated
|
||||
transport, and it would lock out a member who was offline during round one --
|
||||
the exact failure the deterministic nonces exist to survive. With the
|
||||
authenticated transport the scheme assumes anyway it is unnecessary: among
|
||||
`2t-1` contributions with at most `t-1` corruptions, at least `t` are honest,
|
||||
and `t` points already pin a degree `t-1` polynomial.
|
||||
|
||||
**A tweak belongs to the outer session and must not be applied twice.** The
|
||||
module is correct here and needs nothing from the caller. No Iceberg call takes
|
||||
a tweak because `secp256k1_musig_nonce_process` sets
|
||||
the tweak term aside and `secp256k1_musig_partial_sig_agg` adds it in once, at
|
||||
the top. The group's shares carry the key coefficient and not the tweak.
|
||||
|
||||
**`secp256k1_iceberg_shares_gen` is a trusted dealer, and lives outside the
|
||||
installed API for that reason.** For the duration of that one call, one machine
|
||||
holds enough to reconstruct the group's private key, which is the situation a
|
||||
threshold scheme exists to avoid. It is fine for testing, and fine where one
|
||||
party is already trusted with the whole key. It is not fine otherwise, and this
|
||||
module does not provide a distributed key generation.
|
||||
|
||||
It is declared in `include/secp256k1_iceberg_dealer.h`, which is in the tree so
|
||||
the tests, the benchmarks and the example have shares to work with, and is not
|
||||
installed. Including `secp256k1_iceberg.h` does not offer you a dealer. Nothing
|
||||
in the signing API cares how a share was produced: one arrives through
|
||||
`secp256k1_iceberg_share_parse`, so a share from a DKG or from another
|
||||
implementation is used identically.
|
||||
|
||||
## What is stored, and for how long
|
||||
|
||||
| object | who holds it | lifetime | secret? |
|
||||
|---|---|---|---|
|
||||
| `iceberg_share` | one participant | forever | **yes** |
|
||||
| `iceberg_share_cache` | one participant | optional, derived from the share | no |
|
||||
| `iceberg_pubshare` | published | forever | no |
|
||||
| session label (`sid32`) | anyone | one session, and a record that it was used, for as long as the group lives | no |
|
||||
| `iceberg_pubnonce` | published | one session | no |
|
||||
| `iceberg_aggnonce` | coordinator | one session | no |
|
||||
| `iceberg_partial_sig` | published | one session | no |
|
||||
|
||||
Nothing marked secret above needs to survive a reboot except the share,
|
||||
which is why `examples/iceberg.c` wipes every participant between the two rounds
|
||||
and rebuilds them from storage. That is the property Iceberg exists for: a FROST
|
||||
signer must keep a secret nonce alive across the same gap, and losing it,
|
||||
restoring an old copy over it, or running two instances of the signer are all
|
||||
catastrophic.
|
||||
|
||||
It does not follow that a participant is stateless, and the table does not say
|
||||
so. A member must also remember **which labels it has already answered under**.
|
||||
That record is not secret and so is not listed above, but it is not optional: restoring a backup taken before it
|
||||
was written is the one restore that is dangerous. A signer with no memory can
|
||||
also be talked into signing a superseded channel state, which in Lightning costs
|
||||
it the channel.
|
||||
|
||||
The share cache holds no secrets despite being derived from a share: it is
|
||||
Lagrange weights, which depend only on which participant it is. It has no
|
||||
serialized form and does not need one; passing `NULL` wherever a cache is
|
||||
accepted rebuilds it. `bench_iceberg` prints what that costs, along with the
|
||||
rest of the module; the figure moves with the machine.
|
||||
|
||||
## Not implemented
|
||||
|
||||
- **Naming the liar.** `secp256k1_iceberg_partial_sig_verify` ships, so a share
|
||||
can be checked against the commitments it claims to come from, and a failed
|
||||
signature need not be a mystery. What it does not do is assign blame: a 0
|
||||
means that share does not satisfy the equation, not that its author cheated,
|
||||
because MuSig2 partial signatures are forgeable. Naming the liar is error
|
||||
*correction* rather than detection, and correction needs `t+2e` points where
|
||||
detection needed `t+e`, so `3t-2` online at once, against `2t-1` for
|
||||
signing. That is usually more people than the group has, which is why the
|
||||
function is documented as detection and stops there.
|
||||
- **Distributed key generation.** See above.
|
||||
66
doc/musig.md
66
doc/musig.md
@@ -1,66 +0,0 @@
|
||||
Notes on the musig module API
|
||||
===========================
|
||||
|
||||
The following sections contain additional notes on the API of the musig module (`include/secp256k1_musig.h`).
|
||||
A usage example can be found in `examples/musig.c`.
|
||||
|
||||
## API misuse
|
||||
|
||||
The musig API is designed with a focus on misuse resistance.
|
||||
However, due to the interactive nature of the MuSig protocol, there are additional failure modes that are not present in regular (single-party) Schnorr signature creation.
|
||||
While the results can be catastrophic (e.g. leaking of the secret key), it is unfortunately not possible for the musig implementation to prevent all such failure modes.
|
||||
|
||||
Therefore, users of the musig module must take great care to make sure of the following:
|
||||
|
||||
1. A unique nonce per signing session is generated in `secp256k1_musig_nonce_gen`.
|
||||
See the corresponding comment in `include/secp256k1_musig.h` for how to ensure that.
|
||||
2. The `secp256k1_musig_secnonce` structure is never copied or serialized.
|
||||
See also the comment on `secp256k1_musig_secnonce` in `include/secp256k1_musig.h`.
|
||||
3. Opaque data structures are never written to or read from directly.
|
||||
Instead, only the provided accessor functions are used.
|
||||
4. If adaptor signatures are used, all partial signatures are verified.
|
||||
|
||||
## Key Aggregation and (Taproot) Tweaking
|
||||
|
||||
Given a set of public keys, the aggregate public key is computed with `secp256k1_musig_pubkey_agg`.
|
||||
A plain tweak can be added to the resulting public key with `secp256k1_ec_pubkey_tweak_add` by setting the `tweak32` argument to the hash defined in BIP 32. Similarly, a Taproot tweak can be added with `secp256k1_xonly_pubkey_tweak_add` by setting the `tweak32` argument to the TapTweak hash defined in BIP 341.
|
||||
Both types of tweaking can be combined and invoked multiple times if the specific application requires it.
|
||||
|
||||
## Signing
|
||||
|
||||
This is covered by `examples/musig.c`.
|
||||
Essentially, the protocol proceeds in the following steps:
|
||||
|
||||
1. Generate a keypair with `secp256k1_keypair_create` and obtain the public key with `secp256k1_keypair_pub`.
|
||||
2. Call `secp256k1_musig_pubkey_agg` with the pubkeys of all participants.
|
||||
3. Optionally add a (Taproot) tweak with `secp256k1_musig_pubkey_xonly_tweak_add` and a plain tweak with `secp256k1_musig_pubkey_ec_tweak_add`.
|
||||
4. Generate a pair of secret and public nonce with `secp256k1_musig_nonce_gen` and send the public nonce to the other signers.
|
||||
5. Someone (not necessarily the signer) aggregates the public nonces with `secp256k1_musig_nonce_agg` and sends it to the signers.
|
||||
6. Process the aggregate nonce with `secp256k1_musig_nonce_process`.
|
||||
7. Create a partial signature with `secp256k1_musig_partial_sign`.
|
||||
8. Verify the partial signatures (optional in some scenarios) with `secp256k1_musig_partial_sig_verify`.
|
||||
9. Someone (not necessarily the signer) obtains all partial signatures and aggregates them into the final Schnorr signature using `secp256k1_musig_partial_sig_agg`.
|
||||
|
||||
The aggregate signature can be verified with `secp256k1_schnorrsig_verify`.
|
||||
|
||||
Steps 1 through 5 above can occur before or after the signers are aware of the message to be signed.
|
||||
Whenever possible, it is recommended to generate the nonces only after the message is known.
|
||||
This provides enhanced defense-in-depth measures, protecting against potential API misuse in certain scenarios.
|
||||
However, it does require two rounds of communication during the signing process.
|
||||
The alternative, generating the nonces in a pre-processing step before the message is known, eliminates these additional protective measures but allows for non-interactive signing.
|
||||
Similarly, the API supports an alternative protocol flow where generating the aggregate key (steps 1 to 3) is allowed to happen after exchanging nonces (steps 4 to 5).
|
||||
|
||||
## 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 1, 4 and 7.
|
||||
|
||||
## Atomic Swaps
|
||||
|
||||
The signing API supports the production of "adaptor signatures", modified partial signatures
|
||||
which are offset by an auxiliary secret known to one party. That is,
|
||||
1. One party generates a (secret) adaptor `t` with corresponding (public) adaptor `T = t*G`.
|
||||
2. When calling `secp256k1_musig_nonce_process`, the public adaptor `T` is provided as the `adaptor` argument.
|
||||
3. The party who is going to extract the secret adaptor `t` later must verify all partial signatures.
|
||||
4. Due to step 2, the signature output of `secp256k1_musig_partial_sig_agg` is a pre-signature and not a valid Schnorr signature. All parties involved extract this session's `nonce_parity` with `secp256k1_musig_nonce_parity`.
|
||||
5. The party who knows `t` must "adapt" the pre-signature with `t` (and the `nonce_parity` using `secp256k1_musig_adapt` to complete the signature.
|
||||
6. Any party who sees both the final signature and the pre-signature (and has the `nonce_parity`) can extract `t` with `secp256k1_musig_extract_adaptor`.
|
||||
@@ -1,265 +0,0 @@
|
||||
# Prefractal: a nested FROST+MuSig2 signer
|
||||
|
||||
**WARNING: EXPERIMENTAL.** Neither the scheme nor this implementation has been
|
||||
reviewed by anyone outside the project. Do not use it to protect anything of
|
||||
value. The construction comes from [frosty-musig][frosty], which is unaudited
|
||||
research code, and it is built on this repository's `frost` module, which is
|
||||
itself marked experimental and unstable.
|
||||
|
||||
[frosty]: https://github.com/jesseposner/frosty-musig
|
||||
|
||||
## What it does
|
||||
|
||||
It lets a FROST `t`-of-`n` group occupy **one participant slot** of an ordinary
|
||||
MuSig2 (BIP 327) session. The group publishes one ordinary MuSig2 public nonce
|
||||
and one ordinary MuSig2 partial signature. Cosigners need no support for any of
|
||||
this and cannot tell a group is involved.
|
||||
|
||||
The motivating shape is a 2-of-2 taproot output where one of the two
|
||||
"participants" is really a threshold group.
|
||||
|
||||
## The signing equation
|
||||
|
||||
Each member `i` of the participating set computes
|
||||
|
||||
```
|
||||
s_i = k1_i + b_frost * b_musig * k2_i + e * a * lambda_i * g * gacc * d_i
|
||||
```
|
||||
|
||||
- `k1_i`, `k2_i` — the member's two nonce scalars, both negated iff the OUTER
|
||||
final nonce has odd Y.
|
||||
- `b_frost` — this module's nonce-binding coefficient (below).
|
||||
- `b_musig`, `e`, `a`, `g`, `gacc` — all from the OUTER MuSig2 session: the
|
||||
nonce coefficient, the BIP 340 challenge over the aggregate key, the
|
||||
key-aggregation coefficient of the group's threshold public key, and the
|
||||
aggregate key's parity bookkeeping.
|
||||
- `lambda_i` — the member's Lagrange interpolating value over the participating
|
||||
set.
|
||||
- `d_i` — the member's secret share.
|
||||
|
||||
The group's wire nonce is its FROST aggregate nonce with the second component
|
||||
premultiplied by `b_frost`:
|
||||
|
||||
```
|
||||
pubnonce = (R1, b_frost * R2)
|
||||
```
|
||||
|
||||
The aggregator sums the members' shares. That is a plain sum, with no
|
||||
interpolation, because `lambda_i` is already folded into each share.
|
||||
|
||||
## Three deliberate deviations from BIP 445
|
||||
|
||||
These are the parts a reviewer should look at hardest. Each one is a
|
||||
considered trade, and each one is enforced or pinned somewhere in the code.
|
||||
|
||||
### 1. `b_frost` does not commit to the message
|
||||
|
||||
BIP 445's nonce coefficient hashes the message. This module's does not:
|
||||
|
||||
```
|
||||
b_frost = tagged_hash("Prefractal/noncecoef",
|
||||
ser32(u) || sorted ser32 ids || aggnonce66 ||
|
||||
cbytes_ext(thresh_pk))
|
||||
```
|
||||
|
||||
**Why.** The protocols this module targets publish the group's wire nonce
|
||||
*before the message exists*. A lightning channel's funding signer publishes a
|
||||
verification nonce at commitment number `N` long before the transaction that
|
||||
nonce will sign has been built. A coefficient that hashed the message could not
|
||||
be computed in round one and rebuilt identically in round two.
|
||||
|
||||
**Why it is not fatal.** The outer coefficient `b_musig` *does* commit to the
|
||||
message, via `secp256k1_musig_nonce_process_internal`, and it multiplies
|
||||
`b_frost` in every term where `b_frost` appears. The product binds the message.
|
||||
This is the same trade the `iceberg` module makes, for the same reason, with
|
||||
its own `Iceberg/noncecoef` tag.
|
||||
|
||||
**What is different from BIP 445's preimage.** The message is dropped, and the
|
||||
threshold public key is hashed in its full 33-byte extended encoding rather
|
||||
than x-only, because the key is used as a full point everywhere downstream (see
|
||||
deviation 2) and the binding should cover the point that is actually in play.
|
||||
|
||||
**Pinned by.** `run_prefractal_midstate_test` checks the tagged-hash constant
|
||||
against a freshly initialised one. Nothing else in the tree would notice a
|
||||
changed `b_frost`; it would simply produce signatures that do not verify.
|
||||
|
||||
### 2. There is no `g_frost` factor
|
||||
|
||||
Stock FROST negates the secret share when the threshold public key has odd Y:
|
||||
|
||||
```c
|
||||
/* frost/session_impl.h:664 */
|
||||
session_i->g_times_gacc_parity = cache_i->gacc_parity ^ pk_odd;
|
||||
/* frost/session_impl.h:797-800 */
|
||||
if (session_i->g_times_gacc_parity) {
|
||||
secp256k1_scalar_negate(&d, &d);
|
||||
}
|
||||
```
|
||||
|
||||
It does this because standalone FROST produces a BIP 340 x-only signature, so
|
||||
the effective secret is normalised to the even-Y representative of the
|
||||
threshold key.
|
||||
|
||||
**Here that must not happen.** The threshold public key is an *inner
|
||||
participant* of the outer key aggregation. It enters `secp256k1_musig_pubkey_agg`
|
||||
as a full 33-byte point, and MuSig2 does no per-participant parity
|
||||
normalisation: the only key-side flip is at the aggregate level, off the OUTER
|
||||
keyagg cache. So the group's members must reconstruct `d` with `d*G = thresh_pk`
|
||||
exactly as dealt, whatever its Y parity.
|
||||
|
||||
**The trap.** It is tempting to say "the FROST tweak cache is the identity, so
|
||||
the frost key-side factor is 1". That is **false**. The factor is `g * gacc`.
|
||||
An identity cache gives `gacc = 1`, but `g` is still `-1` for every threshold
|
||||
key with odd Y — roughly half of all groups. An implementation that reused
|
||||
`secp256k1_frost_get_session_values`'s key-side handling would produce a signer
|
||||
that works for even-Y groups and fails for odd-Y ones.
|
||||
|
||||
**Pinned by.** The test suite carries two *fixed* threshold secret keys, one of
|
||||
each Y parity, and `run_prefractal_odd_y_group_key_test` asserts the parity of
|
||||
its own fixture so it cannot quietly stop testing what it is named after. This
|
||||
was verified by mutation: injecting the `pk_odd` negation makes the odd-Y test
|
||||
fail while the even-Y one still passes. With a randomly seeded fixture that
|
||||
would have been a coin flip per run.
|
||||
|
||||
### 3. The FROST tweak cache must be the identity
|
||||
|
||||
`tacc == 0` and `gacc_parity == 0`, checked by every entry point that takes a
|
||||
cache.
|
||||
|
||||
**Why.** The target protocols tweak only the *outer* aggregate key — the BIP
|
||||
341 key-path tweak is applied to the MuSig2 keyagg cache and handled by the
|
||||
stock outer session. A frost-level tweak would add an `e * g * tacc` term that
|
||||
the aggregator would have to fold in, and this module's aggregator is a plain
|
||||
sum.
|
||||
|
||||
**Where it is checked.** In `secp256k1_prefractal_sign` and
|
||||
`secp256k1_prefractal_partial_sig_verify`, not only in
|
||||
`secp256k1_prefractal_partial_sig_agg`. Checking only at aggregation would be
|
||||
too late and too weak: the signing path would never see the cache, so nothing
|
||||
would tie the key a member signed under to the cache that was validated. `sign`
|
||||
and `partial_sig_verify` additionally require `thresh_pk` to equal the cache's
|
||||
own key, so the two arguments cannot disagree.
|
||||
|
||||
A tweak-aware aggregation variant (folding `e * g_musig * tacc`, as
|
||||
frosty-musig's `nested_frost_partial_sig_agg` does) is a possible later
|
||||
extension. It is not implemented.
|
||||
|
||||
## Rules the caller must follow
|
||||
|
||||
### One secnonce, one signature
|
||||
|
||||
The usual FROST rule, and this module cannot enforce it any better than FROST
|
||||
can. `secp256k1_prefractal_sign` wipes the secnonce, so a second call with the
|
||||
same one fails — including when the first call failed for some other reason,
|
||||
which is why a member whose signing attempt was refused must generate a fresh
|
||||
nonce rather than retry.
|
||||
|
||||
Deployments that derive nonces deterministically from a session label (which is
|
||||
how a protocol gets a nonce it can publish early and rebuild later) inherit a
|
||||
sharper version of the rule: **one label signs one message, group-wide**. Two
|
||||
different messages under one label leak the secret share, and nothing raises an
|
||||
error.
|
||||
|
||||
### The round-two signer set must equal the round-one set
|
||||
|
||||
Not a subset — the same set.
|
||||
|
||||
`lambda_i` and the aggregate nonce are both defined over the participating set.
|
||||
If round one aggregates over `C` and only `S ⊂ C` signs, then the nonce terms
|
||||
of `C \ S` are still in `R` while their key shares are absent from `sum(s_i)`,
|
||||
and `sum_{i in S} lambda_i^C * d_i != d`. The result is an invalid signature
|
||||
with no error raised at signing time.
|
||||
|
||||
This is worth stating explicitly because the `iceberg` module in this same
|
||||
repository *does* tolerate a subset: its `2t-1` / `t` split comes from VSS
|
||||
interpolation over the contributions, and FROST has no equivalent. Callers
|
||||
porting between the two must not transpose the rule.
|
||||
|
||||
`run_prefractal_partial_sig_verify_test` covers the detectable half of this: a
|
||||
share made for one signer set does not verify under another.
|
||||
|
||||
### Nonces at infinity
|
||||
|
||||
A FROST aggregate nonce component may legitimately be the point at infinity
|
||||
(BIP 445 NonceAgg), but a MuSig2 public nonce has no encoding for one. Both
|
||||
columns can reach infinity independently — the first is passed through
|
||||
unscaled, the second only after the `b_frost` multiplication — and
|
||||
`secp256k1_prefractal_nonce_agg` refuses both. Such a session has to be
|
||||
restarted with fresh nonces.
|
||||
|
||||
## API
|
||||
|
||||
All four functions are sessionless: every call takes its session parameters
|
||||
explicitly, so there are no opaque session objects, no new magics and no
|
||||
`*_SIZE` constants to keep synchronised across bindings.
|
||||
|
||||
| Function | Role |
|
||||
| --- | --- |
|
||||
| `secp256k1_prefractal_nonce_agg` | round one: group wire nonce + unscaled aggnonce |
|
||||
| `secp256k1_prefractal_sign` | round two: one member's partial signature |
|
||||
| `secp256k1_prefractal_partial_sig_verify` | identifiable abort |
|
||||
| `secp256k1_prefractal_partial_sig_agg` | sum shares into a MuSig2 partial signature |
|
||||
|
||||
`aggnonce_out` from `nonce_agg` is an internal value, not a wire value: it is
|
||||
the *unscaled* FROST aggregate, and it must be handed back to `sign` and
|
||||
`partial_sig_verify` unchanged. The wire value is `pubnonce_out`, an ordinary
|
||||
66-byte MuSig2 public nonce.
|
||||
|
||||
Members generate their nonces with the stock `secp256k1_frost_nonce_gen`. This
|
||||
module adds no nonce generation of its own.
|
||||
|
||||
## Relationship to the other modules
|
||||
|
||||
- **`frost`** stays pure, vector-pinned BIP 445. This module deliberately does
|
||||
not live inside it: the deviations above are not BIP 445, and keeping them
|
||||
behind their own opt-in flag gives auditors a clean scope boundary. It also
|
||||
keeps `frost`'s dependency graph honest — `frost` depends only on
|
||||
`schnorrsig`, and every pure-FROST consumer would otherwise have to build
|
||||
`musig` too.
|
||||
- **`musig`** is used unmodified, through its internals. Cosigners run stock
|
||||
MuSig2 throughout.
|
||||
- **`iceberg`** solves the same outer problem with a different inner scheme. The
|
||||
two differ in ways that do not transfer: iceberg's quorum is `2t-1` in round
|
||||
one and `t` in round two and it tolerates a round-two subset; prefractal uses
|
||||
`t` in both rounds and requires set equality. Iceberg cannot express 2-of-2 or
|
||||
3-of-4; prefractal can.
|
||||
|
||||
## Build
|
||||
|
||||
The module depends on both `frost` and `musig` and forces them on.
|
||||
|
||||
```
|
||||
cmake -B build -DSECP256K1_ENABLE_MODULE_PREFRACTAL=ON -DSECP256K1_BUILD_TESTS=ON
|
||||
cmake --build build && ./build/bin/tests --target=prefractal
|
||||
```
|
||||
|
||||
```
|
||||
./autogen.sh
|
||||
./configure --enable-experimental --enable-module-prefractal
|
||||
make && make check
|
||||
```
|
||||
|
||||
Three files order their module blocks differently, and the constraints point in
|
||||
opposite directions. Anyone adding a module by copying this one should read
|
||||
this rather than copying `iceberg`'s positions:
|
||||
|
||||
- `src/secp256k1.c` — the include goes **after** `frost` and `musig`, because
|
||||
the module calls their `static` internals and the whole library is one
|
||||
translation unit.
|
||||
- `src/CMakeLists.txt` — the block goes **before** both, because its `set()`
|
||||
calls are only observed by blocks that run later.
|
||||
- `configure.ac` — the block likewise goes **before** the `musig` block, *not*
|
||||
at `iceberg`'s position further down. `configure.ac` orders `musig` and
|
||||
`frost` ahead of `iceberg`, and iceberg's late `enable_module_musig=yes` is
|
||||
harmless only because `musig` defaults to yes. `frost` defaults to **no**, so
|
||||
a late force-enable would leave `-DENABLE_MODULE_FROST=1` unemitted while
|
||||
`AM_CONDITIONAL` still observed the mutation.
|
||||
|
||||
`frost` is also the first default-OFF module anything depends on, which breaks
|
||||
the dependency-guard idiom used everywhere else in both build systems. The
|
||||
existing `DEFINED X AND NOT X` (CMake) and `x$X = xno` (autotools) tests read as
|
||||
"the user disabled it explicitly" only for default-ON modules, and are true by
|
||||
default for a default-OFF one. Neither build system can distinguish an explicit
|
||||
disable from the default once both are in the cache, so enabling `prefractal`
|
||||
simply implies `frost`; the guard is kept for `musig`, where it still means what
|
||||
it says.
|
||||
@@ -1,94 +0,0 @@
|
||||
# Release process
|
||||
|
||||
This document outlines the process for releasing versions of the form `$MAJOR.$MINOR.$PATCH`.
|
||||
|
||||
We distinguish between two types of releases: *regular* and *maintenance* releases.
|
||||
Regular releases are releases of a new major or minor version as well as patches of the most recent release.
|
||||
Maintenance releases, on the other hand, are required for patches of older releases.
|
||||
|
||||
You should coordinate with the other maintainers on the release date, if possible.
|
||||
This date will be part of the release entry in [CHANGELOG.md](../CHANGELOG.md) and it should match the dates of the remaining steps in the release process (including the date of the tag and the GitHub release).
|
||||
It is best if the maintainers are present during the release, so they can help ensure that the process is followed correctly and, in the case of a regular release, they are aware that they should not modify the master branch between merging the PR in step 1 and the PR in step 3.
|
||||
|
||||
This process also assumes that there will be no minor releases for old major releases.
|
||||
|
||||
We aim to cut a regular release every 3-4 months, approximately twice as frequent as major Bitcoin Core releases. Every second release should be published one month before the feature freeze of the next major Bitcoin Core release, allowing sufficient time to update the library in Core.
|
||||
|
||||
## Sanity checks
|
||||
Perform these checks when reviewing the release PR (see below):
|
||||
|
||||
1. Ensure `make distcheck` doesn't fail.
|
||||
```shell
|
||||
./autogen.sh && ./configure --enable-dev-mode && make distcheck
|
||||
```
|
||||
2. Check installation with autotools:
|
||||
```shell
|
||||
dir=$(mktemp -d)
|
||||
./autogen.sh && ./configure --prefix=$dir && make clean && make install && ls -RlAh $dir
|
||||
gcc -o ecdsa examples/ecdsa.c $(PKG_CONFIG_PATH=$dir/lib/pkgconfig pkg-config --cflags --libs libsecp256k1) -Wl,-rpath,"$dir/lib" && ./ecdsa
|
||||
```
|
||||
3. Check installation with CMake:
|
||||
```shell
|
||||
dir=$(mktemp -d)
|
||||
build=$(mktemp -d)
|
||||
cmake -B $build -DCMAKE_INSTALL_PREFIX=$dir && cmake --build $build && cmake --install $build && ls -RlAh $dir
|
||||
gcc -o ecdsa examples/ecdsa.c -I $dir/include -L $dir/lib*/ -l secp256k1 -Wl,-rpath,"$dir/lib",-rpath,"$dir/lib64" && ./ecdsa
|
||||
```
|
||||
4. Use the [`check-abi.sh`](/tools/check-abi.sh) tool to verify that there are no unexpected ABI incompatibilities and that the version number and the release notes accurately reflect all potential ABI changes. To run this tool, the `abi-dumper` and `abi-compliance-checker` packages are required.
|
||||
```shell
|
||||
tools/check-abi.sh
|
||||
```
|
||||
|
||||
## Regular release
|
||||
|
||||
1. Open a PR to the master branch with a commit (using message `"release: prepare for $MAJOR.$MINOR.$PATCH"`, for example) that
|
||||
* finalizes the release notes in [CHANGELOG.md](../CHANGELOG.md) by
|
||||
* adding a section for the release (make sure that the version number is a link to a diff between the previous and new version),
|
||||
* removing the `[Unreleased]` section header,
|
||||
* ensuring that the release notes are not missing entries (check the `needs-changelog` label on github), and
|
||||
* including an entry for `### ABI Compatibility` if it doesn't exist,
|
||||
* sets `_PKG_VERSION_IS_RELEASE` to `true` in `configure.ac`, and,
|
||||
* if this is not a patch release,
|
||||
* updates `_PKG_VERSION_*` and `_LIB_VERSION_*` in `configure.ac`, and
|
||||
* updates `project(libsecp256k1 VERSION ...)` and `${PROJECT_NAME}_LIB_VERSION_*` in `CMakeLists.txt`.
|
||||
2. Perform the [sanity checks](#sanity-checks) on the PR branch.
|
||||
3. After the PR is merged, tag the commit, and push the tag:
|
||||
```
|
||||
RELEASE_COMMIT=<merge commit of step 1>
|
||||
git tag -s v$MAJOR.$MINOR.$PATCH -m "libsecp256k1 $MAJOR.$MINOR.$PATCH" $RELEASE_COMMIT
|
||||
git push git@github.com:bitcoin-core/secp256k1.git v$MAJOR.$MINOR.$PATCH
|
||||
```
|
||||
4. Open a PR to the master branch with a commit (using message `"release cleanup: bump version after $MAJOR.$MINOR.$PATCH"`, for example) that
|
||||
* sets `_PKG_VERSION_IS_RELEASE` to `false` and increments `_PKG_VERSION_PATCH` and `_LIB_VERSION_REVISION` in `configure.ac`,
|
||||
* increments the `$PATCH` component of `project(libsecp256k1 VERSION ...)` and `${PROJECT_NAME}_LIB_VERSION_REVISION` in `CMakeLists.txt`, and
|
||||
* adds an `[Unreleased]` section header and a corresponding `[Unreleased]` link at the bottom of [CHANGELOG.md](../CHANGELOG.md).
|
||||
|
||||
If other maintainers are not present to approve the PR, it can be merged without ACKs.
|
||||
5. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md).
|
||||
6. Send an announcement email to the bitcoin-dev mailing list.
|
||||
|
||||
## Maintenance release
|
||||
|
||||
Note that bug fixes need to be backported only to releases for which no compatible release without the bug exists.
|
||||
|
||||
1. If there's no maintenance branch `$MAJOR.$MINOR`, create one:
|
||||
```
|
||||
git checkout -b $MAJOR.$MINOR v$MAJOR.$MINOR.$((PATCH - 1))
|
||||
git push git@github.com:bitcoin-core/secp256k1.git $MAJOR.$MINOR
|
||||
```
|
||||
2. Open a pull request to the `$MAJOR.$MINOR` branch that
|
||||
* includes the bug fixes,
|
||||
* finalizes the release notes similar to a regular release,
|
||||
* increments `_PKG_VERSION_PATCH` and `_LIB_VERSION_REVISION` in `configure.ac`
|
||||
and the `$PATCH` component of `project(libsecp256k1 VERSION ...)` and `${PROJECT_NAME}_LIB_VERSION_REVISION` in `CMakeLists.txt`
|
||||
(with commit message `"release: bump versions for $MAJOR.$MINOR.$PATCH"`, for example).
|
||||
3. Perform the [sanity checks](#sanity-checks) on the PR branch.
|
||||
4. After the PRs are merged, update the release branch, tag the commit, and push the tag:
|
||||
```
|
||||
git checkout $MAJOR.$MINOR && git pull
|
||||
git tag -s v$MAJOR.$MINOR.$PATCH -m "libsecp256k1 $MAJOR.$MINOR.$PATCH"
|
||||
git push git@github.com:bitcoin-core/secp256k1.git v$MAJOR.$MINOR.$PATCH
|
||||
```
|
||||
6. Create a new GitHub release with a link to the corresponding entry in [CHANGELOG.md](../CHANGELOG.md).
|
||||
7. Send an announcement email to the bitcoin-dev mailing list.
|
||||
8. Open PR to the master branch that includes a commit (with commit message `"release notes: add $MAJOR.$MINOR.$PATCH"`, for example) that adds release notes to [CHANGELOG.md](../CHANGELOG.md).
|
||||
@@ -1,819 +0,0 @@
|
||||
# The safegcd implementation in libsecp256k1 explained
|
||||
|
||||
This document explains the modular inverse and Jacobi symbol implementations in the `src/modinv*.h` files.
|
||||
It is based on the paper
|
||||
["Fast constant-time gcd computation and modular inversion"](https://gcd.cr.yp.to/papers.html#safegcd)
|
||||
by Daniel J. Bernstein and Bo-Yin Yang. The references below are for the Date: 2019.04.13 version.
|
||||
|
||||
The actual implementation is in C of course, but for demonstration purposes Python3 is used here.
|
||||
Most implementation aspects and optimizations are explained, except those that depend on the specific
|
||||
number representation used in the C code.
|
||||
|
||||
## 1. Computing the Greatest Common Divisor (GCD) using divsteps
|
||||
|
||||
The algorithm from the paper (section 11), at a very high level, is this:
|
||||
|
||||
```python
|
||||
def gcd(f, g):
|
||||
"""Compute the GCD of an odd integer f and another integer g."""
|
||||
assert f & 1 # require f to be odd
|
||||
delta = 1 # additional state variable
|
||||
while g != 0:
|
||||
assert f & 1 # f will be odd in every iteration
|
||||
if delta > 0 and g & 1:
|
||||
delta, f, g = 1 - delta, g, (g - f) // 2
|
||||
elif g & 1:
|
||||
delta, f, g = 1 + delta, f, (g + f) // 2
|
||||
else:
|
||||
delta, f, g = 1 + delta, f, (g ) // 2
|
||||
return abs(f)
|
||||
```
|
||||
|
||||
It computes the greatest common divisor of an odd integer *f* and any integer *g*. Its inner loop
|
||||
keeps rewriting the variables *f* and *g* alongside a state variable *δ* that starts at *1*, until
|
||||
*g=0* is reached. At that point, *|f|* gives the GCD. Each of the transitions in the loop is called a
|
||||
"division step" (referred to as divstep in what follows).
|
||||
|
||||
For example, *gcd(21, 14)* would be computed as:
|
||||
- Start with *δ=1 f=21 g=14*
|
||||
- Take the third branch: *δ=2 f=21 g=7*
|
||||
- Take the first branch: *δ=-1 f=7 g=-7*
|
||||
- Take the second branch: *δ=0 f=7 g=0*
|
||||
- The answer *|f| = 7*.
|
||||
|
||||
Why it works:
|
||||
- Divsteps can be decomposed into two steps (see paragraph 8.2 in the paper):
|
||||
- (a) If *g* is odd, replace *(f,g)* with *(g,g-f)* or (f,g+f), resulting in an even *g*.
|
||||
- (b) Replace *(f,g)* with *(f,g/2)* (where *g* is guaranteed to be even).
|
||||
- Neither of those two operations change the GCD:
|
||||
- For (a), assume *gcd(f,g)=c*, then it must be the case that *f=a c* and *g=b c* for some integers *a*
|
||||
and *b*. As *(g,g-f)=(b c,(b-a)c)* and *(f,f+g)=(a c,(a+b)c)*, the result clearly still has
|
||||
common factor *c*. Reasoning in the other direction shows that no common factor can be added by
|
||||
doing so either.
|
||||
- For (b), we know that *f* is odd, so *gcd(f,g)* clearly has no factor *2*, and we can remove
|
||||
it from *g*.
|
||||
- The algorithm will eventually converge to *g=0*. This is proven in the paper (see theorem G.3).
|
||||
- It follows that eventually we find a final value *f'* for which *gcd(f,g) = gcd(f',0)*. As the
|
||||
gcd of *f'* and *0* is *|f'|* by definition, that is our answer.
|
||||
|
||||
Compared to more [traditional GCD algorithms](https://en.wikipedia.org/wiki/Euclidean_algorithm), this one has the property of only ever looking at
|
||||
the low-order bits of the variables to decide the next steps, and being easy to make
|
||||
constant-time (in more low-level languages than Python). The *δ* parameter is necessary to
|
||||
guide the algorithm towards shrinking the numbers' magnitudes without explicitly needing to look
|
||||
at high order bits.
|
||||
|
||||
Properties that will become important later:
|
||||
- Performing more divsteps than needed is not a problem, as *f* does not change anymore after *g=0*.
|
||||
- Only even numbers are divided by *2*. This means that when reasoning about it algebraically we
|
||||
do not need to worry about rounding.
|
||||
- At every point during the algorithm's execution the next *N* steps only depend on the bottom *N*
|
||||
bits of *f* and *g*, and on *δ*.
|
||||
|
||||
|
||||
## 2. From GCDs to modular inverses
|
||||
|
||||
We want an algorithm to compute the inverse *a* of *x* modulo *M*, i.e. the number a such that *a x=1
|
||||
mod M*. This inverse only exists if the GCD of *x* and *M* is *1*, but that is always the case if *M* is
|
||||
prime and *0 < x < M*. In what follows, assume that the modular inverse exists.
|
||||
It turns out this inverse can be computed as a side effect of computing the GCD by keeping track
|
||||
of how the internal variables can be written as linear combinations of the inputs at every step
|
||||
(see the [extended Euclidean algorithm](https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm)).
|
||||
Since the GCD is *1*, such an algorithm will compute numbers *a* and *b* such that a x + b M = 1*.
|
||||
Taking that expression *mod M* gives *a x mod M = 1*, and we see that *a* is the modular inverse of *x
|
||||
mod M*.
|
||||
|
||||
A similar approach can be used to calculate modular inverses using the divsteps-based GCD
|
||||
algorithm shown above, if the modulus *M* is odd. To do so, compute *gcd(f=M,g=x)*, while keeping
|
||||
track of extra variables *d* and *e*, for which at every step *d = f/x (mod M)* and *e = g/x (mod M)*.
|
||||
*f/x* here means the number which multiplied with *x* gives *f mod M*. As *f* and *g* are initialized to *M*
|
||||
and *x* respectively, *d* and *e* just start off being *0* (*M/x mod M = 0/x mod M = 0*) and *1* (*x/x mod M
|
||||
= 1*).
|
||||
|
||||
```python
|
||||
def div2(M, x):
|
||||
"""Helper routine to compute x/2 mod M (where M is odd)."""
|
||||
assert M & 1
|
||||
if x & 1: # If x is odd, make it even by adding M.
|
||||
x += M
|
||||
# x must be even now, so a clean division by 2 is possible.
|
||||
return x // 2
|
||||
|
||||
def modinv(M, x):
|
||||
"""Compute the inverse of x mod M (given that it exists, and M is odd)."""
|
||||
assert M & 1
|
||||
delta, f, g, d, e = 1, M, x, 0, 1
|
||||
while g != 0:
|
||||
# Note that while division by two for f and g is only ever done on even inputs, this is
|
||||
# not true for d and e, so we need the div2 helper function.
|
||||
if delta > 0 and g & 1:
|
||||
delta, f, g, d, e = 1 - delta, g, (g - f) // 2, e, div2(M, e - d)
|
||||
elif g & 1:
|
||||
delta, f, g, d, e = 1 + delta, f, (g + f) // 2, d, div2(M, e + d)
|
||||
else:
|
||||
delta, f, g, d, e = 1 + delta, f, (g ) // 2, d, div2(M, e )
|
||||
# Verify that the invariants d=f/x mod M, e=g/x mod M are maintained.
|
||||
assert f % M == (d * x) % M
|
||||
assert g % M == (e * x) % M
|
||||
assert f == 1 or f == -1 # |f| is the GCD, it must be 1
|
||||
# Because of invariant d = f/x (mod M), 1/x = d/f (mod M). As |f|=1, d/f = d*f.
|
||||
return (d * f) % M
|
||||
```
|
||||
|
||||
Also note that this approach to track *d* and *e* throughout the computation to determine the inverse
|
||||
is different from the paper. There (see paragraph 12.1 in the paper) a transition matrix for the
|
||||
entire computation is determined (see section 3 below) and the inverse is computed from that.
|
||||
The approach here avoids the need for 2x2 matrix multiplications of various sizes, and appears to
|
||||
be faster at the level of optimization we're able to do in C.
|
||||
|
||||
|
||||
## 3. Batching multiple divsteps
|
||||
|
||||
Every divstep can be expressed as a matrix multiplication, applying a transition matrix *(1/2 t)*
|
||||
to both vectors *[f, g]* and *[d, e]* (see paragraph 8.1 in the paper):
|
||||
|
||||
```
|
||||
t = [ u, v ]
|
||||
[ q, r ]
|
||||
|
||||
[ out_f ] = (1/2 * t) * [ in_f ]
|
||||
[ out_g ] = [ in_g ]
|
||||
|
||||
[ out_d ] = (1/2 * t) * [ in_d ] (mod M)
|
||||
[ out_e ] [ in_e ]
|
||||
```
|
||||
|
||||
where *(u, v, q, r)* is *(0, 2, -1, 1)*, *(2, 0, 1, 1)*, or *(2, 0, 0, 1)*, depending on which branch is
|
||||
taken. As above, the resulting *f* and *g* are always integers.
|
||||
|
||||
Performing multiple divsteps corresponds to a multiplication with the product of all the
|
||||
individual divsteps' transition matrices. As each transition matrix consists of integers
|
||||
divided by *2*, the product of these matrices will consist of integers divided by *2<sup>N</sup>* (see also
|
||||
theorem 9.2 in the paper). These divisions are expensive when updating *d* and *e*, so we delay
|
||||
them: we compute the integer coefficients of the combined transition matrix scaled by *2<sup>N</sup>*, and
|
||||
do one division by *2<sup>N</sup>* as a final step:
|
||||
|
||||
```python
|
||||
def divsteps_n_matrix(delta, f, g):
|
||||
"""Compute delta and transition matrix t after N divsteps (multiplied by 2^N)."""
|
||||
u, v, q, r = 1, 0, 0, 1 # start with identity matrix
|
||||
for _ in range(N):
|
||||
if delta > 0 and g & 1:
|
||||
delta, f, g, u, v, q, r = 1 - delta, g, (g - f) // 2, 2*q, 2*r, q-u, r-v
|
||||
elif g & 1:
|
||||
delta, f, g, u, v, q, r = 1 + delta, f, (g + f) // 2, 2*u, 2*v, q+u, r+v
|
||||
else:
|
||||
delta, f, g, u, v, q, r = 1 + delta, f, (g ) // 2, 2*u, 2*v, q , r
|
||||
return delta, (u, v, q, r)
|
||||
```
|
||||
|
||||
As the branches in the divsteps are completely determined by the bottom *N* bits of *f* and *g*, this
|
||||
function to compute the transition matrix only needs to see those bottom bits. Furthermore all
|
||||
intermediate results and outputs fit in *(N+1)*-bit numbers (unsigned for *f* and *g*; signed for *u*, *v*,
|
||||
*q*, and *r*) (see also paragraph 8.3 in the paper). This means that an implementation using 64-bit
|
||||
integers could set *N=62* and compute the full transition matrix for 62 steps at once without any
|
||||
big integer arithmetic at all. This is the reason why this algorithm is efficient: it only needs
|
||||
to update the full-size *f*, *g*, *d*, and *e* numbers once every *N* steps.
|
||||
|
||||
We still need functions to compute:
|
||||
|
||||
```
|
||||
[ out_f ] = (1/2^N * [ u, v ]) * [ in_f ]
|
||||
[ out_g ] ( [ q, r ]) [ in_g ]
|
||||
|
||||
[ out_d ] = (1/2^N * [ u, v ]) * [ in_d ] (mod M)
|
||||
[ out_e ] ( [ q, r ]) [ in_e ]
|
||||
```
|
||||
|
||||
Because the divsteps transformation only ever divides even numbers by two, the result of *t [f,g]* is always even. When *t* is a composition of *N* divsteps, it follows that the resulting *f*
|
||||
and *g* will be multiple of *2<sup>N</sup>*, and division by *2<sup>N</sup>* is simply shifting them down:
|
||||
|
||||
```python
|
||||
def update_fg(f, g, t):
|
||||
"""Multiply matrix t/2^N with [f, g]."""
|
||||
u, v, q, r = t
|
||||
cf, cg = u*f + v*g, q*f + r*g
|
||||
# (t / 2^N) should cleanly apply to [f,g] so the result of t*[f,g] should have N zero
|
||||
# bottom bits.
|
||||
assert cf % 2**N == 0
|
||||
assert cg % 2**N == 0
|
||||
return cf >> N, cg >> N
|
||||
```
|
||||
|
||||
The same is not true for *d* and *e*, and we need an equivalent of the `div2` function for division by *2<sup>N</sup> mod M*.
|
||||
This is easy if we have precomputed *1/M mod 2<sup>N</sup>* (which always exists for odd *M*):
|
||||
|
||||
```python
|
||||
def div2n(M, Mi, x):
|
||||
"""Compute x/2^N mod M, given Mi = 1/M mod 2^N."""
|
||||
assert (M * Mi) % 2**N == 1
|
||||
# Find a factor m such that m*M has the same bottom N bits as x. We want:
|
||||
# (m * M) mod 2^N = x mod 2^N
|
||||
# <=> m mod 2^N = (x / M) mod 2^N
|
||||
# <=> m mod 2^N = (x * Mi) mod 2^N
|
||||
m = (Mi * x) % 2**N
|
||||
# Subtract that multiple from x, cancelling its bottom N bits.
|
||||
x -= m * M
|
||||
# Now a clean division by 2^N is possible.
|
||||
assert x % 2**N == 0
|
||||
return (x >> N) % M
|
||||
|
||||
def update_de(d, e, t, M, Mi):
|
||||
"""Multiply matrix t/2^N with [d, e], modulo M."""
|
||||
u, v, q, r = t
|
||||
cd, ce = u*d + v*e, q*d + r*e
|
||||
return div2n(M, Mi, cd), div2n(M, Mi, ce)
|
||||
```
|
||||
|
||||
With all of those, we can write a version of `modinv` that performs *N* divsteps at once:
|
||||
|
||||
```python3
|
||||
def modinv(M, Mi, x):
|
||||
"""Compute the modular inverse of x mod M, given Mi=1/M mod 2^N."""
|
||||
assert M & 1
|
||||
delta, f, g, d, e = 1, M, x, 0, 1
|
||||
while g != 0:
|
||||
# Compute the delta and transition matrix t for the next N divsteps (this only needs
|
||||
# (N+1)-bit signed integer arithmetic).
|
||||
delta, t = divsteps_n_matrix(delta, f % 2**N, g % 2**N)
|
||||
# Apply the transition matrix t to [f, g]:
|
||||
f, g = update_fg(f, g, t)
|
||||
# Apply the transition matrix t to [d, e]:
|
||||
d, e = update_de(d, e, t, M, Mi)
|
||||
return (d * f) % M
|
||||
```
|
||||
|
||||
This means that in practice we'll always perform a multiple of *N* divsteps. This is not a problem
|
||||
because once *g=0*, further divsteps do not affect *f*, *g*, *d*, or *e* anymore (only *δ* keeps
|
||||
increasing). For variable time code such excess iterations will be mostly optimized away in later
|
||||
sections.
|
||||
|
||||
|
||||
## 4. Avoiding modulus operations
|
||||
|
||||
So far, there are two places where we compute a remainder of big numbers modulo *M*: at the end of
|
||||
`div2n` in every `update_de`, and at the very end of `modinv` after potentially negating *d* due to the
|
||||
sign of *f*. These are relatively expensive operations when done generically.
|
||||
|
||||
To deal with the modulus operation in `div2n`, we simply stop requiring *d* and *e* to be in range
|
||||
*[0,M)* all the time. Let's start by inlining `div2n` into `update_de`, and dropping the modulus
|
||||
operation at the end:
|
||||
|
||||
```python
|
||||
def update_de(d, e, t, M, Mi):
|
||||
"""Multiply matrix t/2^N with [d, e] mod M, given Mi=1/M mod 2^N."""
|
||||
u, v, q, r = t
|
||||
cd, ce = u*d + v*e, q*d + r*e
|
||||
# Cancel out bottom N bits of cd and ce.
|
||||
md = -((Mi * cd) % 2**N)
|
||||
me = -((Mi * ce) % 2**N)
|
||||
cd += md * M
|
||||
ce += me * M
|
||||
# And cleanly divide by 2**N.
|
||||
return cd >> N, ce >> N
|
||||
```
|
||||
|
||||
Let's look at bounds on the ranges of these numbers. It can be shown that *|u|+|v|* and *|q|+|r|*
|
||||
never exceed *2<sup>N</sup>* (see paragraph 8.3 in the paper), and thus a multiplication with *t* will have
|
||||
outputs whose absolute values are at most *2<sup>N</sup>* times the maximum absolute input value. In case the
|
||||
inputs *d* and *e* are in *(-M,M)*, which is certainly true for the initial values *d=0* and *e=1* assuming
|
||||
*M > 1*, the multiplication results in numbers in range *(-2<sup>N</sup>M,2<sup>N</sup>M)*. Subtracting less than *2<sup>N</sup>*
|
||||
times *M* to cancel out *N* bits brings that up to *(-2<sup>N+1</sup>M,2<sup>N</sup>M)*, and
|
||||
dividing by *2<sup>N</sup>* at the end takes it to *(-2M,M)*. Another application of `update_de` would take that
|
||||
to *(-3M,2M)*, and so forth. This progressive expansion of the variables' ranges can be
|
||||
counteracted by incrementing *d* and *e* by *M* whenever they're negative:
|
||||
|
||||
```python
|
||||
...
|
||||
if d < 0:
|
||||
d += M
|
||||
if e < 0:
|
||||
e += M
|
||||
cd, ce = u*d + v*e, q*d + r*e
|
||||
# Cancel out bottom N bits of cd and ce.
|
||||
...
|
||||
```
|
||||
|
||||
With inputs in *(-2M,M)*, they will first be shifted into range *(-M,M)*, which means that the
|
||||
output will again be in *(-2M,M)*, and this remains the case regardless of how many `update_de`
|
||||
invocations there are. In what follows, we will try to make this more efficient.
|
||||
|
||||
Note that increasing *d* by *M* is equal to incrementing *cd* by *u M* and *ce* by *q M*. Similarly,
|
||||
increasing *e* by *M* is equal to incrementing *cd* by *v M* and *ce* by *r M*. So we could instead write:
|
||||
|
||||
```python
|
||||
...
|
||||
cd, ce = u*d + v*e, q*d + r*e
|
||||
# Perform the equivalent of incrementing d, e by M when they're negative.
|
||||
if d < 0:
|
||||
cd += u*M
|
||||
ce += q*M
|
||||
if e < 0:
|
||||
cd += v*M
|
||||
ce += r*M
|
||||
# Cancel out bottom N bits of cd and ce.
|
||||
md = -((Mi * cd) % 2**N)
|
||||
me = -((Mi * ce) % 2**N)
|
||||
cd += md * M
|
||||
ce += me * M
|
||||
...
|
||||
```
|
||||
|
||||
Now note that we have two steps of corrections to *cd* and *ce* that add multiples of *M*: this
|
||||
increment, and the decrement that cancels out bottom bits. The second one depends on the first
|
||||
one, but they can still be efficiently combined by only computing the bottom bits of *cd* and *ce*
|
||||
at first, and using that to compute the final *md*, *me* values:
|
||||
|
||||
```python
|
||||
def update_de(d, e, t, M, Mi):
|
||||
"""Multiply matrix t/2^N with [d, e], modulo M."""
|
||||
u, v, q, r = t
|
||||
md, me = 0, 0
|
||||
# Compute what multiples of M to add to cd and ce.
|
||||
if d < 0:
|
||||
md += u
|
||||
me += q
|
||||
if e < 0:
|
||||
md += v
|
||||
me += r
|
||||
# Compute bottom N bits of t*[d,e] + M*[md,me].
|
||||
cd, ce = (u*d + v*e + md*M) % 2**N, (q*d + r*e + me*M) % 2**N
|
||||
# Correct md and me such that the bottom N bits of t*[d,e] + M*[md,me] are zero.
|
||||
md -= (Mi * cd) % 2**N
|
||||
me -= (Mi * ce) % 2**N
|
||||
# Do the full computation.
|
||||
cd, ce = u*d + v*e + md*M, q*d + r*e + me*M
|
||||
# And cleanly divide by 2**N.
|
||||
return cd >> N, ce >> N
|
||||
```
|
||||
|
||||
One last optimization: we can avoid the *md M* and *me M* multiplications in the bottom bits of *cd*
|
||||
and *ce* by moving them to the *md* and *me* correction:
|
||||
|
||||
```python
|
||||
...
|
||||
# Compute bottom N bits of t*[d,e].
|
||||
cd, ce = (u*d + v*e) % 2**N, (q*d + r*e) % 2**N
|
||||
# Correct md and me such that the bottom N bits of t*[d,e]+M*[md,me] are zero.
|
||||
# Note that this is not the same as {md = (-Mi * cd) % 2**N} etc. That would also result in N
|
||||
# zero bottom bits, but isn't guaranteed to be a reduction of [0,2^N) compared to the
|
||||
# previous md and me values, and thus would violate our bounds analysis.
|
||||
md -= (Mi*cd + md) % 2**N
|
||||
me -= (Mi*ce + me) % 2**N
|
||||
...
|
||||
```
|
||||
|
||||
The resulting function takes *d* and *e* in range *(-2M,M)* as inputs, and outputs values in the same
|
||||
range. That also means that the *d* value at the end of `modinv` will be in that range, while we want
|
||||
a result in *[0,M)*. To do that, we need a normalization function. It's easy to integrate the
|
||||
conditional negation of *d* (based on the sign of *f*) into it as well:
|
||||
|
||||
```python
|
||||
def normalize(sign, v, M):
|
||||
"""Compute sign*v mod M, where v is in range (-2*M,M); output in [0,M)."""
|
||||
assert sign == 1 or sign == -1
|
||||
# v in (-2*M,M)
|
||||
if v < 0:
|
||||
v += M
|
||||
# v in (-M,M). Now multiply v with sign (which can only be 1 or -1).
|
||||
if sign == -1:
|
||||
v = -v
|
||||
# v in (-M,M)
|
||||
if v < 0:
|
||||
v += M
|
||||
# v in [0,M)
|
||||
return v
|
||||
```
|
||||
|
||||
And calling it in `modinv` is simply:
|
||||
|
||||
```python
|
||||
...
|
||||
return normalize(f, d, M)
|
||||
```
|
||||
|
||||
|
||||
## 5. Constant-time operation
|
||||
|
||||
The primary selling point of the algorithm is fast constant-time operation. What code flow still
|
||||
depends on the input data so far?
|
||||
|
||||
- the number of iterations of the while *g ≠ 0* loop in `modinv`
|
||||
- the branches inside `divsteps_n_matrix`
|
||||
- the sign checks in `update_de`
|
||||
- the sign checks in `normalize`
|
||||
|
||||
To make the while loop in `modinv` constant time it can be replaced with a constant number of
|
||||
iterations. The paper proves (Theorem 11.2) that *741* divsteps are sufficient for any *256*-bit
|
||||
inputs, and [safegcd-bounds](https://github.com/sipa/safegcd-bounds) shows that the slightly better bound *724* is
|
||||
sufficient even. Given that every loop iteration performs *N* divsteps, it will run a total of
|
||||
*⌈724/N⌉* times.
|
||||
|
||||
To deal with the branches in `divsteps_n_matrix` we will replace them with constant-time bitwise
|
||||
operations (and hope the C compiler isn't smart enough to turn them back into branches; see
|
||||
`ctime_tests.c` for automated tests that this isn't the case). To do so, observe that a
|
||||
divstep can be written instead as (compare to the inner loop of `gcd` in section 1).
|
||||
|
||||
```python
|
||||
x = -f if delta > 0 else f # set x equal to (input) -f or f
|
||||
if g & 1:
|
||||
g += x # set g to (input) g-f or g+f
|
||||
if delta > 0:
|
||||
delta = -delta
|
||||
f += g # set f to (input) g (note that g was set to g-f before)
|
||||
delta += 1
|
||||
g >>= 1
|
||||
```
|
||||
|
||||
To convert the above to bitwise operations, we rely on a trick to negate conditionally: per the
|
||||
definition of negative numbers in two's complement, (*-v == ~v + 1*) holds for every number *v*. As
|
||||
*-1* in two's complement is all *1* bits, bitflipping can be expressed as xor with *-1*. It follows
|
||||
that *-v == (v ^ -1) - (-1)*. Thus, if we have a variable *c* that takes on values *0* or *-1*, then
|
||||
*(v ^ c) - c* is *v* if *c=0* and *-v* if *c=-1*.
|
||||
|
||||
Using this we can write:
|
||||
|
||||
```python
|
||||
x = -f if delta > 0 else f
|
||||
```
|
||||
|
||||
in constant-time form as:
|
||||
|
||||
```python
|
||||
c1 = (-delta) >> 63
|
||||
# Conditionally negate f based on c1:
|
||||
x = (f ^ c1) - c1
|
||||
```
|
||||
|
||||
To use that trick, we need a helper mask variable *c1* that resolves the condition *δ>0* to *-1*
|
||||
(if true) or *0* (if false). We compute *c1* using right shifting, which is equivalent to dividing by
|
||||
the specified power of *2* and rounding down (in Python, and also in C under the assumption of a typical two's complement system; see
|
||||
`assumptions.h` for tests that this is the case). Right shifting by *63* thus maps all
|
||||
numbers in range *[-2<sup>63</sup>,0)* to *-1*, and numbers in range *[0,2<sup>63</sup>)* to *0*.
|
||||
|
||||
Using the facts that *x&0=0* and *x&(-1)=x* (on two's complement systems again), we can write:
|
||||
|
||||
```python
|
||||
if g & 1:
|
||||
g += x
|
||||
```
|
||||
|
||||
as:
|
||||
|
||||
```python
|
||||
# Compute c2=0 if g is even and c2=-1 if g is odd.
|
||||
c2 = -(g & 1)
|
||||
# This masks out x if g is even, and leaves x be if g is odd.
|
||||
g += x & c2
|
||||
```
|
||||
|
||||
Using the conditional negation trick again we can write:
|
||||
|
||||
```python
|
||||
if g & 1:
|
||||
if delta > 0:
|
||||
delta = -delta
|
||||
```
|
||||
|
||||
as:
|
||||
|
||||
```python
|
||||
# Compute c3=-1 if g is odd and delta>0, and 0 otherwise.
|
||||
c3 = c1 & c2
|
||||
# Conditionally negate delta based on c3:
|
||||
delta = (delta ^ c3) - c3
|
||||
```
|
||||
|
||||
Finally:
|
||||
|
||||
```python
|
||||
if g & 1:
|
||||
if delta > 0:
|
||||
f += g
|
||||
```
|
||||
|
||||
becomes:
|
||||
|
||||
```python
|
||||
f += g & c3
|
||||
```
|
||||
|
||||
It turns out that this can be implemented more efficiently by applying the substitution
|
||||
*η=-δ*. In this representation, negating *δ* corresponds to negating *η*, and incrementing
|
||||
*δ* corresponds to decrementing *η*. This allows us to remove the negation in the *c1*
|
||||
computation:
|
||||
|
||||
```python
|
||||
# Compute a mask c1 for eta < 0, and compute the conditional negation x of f:
|
||||
c1 = eta >> 63
|
||||
x = (f ^ c1) - c1
|
||||
# Compute a mask c2 for odd g, and conditionally add x to g:
|
||||
c2 = -(g & 1)
|
||||
g += x & c2
|
||||
# Compute a mask c for (eta < 0) and odd (input) g, and use it to conditionally negate eta,
|
||||
# and add g to f:
|
||||
c3 = c1 & c2
|
||||
eta = (eta ^ c3) - c3
|
||||
f += g & c3
|
||||
# Incrementing delta corresponds to decrementing eta.
|
||||
eta -= 1
|
||||
g >>= 1
|
||||
```
|
||||
|
||||
A variant of divsteps with better worst-case performance can be used instead: starting *δ* at
|
||||
*1/2* instead of *1*. This reduces the worst case number of iterations to *590* for *256*-bit inputs
|
||||
(which can be shown using convex hull analysis). In this case, the substitution *ζ=-(δ+1/2)*
|
||||
is used instead to keep the variable integral. Incrementing *δ* by *1* still translates to
|
||||
decrementing *ζ* by *1*, but negating *δ* now corresponds to going from *ζ* to *-(ζ+1)*, or
|
||||
*~ζ*. Doing that conditionally based on *c3* is simply:
|
||||
|
||||
```python
|
||||
...
|
||||
c3 = c1 & c2
|
||||
zeta ^= c3
|
||||
...
|
||||
```
|
||||
|
||||
By replacing the loop in `divsteps_n_matrix` with a variant of the divstep code above (extended to
|
||||
also apply all *f* operations to *u*, *v* and all *g* operations to *q*, *r*), a constant-time version of
|
||||
`divsteps_n_matrix` is obtained. The full code will be in section 7.
|
||||
|
||||
These bit fiddling tricks can also be used to make the conditional negations and additions in
|
||||
`update_de` and `normalize` constant-time.
|
||||
|
||||
|
||||
## 6. Variable-time optimizations
|
||||
|
||||
In section 5, we modified the `divsteps_n_matrix` function (and a few others) to be constant time.
|
||||
Constant time operations are only necessary when computing modular inverses of secret data. In
|
||||
other cases, it slows down calculations unnecessarily. In this section, we will construct a
|
||||
faster non-constant time `divsteps_n_matrix` function.
|
||||
|
||||
To do so, first consider yet another way of writing the inner loop of divstep operations in
|
||||
`gcd` from section 1. This decomposition is also explained in the paper in section 8.2. We use
|
||||
the original version with initial *δ=1* and *η=-δ* here.
|
||||
|
||||
```python
|
||||
for _ in range(N):
|
||||
if g & 1 and eta < 0:
|
||||
eta, f, g = -eta, g, -f
|
||||
if g & 1:
|
||||
g += f
|
||||
eta -= 1
|
||||
g >>= 1
|
||||
```
|
||||
|
||||
Whenever *g* is even, the loop only shifts *g* down and decreases *η*. When *g* ends in multiple zero
|
||||
bits, these iterations can be consolidated into one step. This requires counting the bottom zero
|
||||
bits efficiently, which is possible on most platforms; it is abstracted here as the function
|
||||
`count_trailing_zeros`.
|
||||
|
||||
```python
|
||||
def count_trailing_zeros(v):
|
||||
"""
|
||||
When v is zero, consider all N zero bits as "trailing".
|
||||
For a non-zero value v, find z such that v=(d<<z) for some odd d.
|
||||
"""
|
||||
if v == 0:
|
||||
return N
|
||||
else:
|
||||
return (v & -v).bit_length() - 1
|
||||
|
||||
i = N # divsteps left to do
|
||||
while True:
|
||||
# Get rid of all bottom zeros at once. In the first iteration, g may be odd and the following
|
||||
# lines have no effect (until "if eta < 0").
|
||||
zeros = min(i, count_trailing_zeros(g))
|
||||
eta -= zeros
|
||||
g >>= zeros
|
||||
i -= zeros
|
||||
if i == 0:
|
||||
break
|
||||
# We know g is odd now
|
||||
if eta < 0:
|
||||
eta, f, g = -eta, g, -f
|
||||
g += f
|
||||
# g is even now, and the eta decrement and g shift will happen in the next loop.
|
||||
```
|
||||
|
||||
We can now remove multiple bottom *0* bits from *g* at once, but still need a full iteration whenever
|
||||
there is a bottom *1* bit. In what follows, we will get rid of multiple *1* bits simultaneously as
|
||||
well.
|
||||
|
||||
Observe that as long as *η ≥ 0*, the loop does not modify *f*. Instead, it cancels out bottom
|
||||
bits of *g* and shifts them out, and decreases *η* and *i* accordingly - interrupting only when *η*
|
||||
becomes negative, or when *i* reaches *0*. Combined, this is equivalent to adding a multiple of *f* to
|
||||
*g* to cancel out multiple bottom bits, and then shifting them out.
|
||||
|
||||
It is easy to find what that multiple is: we want a number *w* such that *g+w f* has a few bottom
|
||||
zero bits. If that number of bits is *L*, we want *g+w f mod 2<sup>L</sup> = 0*, or *w = -g/f mod 2<sup>L</sup>*. Since *f*
|
||||
is odd, such a *w* exists for any *L*. *L* cannot be more than *i* steps (as we'd finish the loop before
|
||||
doing more) or more than *η+1* steps (as we'd run `eta, f, g = -eta, g, -f` at that point), but
|
||||
apart from that, we're only limited by the complexity of computing *w*.
|
||||
|
||||
This code demonstrates how to cancel up to 4 bits per step:
|
||||
|
||||
```python
|
||||
NEGINV16 = [15, 5, 3, 9, 7, 13, 11, 1] # NEGINV16[n//2] = (-n)^-1 mod 16, for odd n
|
||||
i = N
|
||||
while True:
|
||||
zeros = min(i, count_trailing_zeros(g))
|
||||
eta -= zeros
|
||||
g >>= zeros
|
||||
i -= zeros
|
||||
if i == 0:
|
||||
break
|
||||
# We know g is odd now
|
||||
if eta < 0:
|
||||
eta, f, g = -eta, g, -f
|
||||
# Compute limit on number of bits to cancel
|
||||
limit = min(min(eta + 1, i), 4)
|
||||
# Compute w = -g/f mod 2**limit, using the table value for -1/f mod 2**4. Note that f is
|
||||
# always odd, so its inverse modulo a power of two always exists.
|
||||
w = (g * NEGINV16[(f & 15) // 2]) % (2**limit)
|
||||
# As w = -g/f mod (2**limit), g+w*f mod 2**limit = 0 mod 2**limit.
|
||||
g += w * f
|
||||
assert g % (2**limit) == 0
|
||||
# The next iteration will now shift out at least limit bottom zero bits from g.
|
||||
```
|
||||
|
||||
By using a bigger table more bits can be cancelled at once. The table can also be implemented
|
||||
as a formula. Several formulas are known for computing modular inverses modulo powers of two;
|
||||
some can be found in Hacker's Delight second edition by Henry S. Warren, Jr. pages 245-247.
|
||||
Here we need the negated modular inverse, which is a simple transformation of those:
|
||||
|
||||
- Instead of a 3-bit table:
|
||||
- *-f* or *f ^ 6*
|
||||
- Instead of a 4-bit table:
|
||||
- *1 - f(f + 1)*
|
||||
- *-(f + (((f + 1) & 4) << 1))*
|
||||
- For larger tables the following technique can be used: if *w=-1/f mod 2<sup>L</sup>*, then *w(w f+2)* is
|
||||
*-1/f mod 2<sup>2L</sup>*. This allows extending the previous formulas (or tables). In particular we
|
||||
have this 6-bit function (based on the 3-bit function above):
|
||||
- *f(f<sup>2</sup> - 2)*
|
||||
|
||||
This loop, again extended to also handle *u*, *v*, *q*, and *r* alongside *f* and *g*, placed in
|
||||
`divsteps_n_matrix`, gives a significantly faster, but non-constant time version.
|
||||
|
||||
|
||||
## 7. Final Python version
|
||||
|
||||
All together we need the following functions:
|
||||
|
||||
- A way to compute the transition matrix in constant time, using the `divsteps_n_matrix` function
|
||||
from section 2, but with its loop replaced by a variant of the constant-time divstep from
|
||||
section 5, extended to handle *u*, *v*, *q*, *r*:
|
||||
|
||||
```python
|
||||
def divsteps_n_matrix(zeta, f, g):
|
||||
"""Compute zeta and transition matrix t after N divsteps (multiplied by 2^N)."""
|
||||
u, v, q, r = 1, 0, 0, 1 # start with identity matrix
|
||||
for _ in range(N):
|
||||
c1 = zeta >> 63
|
||||
# Compute x, y, z as conditionally-negated versions of f, u, v.
|
||||
x, y, z = (f ^ c1) - c1, (u ^ c1) - c1, (v ^ c1) - c1
|
||||
c2 = -(g & 1)
|
||||
# Conditionally add x, y, z to g, q, r.
|
||||
g, q, r = g + (x & c2), q + (y & c2), r + (z & c2)
|
||||
c1 &= c2 # reusing c1 here for the earlier c3 variable
|
||||
zeta = (zeta ^ c1) - 1 # inlining the unconditional zeta decrement here
|
||||
# Conditionally add g, q, r to f, u, v.
|
||||
f, u, v = f + (g & c1), u + (q & c1), v + (r & c1)
|
||||
# When shifting g down, don't shift q, r, as we construct a transition matrix multiplied
|
||||
# by 2^N. Instead, shift f's coefficients u and v up.
|
||||
g, u, v = g >> 1, u << 1, v << 1
|
||||
return zeta, (u, v, q, r)
|
||||
```
|
||||
|
||||
- The functions to update *f* and *g*, and *d* and *e*, from section 2 and section 4, with the constant-time
|
||||
changes to `update_de` from section 5:
|
||||
|
||||
```python
|
||||
def update_fg(f, g, t):
|
||||
"""Multiply matrix t/2^N with [f, g]."""
|
||||
u, v, q, r = t
|
||||
cf, cg = u*f + v*g, q*f + r*g
|
||||
return cf >> N, cg >> N
|
||||
|
||||
def update_de(d, e, t, M, Mi):
|
||||
"""Multiply matrix t/2^N with [d, e], modulo M."""
|
||||
u, v, q, r = t
|
||||
d_sign, e_sign = d >> 257, e >> 257
|
||||
md, me = (u & d_sign) + (v & e_sign), (q & d_sign) + (r & e_sign)
|
||||
cd, ce = (u*d + v*e) % 2**N, (q*d + r*e) % 2**N
|
||||
md -= (Mi*cd + md) % 2**N
|
||||
me -= (Mi*ce + me) % 2**N
|
||||
cd, ce = u*d + v*e + M*md, q*d + r*e + M*me
|
||||
return cd >> N, ce >> N
|
||||
```
|
||||
|
||||
- The `normalize` function from section 4, made constant time as well:
|
||||
|
||||
```python
|
||||
def normalize(sign, v, M):
|
||||
"""Compute sign*v mod M, where v in (-2*M,M); output in [0,M)."""
|
||||
v_sign = v >> 257
|
||||
# Conditionally add M to v.
|
||||
v += M & v_sign
|
||||
c = (sign - 1) >> 1
|
||||
# Conditionally negate v.
|
||||
v = (v ^ c) - c
|
||||
v_sign = v >> 257
|
||||
# Conditionally add M to v again.
|
||||
v += M & v_sign
|
||||
return v
|
||||
```
|
||||
|
||||
- And finally the `modinv` function too, adapted to use *ζ* instead of *δ*, and using the fixed
|
||||
iteration count from section 5:
|
||||
|
||||
```python
|
||||
def modinv(M, Mi, x):
|
||||
"""Compute the modular inverse of x mod M, given Mi=1/M mod 2^N."""
|
||||
zeta, f, g, d, e = -1, M, x, 0, 1
|
||||
for _ in range((590 + N - 1) // N):
|
||||
zeta, t = divsteps_n_matrix(zeta, f % 2**N, g % 2**N)
|
||||
f, g = update_fg(f, g, t)
|
||||
d, e = update_de(d, e, t, M, Mi)
|
||||
return normalize(f, d, M)
|
||||
```
|
||||
|
||||
- To get a variable time version, replace the `divsteps_n_matrix` function with one that uses the
|
||||
divsteps loop from section 5, and a `modinv` version that calls it without the fixed iteration
|
||||
count:
|
||||
|
||||
```python
|
||||
NEGINV16 = [15, 5, 3, 9, 7, 13, 11, 1] # NEGINV16[n//2] = (-n)^-1 mod 16, for odd n
|
||||
def divsteps_n_matrix_var(eta, f, g):
|
||||
"""Compute eta and transition matrix t after N divsteps (multiplied by 2^N)."""
|
||||
u, v, q, r = 1, 0, 0, 1
|
||||
i = N
|
||||
while True:
|
||||
zeros = min(i, count_trailing_zeros(g))
|
||||
eta, i = eta - zeros, i - zeros
|
||||
g, u, v = g >> zeros, u << zeros, v << zeros
|
||||
if i == 0:
|
||||
break
|
||||
if eta < 0:
|
||||
eta, f, u, v, g, q, r = -eta, g, q, r, -f, -u, -v
|
||||
limit = min(min(eta + 1, i), 4)
|
||||
w = (g * NEGINV16[(f & 15) // 2]) % (2**limit)
|
||||
g, q, r = g + w*f, q + w*u, r + w*v
|
||||
return eta, (u, v, q, r)
|
||||
|
||||
def modinv_var(M, Mi, x):
|
||||
"""Compute the modular inverse of x mod M, given Mi = 1/M mod 2^N."""
|
||||
eta, f, g, d, e = -1, M, x, 0, 1
|
||||
while g != 0:
|
||||
eta, t = divsteps_n_matrix_var(eta, f % 2**N, g % 2**N)
|
||||
f, g = update_fg(f, g, t)
|
||||
d, e = update_de(d, e, t, M, Mi)
|
||||
return normalize(f, d, Mi)
|
||||
```
|
||||
|
||||
## 8. From GCDs to Jacobi symbol
|
||||
|
||||
We can also use a similar approach to calculate Jacobi symbol *(x | M)* by keeping track of an
|
||||
extra variable *j*, for which at every step *(x | M) = j (g | f)*. As we update *f* and *g*, we
|
||||
make corresponding updates to *j* using
|
||||
[properties of the Jacobi symbol](https://en.wikipedia.org/wiki/Jacobi_symbol#Properties):
|
||||
* *((g/2) | f)* is either *(g | f)* or *-(g | f)*, depending on the value of *f mod 8* (negating if it's *3* or *5*).
|
||||
* *(f | g)* is either *(g | f)* or *-(g | f)*, depending on *f mod 4* and *g mod 4* (negating if both are *3*).
|
||||
|
||||
These updates depend only on the values of *f* and *g* modulo *4* or *8*, and can thus be applied
|
||||
very quickly, as long as we keep track of a few additional bits of *f* and *g*. Overall, this
|
||||
calculation is slightly simpler than the one for the modular inverse because we no longer need to
|
||||
keep track of *d* and *e*.
|
||||
|
||||
However, one difficulty of this approach is that the Jacobi symbol *(a | n)* is only defined for
|
||||
positive odd integers *n*, whereas in the original safegcd algorithm, *f, g* can take negative
|
||||
values. We resolve this by using the following modified steps:
|
||||
|
||||
```python
|
||||
# Before
|
||||
if delta > 0 and g & 1:
|
||||
delta, f, g = 1 - delta, g, (g - f) // 2
|
||||
|
||||
# After
|
||||
if delta > 0 and g & 1:
|
||||
delta, f, g = 1 - delta, g, (g + f) // 2
|
||||
```
|
||||
|
||||
The algorithm is still correct, since the changed divstep, called a "posdivstep" (see section 8.4
|
||||
and E.5 in the paper) preserves *gcd(f, g)*. However, there's no proof that the modified algorithm
|
||||
will converge. The justification for posdivsteps is completely empirical: in practice, it appears
|
||||
that the vast majority of nonzero inputs converge to *f=g=gcd(f<sub>0</sub>, g<sub>0</sub>)* in a
|
||||
number of steps proportional to their logarithm.
|
||||
|
||||
Note that:
|
||||
- We require inputs to satisfy *gcd(x, M) = 1*, as otherwise *f=1* is not reached.
|
||||
- We require inputs *x &neq; 0*, because applying posdivstep with *g=0* has no effect.
|
||||
- We need to update the termination condition from *g=0* to *f=1*.
|
||||
|
||||
We account for the possibility of nonconvergence by only performing a bounded number of
|
||||
posdivsteps, and then falling back to square-root based Jacobi calculation if a solution has not
|
||||
yet been found.
|
||||
|
||||
The optimizations in sections 3-7 above are described in the context of the original divsteps, but
|
||||
in the C implementation we also adapt most of them (not including "avoiding modulus operations",
|
||||
since it's not necessary to track *d, e*, and "constant-time operation", since we never calculate
|
||||
Jacobi symbols for secret data) to the posdivsteps version.
|
||||
@@ -1,49 +0,0 @@
|
||||
function(add_example name)
|
||||
set(target_name ${name}_example)
|
||||
add_executable(${target_name} ${name}.c)
|
||||
target_include_directories(${target_name} PRIVATE
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries(${target_name}
|
||||
secp256k1
|
||||
$<$<PLATFORM_ID:Windows>:bcrypt>
|
||||
)
|
||||
add_test(NAME secp256k1.example.${name} COMMAND ${target_name})
|
||||
set_tests_properties(secp256k1.example.${name} PROPERTIES
|
||||
LABELS secp256k1_example
|
||||
)
|
||||
endfunction()
|
||||
|
||||
add_example(ecdsa)
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ECDH)
|
||||
add_example(ecdh)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_SCHNORRSIG)
|
||||
add_example(schnorr)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ELLSWIFT)
|
||||
add_example(ellswift)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_MUSIG)
|
||||
add_example(musig)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_FROST)
|
||||
add_example(frost)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_CHILLDKG)
|
||||
add_example(chilldkg)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ICEBERG)
|
||||
add_example(iceberg)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT)
|
||||
add_example(frost_enrollment)
|
||||
endif()
|
||||
@@ -1,121 +0,0 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
@@ -1,212 +0,0 @@
|
||||
/*************************************************************************
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
/** This file demonstrates how to use the ChillDKG module to run a 2-of-3
|
||||
* distributed key generation (DKG) session for FROST (BIP 445) threshold
|
||||
* signatures. Additionally, see the documentation in
|
||||
* include/secp256k1_chilldkg.h and src/modules/chilldkg/chilldkg.md.
|
||||
*
|
||||
* The example runs all roles (participants and coordinator) in a single
|
||||
* process. In a real deployment these roles are performed by different
|
||||
* parties communicating over secure/authenticated channels, and the
|
||||
* coordinator is untrusted.
|
||||
*
|
||||
* The resulting key material (secret share, threshold public key and public
|
||||
* shares) can be used with the FROST signing module (see examples/frost.c).
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_chilldkg.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
/* Total number of participants n */
|
||||
#define N_PARTICIPANTS 3
|
||||
/* Threshold t: the minimum number of signers required to produce a
|
||||
* signature */
|
||||
#define THRESHOLD 2
|
||||
|
||||
struct participant {
|
||||
/* Long-term secret key; the participant's identity. All session outputs
|
||||
* can be recovered from the hostseckey and the recovery data. */
|
||||
unsigned char hostseckey[32];
|
||||
unsigned char hostpubkey[33];
|
||||
secp256k1_chilldkg_participant_state1 state1;
|
||||
secp256k1_chilldkg_participant_state2 state2;
|
||||
/* Buffers for the messages sent to the coordinator */
|
||||
unsigned char pmsg1[33 * THRESHOLD + 32 * N_PARTICIPANTS + 97];
|
||||
unsigned char pmsg2[64];
|
||||
/* Outputs */
|
||||
unsigned char secshare[32];
|
||||
unsigned char recovery[4 + 33 * THRESHOLD + 162 * N_PARTICIPANTS];
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
secp256k1_context *ctx;
|
||||
struct participant participants[N_PARTICIPANTS];
|
||||
secp256k1_chilldkg_coordinator_state coord_state;
|
||||
unsigned char hostpubkeys[N_PARTICIPANTS * 33];
|
||||
unsigned char params_hash[32];
|
||||
unsigned char cmsg1[162 * N_PARTICIPANTS + 33 * (THRESHOLD - 1)];
|
||||
unsigned char cmsg2[64 * N_PARTICIPANTS];
|
||||
const unsigned char *pmsg1_ptrs[N_PARTICIPANTS];
|
||||
const unsigned char *pmsg2_ptrs[N_PARTICIPANTS];
|
||||
unsigned char thresh_pk[33];
|
||||
unsigned char pubshares[N_PARTICIPANTS * 33];
|
||||
unsigned char coord_recovery[sizeof(participants[0].recovery)];
|
||||
uint32_t fault_index;
|
||||
secp256k1_chilldkg_fault fault;
|
||||
int i;
|
||||
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
|
||||
/* Every participant generates a long-term host key pair. The hostseckey
|
||||
* must be generated with a cryptographically secure random number
|
||||
* generator and stored securely. */
|
||||
printf("Generating host keys...\n");
|
||||
for (i = 0; i < N_PARTICIPANTS; i++) {
|
||||
if (!fill_random(participants[i].hostseckey, 32)) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!secp256k1_chilldkg_hostpubkey_gen(ctx, participants[i].hostpubkey, participants[i].hostseckey)) {
|
||||
printf("Failed to generate host public key\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
memcpy(&hostpubkeys[33 * i], participants[i].hostpubkey, 33);
|
||||
}
|
||||
|
||||
/* The participants exchange their host public keys (over authenticated
|
||||
* channels) and compare the parameters hash out of band to ensure they
|
||||
* all agree on the session parameters. */
|
||||
if (!secp256k1_chilldkg_params_hash(ctx, params_hash, hostpubkeys, N_PARTICIPANTS, THRESHOLD)) {
|
||||
printf("Invalid session parameters\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("Session parameters hash: ");
|
||||
print_hex(params_hash, 32);
|
||||
|
||||
/* Step 1: every participant creates its first message and sends it to the
|
||||
* coordinator. The randomness must be FRESH for every session. */
|
||||
printf("Running participant step 1...\n");
|
||||
for (i = 0; i < N_PARTICIPANTS; i++) {
|
||||
unsigned char random32[32];
|
||||
if (!fill_random(random32, 32)) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!secp256k1_chilldkg_participant_step1(ctx, &participants[i].state1, participants[i].pmsg1, participants[i].hostseckey, hostpubkeys, N_PARTICIPANTS, THRESHOLD, random32)) {
|
||||
printf("participant_step1 failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
secure_erase(random32, sizeof(random32));
|
||||
pmsg1_ptrs[i] = participants[i].pmsg1;
|
||||
}
|
||||
|
||||
/* Step 1 (coordinator): the coordinator aggregates the first messages and
|
||||
* broadcasts cmsg1 to all participants. */
|
||||
printf("Running coordinator step 1...\n");
|
||||
fault = secp256k1_chilldkg_coordinator_step1(ctx, &coord_state, cmsg1, &fault_index, pmsg1_ptrs, hostpubkeys, N_PARTICIPANTS, THRESHOLD);
|
||||
if (fault != SECP256K1_CHILLDKG_OK) {
|
||||
printf("coordinator_step1 failed (fault %d, index %u)\n", fault, fault_index);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Step 2: every participant verifies cmsg1, computes its DKG output, and
|
||||
* sends a CertEq signature over the session transcript to the
|
||||
* coordinator. */
|
||||
printf("Running participant step 2...\n");
|
||||
for (i = 0; i < N_PARTICIPANTS; i++) {
|
||||
unsigned char aux_rand32[32];
|
||||
if (!fill_random(aux_rand32, 32)) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
fault = secp256k1_chilldkg_participant_step2(ctx, &participants[i].state2, participants[i].pmsg2, &fault_index, NULL, &participants[i].state1, participants[i].hostseckey, cmsg1, aux_rand32);
|
||||
secure_erase(aux_rand32, sizeof(aux_rand32));
|
||||
if (fault != SECP256K1_CHILLDKG_OK) {
|
||||
printf("participant_step2 failed for participant %d (fault %d, index %u)\n", i, fault, fault_index);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
pmsg2_ptrs[i] = participants[i].pmsg2;
|
||||
}
|
||||
|
||||
/* Step 2 (coordinator): the coordinator collects the CertEq signatures
|
||||
* into the certificate and broadcasts it to all participants. */
|
||||
printf("Running coordinator finalize...\n");
|
||||
fault = secp256k1_chilldkg_coordinator_finalize(ctx, cmsg2, thresh_pk, pubshares, coord_recovery, &fault_index, &coord_state, pmsg2_ptrs);
|
||||
if (fault != SECP256K1_CHILLDKG_OK) {
|
||||
printf("coordinator_finalize failed (fault %d, index %u)\n", fault, fault_index);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Finalize: every participant verifies the certificate and outputs the
|
||||
* DKG result and the recovery data. */
|
||||
printf("Running participant finalize...\n");
|
||||
for (i = 0; i < N_PARTICIPANTS; i++) {
|
||||
fault = secp256k1_chilldkg_participant_finalize(ctx, participants[i].secshare, thresh_pk, pubshares, participants[i].recovery, &fault_index, &participants[i].state2, cmsg2);
|
||||
if (fault != SECP256K1_CHILLDKG_OK) {
|
||||
printf("participant_finalize failed for participant %d (fault %d, index %u)\n", i, fault, fault_index);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* All participants (and the coordinator) hold identical recovery
|
||||
* data. Keep it safe: anyone with the recovery data and their
|
||||
* hostseckey can recover the DKG output, e.g. after data loss. */
|
||||
if (memcmp(participants[i].recovery, coord_recovery, sizeof(coord_recovery)) != 0) {
|
||||
printf("recovery data mismatch\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
printf("Threshold public key: ");
|
||||
print_hex(thresh_pk, 33);
|
||||
for (i = 0; i < N_PARTICIPANTS; i++) {
|
||||
printf("Public share of participant %d: ", i);
|
||||
print_hex(&pubshares[33 * i], 33);
|
||||
}
|
||||
|
||||
/* Recovery: a participant can recover its DKG output from a backup of its
|
||||
* hostseckey and the recovery data at any time. */
|
||||
printf("Recovering participant 0 from recovery data...\n");
|
||||
{
|
||||
unsigned char rec_secshare[32];
|
||||
unsigned char rec_thresh_pk[33];
|
||||
unsigned char rec_pubshares[N_PARTICIPANTS * 33];
|
||||
unsigned char rec_hostpubkeys[N_PARTICIPANTS * 33];
|
||||
size_t n_rec;
|
||||
uint32_t t_rec;
|
||||
fault = secp256k1_chilldkg_participant_recover(ctx, rec_secshare, rec_thresh_pk, rec_pubshares, rec_hostpubkeys, &n_rec, &t_rec, &fault_index, participants[0].hostseckey, participants[0].recovery, sizeof(participants[0].recovery));
|
||||
if (fault != SECP256K1_CHILLDKG_OK) {
|
||||
printf("participant_recover failed (fault %d, index %u)\n", fault, fault_index);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (n_rec != N_PARTICIPANTS || t_rec != THRESHOLD
|
||||
|| memcmp(rec_secshare, participants[0].secshare, 32) != 0
|
||||
|| memcmp(rec_thresh_pk, thresh_pk, 33) != 0) {
|
||||
printf("recovered output mismatch\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
secure_erase(rec_secshare, sizeof(rec_secshare));
|
||||
}
|
||||
|
||||
printf("DKG session completed successfully.\n");
|
||||
|
||||
/* Clear secrets from memory (see examples/frost.c for why). The
|
||||
* participants' state2 objects have been consumed by
|
||||
* participant_finalize. */
|
||||
for (i = 0; i < N_PARTICIPANTS; i++) {
|
||||
secure_erase(participants[i].hostseckey, 32);
|
||||
secure_erase(participants[i].secshare, 32);
|
||||
}
|
||||
secp256k1_context_destroy(ctx);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
121
examples/ecdh.c
121
examples/ecdh.c
@@ -1,121 +0,0 @@
|
||||
/*************************************************************************
|
||||
* Written in 2020-2022 by Elichai Turkel *
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_ecdh.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
int main(void) {
|
||||
unsigned char seckey1[32];
|
||||
unsigned char seckey2[32];
|
||||
unsigned char compressed_pubkey1[33];
|
||||
unsigned char compressed_pubkey2[33];
|
||||
unsigned char shared_secret1[32];
|
||||
unsigned char shared_secret2[32];
|
||||
unsigned char randomize[32];
|
||||
int return_val;
|
||||
size_t len;
|
||||
secp256k1_pubkey pubkey1;
|
||||
secp256k1_pubkey pubkey2;
|
||||
|
||||
/* Before we can call actual API functions, we need to create a "context". */
|
||||
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
if (!fill_random(randomize, sizeof(randomize))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* Randomizing the context is recommended to protect against side-channel
|
||||
* leakage See `secp256k1_context_randomize` in secp256k1.h for more
|
||||
* information about it. This should never fail. */
|
||||
return_val = secp256k1_context_randomize(ctx, randomize);
|
||||
assert(return_val);
|
||||
|
||||
/*** Key Generation ***/
|
||||
if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* If the secret key is zero or out of range (greater than secp256k1's
|
||||
* order), we fail. Note that the probability of this occurring is negligible
|
||||
* with a properly functioning random number generator. */
|
||||
if (!secp256k1_ec_seckey_verify(ctx, seckey1) || !secp256k1_ec_seckey_verify(ctx, seckey2)) {
|
||||
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Public key creation using a valid context with a verified secret key should never fail */
|
||||
return_val = secp256k1_ec_pubkey_create(ctx, &pubkey1, seckey1);
|
||||
assert(return_val);
|
||||
return_val = secp256k1_ec_pubkey_create(ctx, &pubkey2, seckey2);
|
||||
assert(return_val);
|
||||
|
||||
/* Serialize pubkey1 in a compressed form (33 bytes), should always return 1 */
|
||||
len = sizeof(compressed_pubkey1);
|
||||
return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey1, &len, &pubkey1, SECP256K1_EC_COMPRESSED);
|
||||
assert(return_val);
|
||||
/* Should be the same size as the size of the output, because we passed a 33 byte array. */
|
||||
assert(len == sizeof(compressed_pubkey1));
|
||||
|
||||
/* Serialize pubkey2 in a compressed form (33 bytes) */
|
||||
len = sizeof(compressed_pubkey2);
|
||||
return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey2, &len, &pubkey2, SECP256K1_EC_COMPRESSED);
|
||||
assert(return_val);
|
||||
/* Should be the same size as the size of the output, because we passed a 33 byte array. */
|
||||
assert(len == sizeof(compressed_pubkey2));
|
||||
|
||||
/*** Creating the shared secret ***/
|
||||
|
||||
/* Perform ECDH with seckey1 and pubkey2. Should never fail with a verified
|
||||
* seckey and valid pubkey */
|
||||
return_val = secp256k1_ecdh(ctx, shared_secret1, &pubkey2, seckey1, NULL, NULL);
|
||||
assert(return_val);
|
||||
|
||||
/* Perform ECDH with seckey2 and pubkey1. Should never fail with a verified
|
||||
* seckey and valid pubkey */
|
||||
return_val = secp256k1_ecdh(ctx, shared_secret2, &pubkey1, seckey2, NULL, NULL);
|
||||
assert(return_val);
|
||||
|
||||
/* Both parties should end up with the same shared secret */
|
||||
return_val = memcmp(shared_secret1, shared_secret2, sizeof(shared_secret1));
|
||||
assert(return_val == 0);
|
||||
|
||||
printf("Secret Key1: ");
|
||||
print_hex(seckey1, sizeof(seckey1));
|
||||
printf("Compressed Pubkey1: ");
|
||||
print_hex(compressed_pubkey1, sizeof(compressed_pubkey1));
|
||||
printf("\nSecret Key2: ");
|
||||
print_hex(seckey2, sizeof(seckey2));
|
||||
printf("Compressed Pubkey2: ");
|
||||
print_hex(compressed_pubkey2, sizeof(compressed_pubkey2));
|
||||
printf("\nShared Secret: ");
|
||||
print_hex(shared_secret1, sizeof(shared_secret1));
|
||||
|
||||
/* This will clear everything from the context and free the memory */
|
||||
secp256k1_context_destroy(ctx);
|
||||
|
||||
/* It's best practice to try to clear secrets from memory after using them.
|
||||
* This is done because some bugs can allow an attacker to leak memory, for
|
||||
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||||
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
|
||||
*
|
||||
* Here we are preventing these writes from being optimized out, as any good compiler
|
||||
* will remove any writes that aren't used. */
|
||||
secure_erase(seckey1, sizeof(seckey1));
|
||||
secure_erase(seckey2, sizeof(seckey2));
|
||||
secure_erase(shared_secret1, sizeof(shared_secret1));
|
||||
secure_erase(shared_secret2, sizeof(shared_secret2));
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
138
examples/ecdsa.c
138
examples/ecdsa.c
@@ -1,138 +0,0 @@
|
||||
/*************************************************************************
|
||||
* Written in 2020-2022 by Elichai Turkel *
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
int main(void) {
|
||||
/* Instead of signing the message directly, we must sign a 32-byte hash.
|
||||
* Here the message is "Hello, world!" and the hash function was SHA-256.
|
||||
* An actual implementation should just call SHA-256, but this example
|
||||
* hardcodes the output to avoid depending on an additional library.
|
||||
* See https://bitcoin.stackexchange.com/questions/81115/if-someone-wanted-to-pretend-to-be-satoshi-by-posting-a-fake-signature-to-defrau/81116#81116 */
|
||||
unsigned char msg_hash[32] = {
|
||||
0x31, 0x5F, 0x5B, 0xDB, 0x76, 0xD0, 0x78, 0xC4,
|
||||
0x3B, 0x8A, 0xC0, 0x06, 0x4E, 0x4A, 0x01, 0x64,
|
||||
0x61, 0x2B, 0x1F, 0xCE, 0x77, 0xC8, 0x69, 0x34,
|
||||
0x5B, 0xFC, 0x94, 0xC7, 0x58, 0x94, 0xED, 0xD3,
|
||||
};
|
||||
unsigned char seckey[32];
|
||||
unsigned char randomize[32];
|
||||
unsigned char compressed_pubkey[33];
|
||||
unsigned char serialized_signature[64];
|
||||
size_t len;
|
||||
int is_signature_valid, is_signature_valid2;
|
||||
int return_val;
|
||||
secp256k1_pubkey pubkey;
|
||||
secp256k1_ecdsa_signature sig;
|
||||
/* Before we can call actual API functions, we need to create a "context". */
|
||||
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
if (!fill_random(randomize, sizeof(randomize))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* Randomizing the context is recommended to protect against side-channel
|
||||
* leakage See `secp256k1_context_randomize` in secp256k1.h for more
|
||||
* information about it. This should never fail. */
|
||||
return_val = secp256k1_context_randomize(ctx, randomize);
|
||||
assert(return_val);
|
||||
|
||||
/*** Key Generation ***/
|
||||
if (!fill_random(seckey, sizeof(seckey))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* If the secret key is zero or out of range (greater than secp256k1's
|
||||
* order), we fail. Note that the probability of this occurring is negligible
|
||||
* with a properly functioning random number generator. */
|
||||
if (!secp256k1_ec_seckey_verify(ctx, seckey)) {
|
||||
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Public key creation using a valid context with a verified secret key should never fail */
|
||||
return_val = secp256k1_ec_pubkey_create(ctx, &pubkey, seckey);
|
||||
assert(return_val);
|
||||
|
||||
/* Serialize the pubkey in a compressed form(33 bytes). Should always return 1. */
|
||||
len = sizeof(compressed_pubkey);
|
||||
return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey, &len, &pubkey, SECP256K1_EC_COMPRESSED);
|
||||
assert(return_val);
|
||||
/* Should be the same size as the size of the output, because we passed a 33 byte array. */
|
||||
assert(len == sizeof(compressed_pubkey));
|
||||
|
||||
/*** Signing ***/
|
||||
|
||||
/* Generate an ECDSA signature `noncefp` and `ndata` allows you to pass a
|
||||
* custom nonce function, passing `NULL` will use the RFC-6979 safe default.
|
||||
* Signing with a valid context, verified secret key
|
||||
* and the default nonce function should never fail. */
|
||||
return_val = secp256k1_ecdsa_sign(ctx, &sig, msg_hash, seckey, NULL, NULL);
|
||||
assert(return_val);
|
||||
|
||||
/* Serialize the signature in a compact form. Should always return 1
|
||||
* according to the documentation in secp256k1.h. */
|
||||
return_val = secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_signature, &sig);
|
||||
assert(return_val);
|
||||
|
||||
|
||||
/*** Verification ***/
|
||||
|
||||
/* Deserialize the signature. This will return 0 if the signature can't be parsed correctly. */
|
||||
if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, serialized_signature)) {
|
||||
printf("Failed parsing the signature\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Deserialize the public key. This will return 0 if the public key can't be parsed correctly. */
|
||||
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey))) {
|
||||
printf("Failed parsing the public key\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Verify a signature. This will return 1 if it's valid and 0 if it's not. */
|
||||
is_signature_valid = secp256k1_ecdsa_verify(ctx, &sig, msg_hash, &pubkey);
|
||||
|
||||
printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false");
|
||||
printf("Secret Key: ");
|
||||
print_hex(seckey, sizeof(seckey));
|
||||
printf("Public Key: ");
|
||||
print_hex(compressed_pubkey, sizeof(compressed_pubkey));
|
||||
printf("Signature: ");
|
||||
print_hex(serialized_signature, sizeof(serialized_signature));
|
||||
|
||||
/* This will clear everything from the context and free the memory */
|
||||
secp256k1_context_destroy(ctx);
|
||||
|
||||
/* Bonus example: if all we need is signature verification (and no key
|
||||
generation or signing), we don't need to use a context created via
|
||||
secp256k1_context_create(). We can simply use the static (i.e., global)
|
||||
context secp256k1_context_static. See its description in
|
||||
include/secp256k1.h for details. */
|
||||
is_signature_valid2 = secp256k1_ecdsa_verify(secp256k1_context_static,
|
||||
&sig, msg_hash, &pubkey);
|
||||
assert(is_signature_valid2 == is_signature_valid);
|
||||
|
||||
/* It's best practice to try to clear secrets from memory after using them.
|
||||
* This is done because some bugs can allow an attacker to leak memory, for
|
||||
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||||
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
|
||||
*
|
||||
* Here we are preventing these writes from being optimized out, as any good compiler
|
||||
* will remove any writes that aren't used. */
|
||||
secure_erase(seckey, sizeof(seckey));
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*************************************************************************
|
||||
* Written in 2024 by Sebastian Falbesoner *
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
/** This file demonstrates how to use the ElligatorSwift module to perform
|
||||
* a key exchange according to BIP 324. Additionally, see the documentation
|
||||
* in include/secp256k1_ellswift.h and doc/ellswift.md.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_ellswift.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
int main(void) {
|
||||
secp256k1_context* ctx;
|
||||
unsigned char randomize[32];
|
||||
unsigned char auxrand1[32];
|
||||
unsigned char auxrand2[32];
|
||||
unsigned char seckey1[32];
|
||||
unsigned char seckey2[32];
|
||||
unsigned char ellswift_pubkey1[64];
|
||||
unsigned char ellswift_pubkey2[64];
|
||||
unsigned char shared_secret1[32];
|
||||
unsigned char shared_secret2[32];
|
||||
int return_val;
|
||||
|
||||
/* Create a secp256k1 context */
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
if (!fill_random(randomize, sizeof(randomize))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* Randomizing the context is recommended to protect against side-channel
|
||||
* leakage. See `secp256k1_context_randomize` in secp256k1.h for more
|
||||
* information about it. This should never fail. */
|
||||
return_val = secp256k1_context_randomize(ctx, randomize);
|
||||
assert(return_val);
|
||||
|
||||
/*** Generate secret keys ***/
|
||||
if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* If the secret key is zero or out of range (greater than secp256k1's
|
||||
* order), we fail. Note that the probability of this occurring is negligible
|
||||
* with a properly functioning random number generator. */
|
||||
if (!secp256k1_ec_seckey_verify(ctx, seckey1) || !secp256k1_ec_seckey_verify(ctx, seckey2)) {
|
||||
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Generate ElligatorSwift public keys. This should never fail with valid context and
|
||||
verified secret keys. Note that providing additional randomness (fourth parameter) is
|
||||
optional, but recommended. */
|
||||
if (!fill_random(auxrand1, sizeof(auxrand1)) || !fill_random(auxrand2, sizeof(auxrand2))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return_val = secp256k1_ellswift_create(ctx, ellswift_pubkey1, seckey1, auxrand1);
|
||||
assert(return_val);
|
||||
return_val = secp256k1_ellswift_create(ctx, ellswift_pubkey2, seckey2, auxrand2);
|
||||
assert(return_val);
|
||||
|
||||
/*** Create the shared secret on each side ***/
|
||||
|
||||
/* Perform x-only ECDH with seckey1 and ellswift_pubkey2. Should never fail
|
||||
* with a verified seckey and valid pubkey. Note that both parties pass both
|
||||
* EllSwift pubkeys in the same order; the pubkey of the calling party is
|
||||
* determined by the "party" boolean (sixth parameter). */
|
||||
return_val = secp256k1_ellswift_xdh(ctx, shared_secret1, ellswift_pubkey1, ellswift_pubkey2,
|
||||
seckey1, 0, secp256k1_ellswift_xdh_hash_function_bip324, NULL);
|
||||
assert(return_val);
|
||||
|
||||
/* Perform x-only ECDH with seckey2 and ellswift_pubkey1. Should never fail
|
||||
* with a verified seckey and valid pubkey. */
|
||||
return_val = secp256k1_ellswift_xdh(ctx, shared_secret2, ellswift_pubkey1, ellswift_pubkey2,
|
||||
seckey2, 1, secp256k1_ellswift_xdh_hash_function_bip324, NULL);
|
||||
assert(return_val);
|
||||
|
||||
/* Both parties should end up with the same shared secret */
|
||||
return_val = memcmp(shared_secret1, shared_secret2, sizeof(shared_secret1));
|
||||
assert(return_val == 0);
|
||||
|
||||
printf( " Secret Key1: ");
|
||||
print_hex(seckey1, sizeof(seckey1));
|
||||
printf( "EllSwift Pubkey1: ");
|
||||
print_hex(ellswift_pubkey1, sizeof(ellswift_pubkey1));
|
||||
printf("\n Secret Key2: ");
|
||||
print_hex(seckey2, sizeof(seckey2));
|
||||
printf( "EllSwift Pubkey2: ");
|
||||
print_hex(ellswift_pubkey2, sizeof(ellswift_pubkey2));
|
||||
printf("\n Shared Secret: ");
|
||||
print_hex(shared_secret1, sizeof(shared_secret1));
|
||||
|
||||
/* This will clear everything from the context and free the memory */
|
||||
secp256k1_context_destroy(ctx);
|
||||
|
||||
/* It's best practice to try to clear secrets from memory after using them.
|
||||
* This is done because some bugs can allow an attacker to leak memory, for
|
||||
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||||
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
|
||||
*
|
||||
* Here we are preventing these writes from being optimized out, as any good compiler
|
||||
* will remove any writes that aren't used. */
|
||||
secure_erase(seckey1, sizeof(seckey1));
|
||||
secure_erase(seckey2, sizeof(seckey2));
|
||||
secure_erase(shared_secret1, sizeof(shared_secret1));
|
||||
secure_erase(shared_secret2, sizeof(shared_secret2));
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*************************************************************************
|
||||
* Copyright (c) 2020-2021 Elichai Turkel *
|
||||
* Distributed under the CC0 software license, see the accompanying file *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
/*
|
||||
* This file is an attempt at collecting best practice methods for obtaining randomness with different operating systems.
|
||||
* It may be out-of-date. Consult the documentation of the operating system before considering to use the methods below.
|
||||
*
|
||||
* Platform randomness sources:
|
||||
* Linux -> `getrandom(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. http://man7.org/linux/man-pages/man2/getrandom.2.html, https://linux.die.net/man/4/urandom
|
||||
* macOS -> `getentropy(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. https://www.unix.com/man-page/mojave/2/getentropy, https://opensource.apple.com/source/xnu/xnu-517.12.7/bsd/man/man4/random.4.auto.html
|
||||
* FreeBSD -> `getrandom(2)`(`sys/random.h`), if not available `kern.arandom` should be used. https://www.freebsd.org/cgi/man.cgi?query=getrandom, https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4
|
||||
* OpenBSD -> `getentropy(2)`(`unistd.h`), if not available `/dev/urandom` should be used. https://man.openbsd.org/getentropy, https://man.openbsd.org/urandom
|
||||
* Windows -> `BCryptGenRandom`(`bcrypt.h`). https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
|
||||
*/
|
||||
|
||||
#if defined(_WIN32)
|
||||
/*
|
||||
* The defined WIN32_NO_STATUS macro disables return code definitions in
|
||||
* windows.h, which avoids "macro redefinition" MSVC warnings in ntstatus.h.
|
||||
*/
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#undef WIN32_NO_STATUS
|
||||
#include <ntstatus.h>
|
||||
#include <bcrypt.h>
|
||||
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <sys/random.h>
|
||||
#elif defined(__OpenBSD__)
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#error "Couldn't identify the OS"
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
/* Returns 1 on success, and 0 on failure. */
|
||||
static int fill_random(unsigned char* data, size_t size) {
|
||||
#if defined(_WIN32)
|
||||
NTSTATUS res = BCryptGenRandom(NULL, data, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
if (res != STATUS_SUCCESS || size > ULONG_MAX) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
/* If `getrandom(2)` is not available you should fallback to /dev/urandom */
|
||||
ssize_t res = getrandom(data, size, 0);
|
||||
if (res < 0 || (size_t)res != size ) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
#elif defined(__APPLE__) || defined(__OpenBSD__)
|
||||
/* If `getentropy(2)` is not available you should fallback to either
|
||||
* `SecRandomCopyBytes` or /dev/urandom */
|
||||
int res = getentropy(data, size);
|
||||
if (res == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_hex(unsigned char* data, size_t size) {
|
||||
size_t i;
|
||||
printf("0x");
|
||||
for (i = 0; i < size; i++) {
|
||||
printf("%02x", data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
// For SecureZeroMemory
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
/* Cleanses memory to prevent leaking sensitive info. Won't be optimized out. */
|
||||
static void secure_erase(void *ptr, size_t len) {
|
||||
#if defined(_MSC_VER)
|
||||
/* SecureZeroMemory is guaranteed not to be optimized out by MSVC. */
|
||||
SecureZeroMemory(ptr, len);
|
||||
#elif defined(__GNUC__)
|
||||
/* We use a memory barrier that scares the compiler away from optimizing out the memset.
|
||||
*
|
||||
* Quoting Adam Langley <agl@google.com> in commit ad1907fe73334d6c696c8539646c21b11178f20f
|
||||
* in BoringSSL (ISC License):
|
||||
* As best as we can tell, this is sufficient to break any optimisations that
|
||||
* might try to eliminate "superfluous" memsets.
|
||||
* This method used in memzero_explicit() the Linux kernel, too. Its advantage is that it is
|
||||
* pretty efficient, because the compiler can still implement the memset() efficiently,
|
||||
* just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by
|
||||
* Yang et al. (USENIX Security 2017) for more background.
|
||||
*/
|
||||
memset(ptr, 0, len);
|
||||
__asm__ __volatile__("" : : "r"(ptr) : "memory");
|
||||
#else
|
||||
void *(*volatile const volatile_memset)(void *, int, size_t) = memset;
|
||||
volatile_memset(ptr, 0, len);
|
||||
#endif
|
||||
}
|
||||
281
examples/frost.c
281
examples/frost.c
@@ -1,281 +0,0 @@
|
||||
/*************************************************************************
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
/** This file demonstrates how to use the FROST module (BIP 445) to create a
|
||||
* 2-of-3 threshold signature. Additionally, see the documentation in
|
||||
* include/secp256k1_frost.h and src/modules/frost/frost.md.
|
||||
*
|
||||
* The example runs all roles (trusted dealer, signers, coordinator) in a
|
||||
* single process. In a real deployment these roles are performed by
|
||||
* different parties communicating over secure/authenticated channels.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_extrakeys.h>
|
||||
#include <secp256k1_frost.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
/* Total number of participants n */
|
||||
#define N_PARTICIPANTS 3
|
||||
/* Threshold t: the minimum number of signers required to produce a
|
||||
* signature */
|
||||
#define THRESHOLD 2
|
||||
/* The signers of this signing session (u = 2, participants 0 and 2) */
|
||||
#define N_SIGNERS 2
|
||||
static const uint32_t SIGNER_IDS[N_SIGNERS] = { 0, 2 };
|
||||
|
||||
struct signer_secrets {
|
||||
/* The signer's secret share, received from the dealer over a secure
|
||||
* channel */
|
||||
unsigned char secshare[32];
|
||||
/* Secret nonce. It is wiped by secp256k1_frost_sign; never reuse it. */
|
||||
secp256k1_frost_secnonce secnonce;
|
||||
};
|
||||
|
||||
struct signer {
|
||||
uint32_t id;
|
||||
secp256k1_pubkey pubshare;
|
||||
secp256k1_frost_pubnonce pubnonce;
|
||||
secp256k1_frost_partial_sig partial_sig;
|
||||
};
|
||||
|
||||
/* Run the trusted dealer key generation: from a random threshold secret key,
|
||||
* derive the secret share of every participant, the threshold public key and
|
||||
* the public shares.
|
||||
*
|
||||
* WARNING: The trusted dealer knows the threshold secret key and all secret
|
||||
* shares, and must erase them securely after distributing the shares. A
|
||||
* distributed key generation (DKG) protocol avoids a trusted dealer entirely
|
||||
* but is out of scope for BIP 445 and for this example. */
|
||||
static int trusted_dealer_keygen(const secp256k1_context* ctx, unsigned char *threshold_seckey, unsigned char *secshares, secp256k1_pubkey *thresh_pk, secp256k1_pubkey *pubshares) {
|
||||
if (!fill_random(threshold_seckey, 32)) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_frost_trusted_dealer_keygen(ctx, secshares, thresh_pk, pubshares, N_PARTICIPANTS, THRESHOLD, threshold_seckey)) {
|
||||
return 0;
|
||||
}
|
||||
/* Everyone can check that the public shares and the threshold public key
|
||||
* are consistent. Note that this does NOT validate the security of the
|
||||
* key generation that produced them. */
|
||||
if (!secp256k1_frost_threshold_info_validate(ctx, thresh_pk, pubshares, N_PARTICIPANTS, THRESHOLD)) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Apply an x-only tweak to the threshold public key, e.g. a BIP 341
|
||||
* ("Taproot") tweak committing to a script tree. All participants (and the
|
||||
* coordinator) must apply exactly the same tweaks in the same order to their
|
||||
* own tweak cache. */
|
||||
static int tweak(const secp256k1_context* ctx, secp256k1_frost_tweak_cache *cache, secp256k1_xonly_pubkey *tweaked_pk) {
|
||||
/* For Taproot tweaking the tweak is set to the TapTweak hash as defined
|
||||
* in BIP 341. */
|
||||
unsigned char xonly_tweak[32] = "this could be a Taproot tweak..";
|
||||
|
||||
if (!secp256k1_frost_pubkey_xonly_tweak_add(ctx, tweaked_pk, cache, xonly_tweak)) {
|
||||
return 0;
|
||||
}
|
||||
/* For BIP 32-style plain tweaking, use
|
||||
* secp256k1_frost_pubkey_ec_tweak_add instead. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Sign a message with the given signers and store the resulting BIP340
|
||||
* signature in sig64. */
|
||||
static int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signers, const secp256k1_frost_tweak_cache *cache, const unsigned char *msg, size_t msglen, unsigned char *sig64) {
|
||||
int i;
|
||||
const secp256k1_frost_pubnonce *pubnonces[N_SIGNERS];
|
||||
const secp256k1_frost_partial_sig *partial_sigs[N_SIGNERS];
|
||||
secp256k1_pubkey signer_pubshares[N_SIGNERS];
|
||||
uint32_t ids[N_SIGNERS];
|
||||
/* The same for all signers and the coordinator */
|
||||
secp256k1_frost_aggnonce aggnonce;
|
||||
secp256k1_frost_session session;
|
||||
/* The x-only encoding of the tweaked threshold public key, bound into the
|
||||
* nonce derivation */
|
||||
secp256k1_xonly_pubkey tweaked_pk;
|
||||
unsigned char tweaked_pk32[32];
|
||||
|
||||
if (!secp256k1_frost_tweaked_pubkey_get(ctx, &tweaked_pk, cache)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_xonly_pubkey_serialize(ctx, tweaked_pk32, &tweaked_pk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Every signer creates a nonce pair and sends the pubnonce to the
|
||||
* coordinator. */
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
unsigned char session_secrand[32];
|
||||
|
||||
ids[i] = signers[i].id;
|
||||
signer_pubshares[i] = signers[i].pubshare;
|
||||
pubnonces[i] = &signers[i].pubnonce;
|
||||
partial_sigs[i] = &signers[i].partial_sig;
|
||||
|
||||
/* Create random session randomness. It is absolutely necessary that
|
||||
* this is unique for every call of secp256k1_frost_nonce_gen.
|
||||
* Otherwise it's trivial for an attacker to extract the secret share!
|
||||
* nonce_gen wipes session_secrand before returning. */
|
||||
if (!fill_random(session_secrand, sizeof(session_secrand))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return 0;
|
||||
}
|
||||
/* Bind the secret share (defense-in-depth against bad randomness),
|
||||
* the public share, the tweaked threshold public key and the message
|
||||
* into the nonce derivation. */
|
||||
if (!secp256k1_frost_nonce_gen(ctx, &signer_secrets[i].secnonce, &signers[i].pubnonce, session_secrand, signer_secrets[i].secshare, &signers[i].pubshare, tweaked_pk32, msg, msglen, NULL, 0)) {
|
||||
return 0;
|
||||
}
|
||||
secure_erase(session_secrand, sizeof(session_secrand));
|
||||
}
|
||||
|
||||
/* Communication round 1: the coordinator aggregates the pubnonces and
|
||||
* sends the aggregate nonce to the signers. */
|
||||
if (!secp256k1_frost_nonce_agg(ctx, &aggnonce, NULL, pubnonces, N_SIGNERS)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Every signer and the coordinator run session_init locally with
|
||||
* identical arguments (aside from the session object), which yields
|
||||
* identical sessions. */
|
||||
if (!secp256k1_frost_session_init(ctx, &session, &aggnonce, ids, signer_pubshares, N_SIGNERS, N_PARTICIPANTS, THRESHOLD, cache, msg, msglen)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Every signer creates a partial signature and sends it to the
|
||||
* coordinator. frost_sign wipes the secnonce. That's because you must
|
||||
* _never_ reuse the secnonce (or use the same session_secrand to create a
|
||||
* secnonce). If you do, you effectively reuse the nonce and leak the
|
||||
* secret share. */
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
if (!secp256k1_frost_sign(ctx, &signers[i].partial_sig, &signer_secrets[i].secnonce, signer_secrets[i].secshare, &session, ids, signer_pubshares, N_SIGNERS, signers[i].id)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Communication round 2: the coordinator verifies the partial signatures
|
||||
* and aggregates them. Verifying the individual partial signatures (as
|
||||
* opposed to only verifying the final signature) allows the coordinator
|
||||
* to identify which signer misbehaved if the protocol run fails. */
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
if (!secp256k1_frost_partial_sig_verify(ctx, &signers[i].partial_sig, &signers[i].pubnonce, &signers[i].pubshare, &session, ids, N_SIGNERS, i)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return secp256k1_frost_partial_sig_agg(ctx, sig64, NULL, &session, partial_sigs, N_SIGNERS);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
secp256k1_context* ctx;
|
||||
int i;
|
||||
/* Secret key material known to the trusted dealer. The dealer must erase
|
||||
* it after the participants received their shares (done at the end of
|
||||
* this function). */
|
||||
unsigned char threshold_seckey[32];
|
||||
unsigned char secshares[N_PARTICIPANTS * 32];
|
||||
secp256k1_pubkey thresh_pk;
|
||||
secp256k1_pubkey pubshares[N_PARTICIPANTS];
|
||||
secp256k1_frost_tweak_cache cache;
|
||||
secp256k1_xonly_pubkey tweaked_pk;
|
||||
struct signer_secrets signer_secrets[N_SIGNERS];
|
||||
struct signer signers[N_SIGNERS];
|
||||
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
|
||||
unsigned char sig[64];
|
||||
unsigned char buf[33];
|
||||
size_t outputlen;
|
||||
|
||||
/* Create a secp256k1 context */
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
|
||||
printf("Generating threshold key material...");
|
||||
fflush(stdout);
|
||||
if (!trusted_dealer_keygen(ctx, threshold_seckey, secshares, &thresh_pk, pubshares)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
outputlen = sizeof(buf);
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, buf, &outputlen, &thresh_pk, SECP256K1_EC_COMPRESSED)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("Threshold public key: ");
|
||||
print_hex(buf, outputlen);
|
||||
fflush(stdout);
|
||||
|
||||
/* The dealer hands each participant their secret share (over a secure
|
||||
* channel). This example signs with the participants 0 and 2. */
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
signers[i].id = SIGNER_IDS[i];
|
||||
signers[i].pubshare = pubshares[SIGNER_IDS[i]];
|
||||
memcpy(signer_secrets[i].secshare, &secshares[32 * SIGNER_IDS[i]], 32);
|
||||
}
|
||||
|
||||
printf("Applying x-only tweak (Taproot-style)...");
|
||||
fflush(stdout);
|
||||
/* Every participant and the coordinator initializes a tweak cache from
|
||||
* the threshold public key and applies the same tweaks. */
|
||||
if (!secp256k1_frost_tweak_cache_init(ctx, &cache, &thresh_pk)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!tweak(ctx, &cache, &tweaked_pk)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
if (!secp256k1_xonly_pubkey_serialize(ctx, buf, &tweaked_pk)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("Tweaked threshold public key: ");
|
||||
print_hex(buf, 32);
|
||||
fflush(stdout);
|
||||
|
||||
printf("Signing message with %d-of-%d signers...", N_SIGNERS, N_PARTICIPANTS);
|
||||
fflush(stdout);
|
||||
if (!sign(ctx, signer_secrets, signers, &cache, msg, sizeof(msg), sig)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
printf("Verifying signature.....");
|
||||
fflush(stdout);
|
||||
if (!secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &tweaked_pk)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* It's best practice to try to clear secrets from memory after using them.
|
||||
* This is done because some bugs can allow an attacker to leak memory, for
|
||||
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||||
* swapping them to disk. Hence, we overwrite secret key material with zeros.
|
||||
*
|
||||
* Here we are preventing these writes from being optimized out, as any good compiler
|
||||
* will remove any writes that aren't used.
|
||||
*
|
||||
* The secnonces have already been wiped by secp256k1_frost_sign. */
|
||||
secure_erase(threshold_seckey, sizeof(threshold_seckey));
|
||||
secure_erase(secshares, sizeof(secshares));
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
secure_erase(&signer_secrets[i], sizeof(signer_secrets[i]));
|
||||
}
|
||||
secp256k1_context_destroy(ctx);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,459 +0,0 @@
|
||||
/*************************************************************************
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
/** This file demonstrates how to use the FROST enrollment module: first to
|
||||
* grow a 2-of-3 group into a 2-of-4 one without re-running key generation,
|
||||
* then to repair a participant's lost share. Additionally, see the
|
||||
* documentation in include/secp256k1_frost_enrollment.h and
|
||||
* src/modules/frost_enrollment/frost_enrollment.md.
|
||||
*
|
||||
* The example runs all roles (trusted dealer, helpers, the party receiving
|
||||
* the share, coordinator) in a single process. In a real deployment these
|
||||
* roles are performed by different parties, and the enrollment shares and
|
||||
* the aggregated values they produce MUST travel over confidential and
|
||||
* authenticated channels: they are additive shares of real secret shares.
|
||||
*
|
||||
* Two things this example demonstrates that are easy to get wrong:
|
||||
*
|
||||
* 1. The verification flow is only non-circular if the threshold public key
|
||||
* comes from somewhere the helpers do not control. Here the enrollee gets
|
||||
* it straight from the dealer step, which stands in for real
|
||||
* authentication, and validates the helpers' public shares against it
|
||||
* before deriving anything from them.
|
||||
* 2. Nothing in the protocol decides WHO may be enrolled. That is a
|
||||
* caller-side precondition; see the note before enroll() below.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_extrakeys.h>
|
||||
#include <secp256k1_frost.h>
|
||||
#include <secp256k1_frost_enrollment.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
/* Number of participants n before the enrollment */
|
||||
#define N_PARTICIPANTS 3
|
||||
/* Number of participants after the enrollment */
|
||||
#define N_PARTICIPANTS_AFTER (N_PARTICIPANTS + 1)
|
||||
/* Threshold t */
|
||||
#define THRESHOLD 2
|
||||
/* Number of helpers u taking part in the enrollment run. Any u with
|
||||
* THRESHOLD <= u <= N_PARTICIPANTS works; the result does not depend on the
|
||||
* choice. */
|
||||
#define N_HELPERS 2
|
||||
/* The identifier the new participant will hold. In enrollment mode this must
|
||||
* be exactly the current participant count. */
|
||||
#define NEW_ID N_PARTICIPANTS
|
||||
/* The signers of the demonstration signing session: participant 2 and the
|
||||
* newly enrolled participant. */
|
||||
#define N_SIGNERS 2
|
||||
|
||||
/* What one helper holds during a run. */
|
||||
struct helper {
|
||||
uint32_t id;
|
||||
/* The helper's own secret share of the group key. Never leaves the
|
||||
* helper. */
|
||||
unsigned char secshare[32];
|
||||
/* Round 1.1 output, aligned with the helper id array: entry j goes to the
|
||||
* helper at position j. The entry at this helper's own position is kept
|
||||
* locally. These are secret. */
|
||||
unsigned char shares[N_HELPERS * 32];
|
||||
/* The parameters hash this helper computed, sent alongside every share it
|
||||
* distributes and to the enrollee. Public. */
|
||||
unsigned char params_hash[32];
|
||||
/* Round 1.2 output, sent to the enrollee. Secret. */
|
||||
unsigned char sigma[32];
|
||||
};
|
||||
|
||||
/* Run the trusted dealer key generation for the initial 2-of-3 group.
|
||||
*
|
||||
* WARNING: The trusted dealer knows the threshold secret key and all secret
|
||||
* shares, and must erase them after distributing the shares. A distributed key
|
||||
* generation protocol avoids a trusted dealer entirely; see the chilldkg
|
||||
* module. */
|
||||
static int trusted_dealer_keygen(const secp256k1_context* ctx, unsigned char *threshold_seckey, unsigned char *secshares, secp256k1_pubkey *thresh_pk, secp256k1_pubkey *pubshares) {
|
||||
if (!fill_random(threshold_seckey, 32)) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_frost_trusted_dealer_keygen(ctx, secshares, thresh_pk, pubshares, N_PARTICIPANTS, THRESHOLD, threshold_seckey)) {
|
||||
return 0;
|
||||
}
|
||||
return secp256k1_frost_threshold_info_validate(ctx, thresh_pk, pubshares, N_PARTICIPANTS, THRESHOLD);
|
||||
}
|
||||
|
||||
/* Run one enrollment or repair, all three rounds.
|
||||
*
|
||||
* `helper_pubshares` and `helpers` are aligned with `ids`. On success the new
|
||||
* secret share is written to `new_secshare` and its public counterpart to
|
||||
* `new_pubshare`.
|
||||
*
|
||||
* `n_participants` must be the group size all parties CURRENTLY agree on: it
|
||||
* is bound into the parameters hash, so a helper using a stale n and one
|
||||
* using the updated n abort round 1.2 against each other. That is why this is
|
||||
* a parameter and not the N_PARTICIPANTS constant -- the repair below runs
|
||||
* after the enrollment, when the group has already grown to
|
||||
* N_PARTICIPANTS_AFTER.
|
||||
*
|
||||
* PRECONDITION THE LIBRARY CANNOT ENFORCE: the helpers must already have
|
||||
* agreed, out of band, that this party is entitled to a share at `new_id`.
|
||||
* The protocol has no authorization step of its own: anyone who convinces t
|
||||
* helpers to run it walks away with a valid share, and in repair mode that is
|
||||
* an existing participant's actual share. Authenticated channels establish who
|
||||
* is speaking, not that the group approved the request. */
|
||||
static int enroll(const secp256k1_context* ctx, unsigned char *new_secshare, secp256k1_pubkey *new_pubshare, struct helper *helpers, const uint32_t *ids, const secp256k1_pubkey *helper_pubshares, const secp256k1_pubkey *thresh_pk, uint32_t new_id, size_t n_participants) {
|
||||
unsigned char sigmas[N_HELPERS * 32];
|
||||
unsigned char all_shares[N_HELPERS * 32];
|
||||
unsigned char enrollee_params_hash[32];
|
||||
int i, j;
|
||||
int ret = 0;
|
||||
|
||||
memset(sigmas, 0, sizeof(sigmas));
|
||||
memset(all_shares, 0, sizeof(all_shares));
|
||||
|
||||
/* --- Round 1.1 ------------------------------------------------------
|
||||
* Every helper splits its Lagrange-weighted share into one additive share
|
||||
* per helper, keeps its own and sends the rest out. */
|
||||
for (i = 0; i < N_HELPERS; i++) {
|
||||
unsigned char session_secrand[32];
|
||||
|
||||
/* Fresh randomness for every run: reusing it across runs leaks share
|
||||
* information. No secure_erase is needed here -- shares_gen wipes the
|
||||
* seed itself, on success and on failure alike, so that a failed call
|
||||
* cannot be retried on the same randomness. */
|
||||
if (!fill_random(session_secrand, sizeof(session_secrand))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
secure_erase(session_secrand, sizeof(session_secrand));
|
||||
goto cleanup;
|
||||
}
|
||||
if (!secp256k1_frost_enrollment_shares_gen(ctx, helpers[i].shares, helpers[i].params_hash, session_secrand, helpers[i].secshare, thresh_pk, ids, N_HELPERS, helpers[i].id, new_id, n_participants, THRESHOLD)) {
|
||||
/* shares_gen wipes the seed on every path, including this one. */
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Round 1.2 ------------------------------------------------------
|
||||
* Helper j receives entry j of every other helper's buffer, over a
|
||||
* confidential and authenticated channel, together with that helper's
|
||||
* parameters hash. It recomputes the hash itself and compares. */
|
||||
for (j = 0; j < N_HELPERS; j++) {
|
||||
unsigned char received_hashes[N_HELPERS * 32];
|
||||
uint32_t mismatch_id;
|
||||
|
||||
/* The two buffers take deliberately opposite conventions at this
|
||||
* helper's own position: the share kept locally IS read, while the
|
||||
* hash slot is never read and stays zero. The own hash is
|
||||
* recomputed, never taken from a buffer -- which is what makes this
|
||||
* a recomputation check rather than a comparison between two strings
|
||||
* the caller supplied. */
|
||||
memset(received_hashes, 0, sizeof(received_hashes));
|
||||
for (i = 0; i < N_HELPERS; i++) {
|
||||
memcpy(&all_shares[32 * i], &helpers[i].shares[32 * j], 32);
|
||||
if (i != j) {
|
||||
memcpy(&received_hashes[32 * i], helpers[i].params_hash, 32);
|
||||
}
|
||||
}
|
||||
if (!secp256k1_frost_enrollment_share_agg(ctx, helpers[j].sigma, &mismatch_id, all_shares, received_hashes, thresh_pk, ids, N_HELPERS, helpers[j].id, new_id, n_participants, THRESHOLD)) {
|
||||
if (mismatch_id != UINT32_MAX) {
|
||||
/* Either that helper ran round 1.1 on different parameters,
|
||||
* or its share did not survive transit. share_agg does not
|
||||
* distinguish the two, so neither can this message. */
|
||||
printf("\nHelper %u contributed a share this helper cannot use\n", mismatch_id);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Round 2 --------------------------------------------------------
|
||||
* The enrollee receives one value from each helper, again confidentially
|
||||
* and authenticated, plus the parameters hash.
|
||||
*
|
||||
* The expected public share is derived from the helpers' public shares --
|
||||
* which the caller has already validated against an independently
|
||||
* authenticated threshold public key (see main). Without that step both
|
||||
* checks below would be circular: t colluding helpers could present a
|
||||
* consistent but fabricated polynomial and every check would pass on a
|
||||
* worthless share. */
|
||||
for (i = 0; i < N_HELPERS; i++) {
|
||||
memcpy(&sigmas[32 * i], helpers[i].sigma, 32);
|
||||
}
|
||||
memcpy(enrollee_params_hash, helpers[0].params_hash, 32);
|
||||
|
||||
if (!secp256k1_frost_enrollment_pubshare_derive(ctx, new_pubshare, helper_pubshares, ids, N_HELPERS, new_id, n_participants, THRESHOLD)) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (!secp256k1_frost_enrollment_secshare_gen(ctx, new_secshare, sigmas, thresh_pk, ids, N_HELPERS, new_id, n_participants, THRESHOLD, enrollee_params_hash, new_pubshare)) {
|
||||
goto cleanup;
|
||||
}
|
||||
ret = 1;
|
||||
|
||||
cleanup:
|
||||
/* Every exit runs this, not just the successful one. The delta and sigma
|
||||
* values are additive shares of real secret shares, and the paths where
|
||||
* hygiene matters most are exactly the ones a protocol fault takes. */
|
||||
secure_erase(sigmas, sizeof(sigmas));
|
||||
secure_erase(all_shares, sizeof(all_shares));
|
||||
for (i = 0; i < N_HELPERS; i++) {
|
||||
secure_erase(helpers[i].shares, sizeof(helpers[i].shares));
|
||||
secure_erase(helpers[i].sigma, sizeof(helpers[i].sigma));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Produce a BIP340 signature with the given signer set and verify it against
|
||||
* the (untweaked) threshold public key. `secshares` and `pubshares` are
|
||||
* aligned with `ids`. `n_participants` is the group size AFTER the enrollment,
|
||||
* which every participant must have updated consistently. */
|
||||
static int sign_and_verify(const secp256k1_context* ctx, const uint32_t *ids, unsigned char (*secshares)[32], const secp256k1_pubkey *pubshares, const secp256k1_pubkey *thresh_pk, size_t n_participants, const unsigned char *msg, size_t msglen) {
|
||||
secp256k1_frost_tweak_cache cache;
|
||||
secp256k1_frost_secnonce secnonces[N_SIGNERS];
|
||||
secp256k1_frost_pubnonce pubnonces[N_SIGNERS];
|
||||
const secp256k1_frost_pubnonce *pubnonce_ptrs[N_SIGNERS];
|
||||
secp256k1_frost_partial_sig partial_sigs[N_SIGNERS];
|
||||
const secp256k1_frost_partial_sig *partial_sig_ptrs[N_SIGNERS];
|
||||
secp256k1_frost_aggnonce aggnonce;
|
||||
secp256k1_frost_session session;
|
||||
secp256k1_xonly_pubkey tweaked_pk;
|
||||
unsigned char tweaked_pk32[32];
|
||||
unsigned char sig[64];
|
||||
int i;
|
||||
int ret = 0;
|
||||
|
||||
/* Zeroed up front so the cleanup below can erase them unconditionally,
|
||||
* whichever failure path got there. */
|
||||
memset(secnonces, 0, sizeof(secnonces));
|
||||
|
||||
if (!secp256k1_frost_tweak_cache_init(ctx, &cache, thresh_pk)) {
|
||||
goto cleanup;
|
||||
}
|
||||
/* No tweaks are applied here, so the "tweaked" key is the threshold public
|
||||
* key itself in its x-only encoding. */
|
||||
if (!secp256k1_frost_tweaked_pubkey_get(ctx, &tweaked_pk, &cache)
|
||||
|| !secp256k1_xonly_pubkey_serialize(ctx, tweaked_pk32, &tweaked_pk)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
unsigned char session_secrand[32];
|
||||
if (!fill_random(session_secrand, sizeof(session_secrand))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
secure_erase(session_secrand, sizeof(session_secrand));
|
||||
goto cleanup;
|
||||
}
|
||||
/* nonce_gen wipes the seed itself, on every path. */
|
||||
if (!secp256k1_frost_nonce_gen(ctx, &secnonces[i], &pubnonces[i], session_secrand, secshares[i], &pubshares[i], tweaked_pk32, msg, msglen, NULL, 0)) {
|
||||
goto cleanup;
|
||||
}
|
||||
pubnonce_ptrs[i] = &pubnonces[i];
|
||||
partial_sig_ptrs[i] = &partial_sigs[i];
|
||||
}
|
||||
if (!secp256k1_frost_nonce_agg(ctx, &aggnonce, NULL, pubnonce_ptrs, N_SIGNERS)) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (!secp256k1_frost_session_init(ctx, &session, &aggnonce, ids, pubshares, N_SIGNERS, n_participants, THRESHOLD, &cache, msg, msglen)) {
|
||||
goto cleanup;
|
||||
}
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
if (!secp256k1_frost_sign(ctx, &partial_sigs[i], &secnonces[i], secshares[i], &session, ids, pubshares, N_SIGNERS, ids[i])) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (!secp256k1_frost_partial_sig_verify(ctx, &partial_sigs[i], &pubnonces[i], &pubshares[i], &session, ids, N_SIGNERS, (size_t)i)) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
if (!secp256k1_frost_partial_sig_agg(ctx, sig, NULL, &session, partial_sig_ptrs, N_SIGNERS)) {
|
||||
goto cleanup;
|
||||
}
|
||||
ret = secp256k1_schnorrsig_verify(ctx, sig, msg, msglen, &tweaked_pk);
|
||||
|
||||
cleanup:
|
||||
/* frost_sign wipes a secnonce it consumed, but a failure before or during
|
||||
* the signing loop can leave others live. */
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
secure_erase(&secnonces[i], sizeof(secnonces[i]));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
secp256k1_context* ctx;
|
||||
/* Key material of the initial 2-of-3 group. The dealer erases all of it
|
||||
* at the end of this function. */
|
||||
unsigned char threshold_seckey[32];
|
||||
unsigned char secshares[N_PARTICIPANTS * 32];
|
||||
secp256k1_pubkey thresh_pk;
|
||||
secp256k1_pubkey pubshares[N_PARTICIPANTS_AFTER];
|
||||
|
||||
/* The enrollment run for the new participant 3. */
|
||||
struct helper helpers[N_HELPERS];
|
||||
uint32_t helper_ids[N_HELPERS] = { 0, 1 };
|
||||
secp256k1_pubkey helper_pubshares[N_HELPERS];
|
||||
unsigned char new_secshare[32];
|
||||
secp256k1_pubkey new_pubshare;
|
||||
|
||||
/* The repair run for participant 1. */
|
||||
struct helper repair_helpers[N_HELPERS];
|
||||
uint32_t repair_ids[N_HELPERS] = { 0, 2 };
|
||||
secp256k1_pubkey repair_pubshares[N_HELPERS];
|
||||
unsigned char repaired_secshare[32];
|
||||
secp256k1_pubkey repaired_pubshare;
|
||||
|
||||
/* The signing session that proves the enrolled participant works. */
|
||||
uint32_t signer_ids[N_SIGNERS] = { 2, NEW_ID };
|
||||
unsigned char signer_secshares[N_SIGNERS][32];
|
||||
secp256k1_pubkey signer_pubshares[N_SIGNERS];
|
||||
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
|
||||
|
||||
unsigned char buf[33];
|
||||
size_t outputlen;
|
||||
int i;
|
||||
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
|
||||
printf("Generating threshold key material for a %d-of-%d group...", THRESHOLD, N_PARTICIPANTS);
|
||||
fflush(stdout);
|
||||
if (!trusted_dealer_keygen(ctx, threshold_seckey, secshares, &thresh_pk, pubshares)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
outputlen = sizeof(buf);
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, buf, &outputlen, &thresh_pk, SECP256K1_EC_COMPRESSED)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("Threshold public key: ");
|
||||
print_hex(buf, outputlen);
|
||||
fflush(stdout);
|
||||
|
||||
/* The new participant obtains the threshold public key from a source it
|
||||
* authenticates INDEPENDENTLY OF THE HELPERS. In this single-process
|
||||
* example that is the dealer step above; in a real deployment it might be
|
||||
* a signed group descriptor, an on-chain commitment, or whatever the group
|
||||
* already trusts to say what it is.
|
||||
*
|
||||
* It then validates the helpers' public shares against that key. This is
|
||||
* the step that makes the round 2 checks worth anything: without it, the
|
||||
* enrollee would be checking the helpers' arithmetic against numbers the
|
||||
* helpers also chose, and t colluding helpers could hand it a consistent
|
||||
* fake polynomial. */
|
||||
printf("Validating the group's public shares against the authenticated key...");
|
||||
fflush(stdout);
|
||||
if (!secp256k1_frost_threshold_info_validate(ctx, &thresh_pk, pubshares, N_PARTICIPANTS, THRESHOLD)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* Enrollment: helpers 0 and 1 give participant 3 a share. */
|
||||
for (i = 0; i < N_HELPERS; i++) {
|
||||
helpers[i].id = helper_ids[i];
|
||||
memcpy(helpers[i].secshare, &secshares[32 * helper_ids[i]], 32);
|
||||
helper_pubshares[i] = pubshares[helper_ids[i]];
|
||||
}
|
||||
printf("Enrolling participant %d with %d helpers...", NEW_ID, N_HELPERS);
|
||||
fflush(stdout);
|
||||
if (!enroll(ctx, new_secshare, &new_pubshare, helpers, helper_ids, helper_pubshares, &thresh_pk, NEW_ID, N_PARTICIPANTS)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* n -> n+1 bookkeeping. Every participant must extend its table of public
|
||||
* shares and update its record of n; participants that disagree about n
|
||||
* compute different Lagrange coefficients and cannot sign together. */
|
||||
pubshares[NEW_ID] = new_pubshare;
|
||||
printf("Validating the extended %d-participant key material...", N_PARTICIPANTS_AFTER);
|
||||
fflush(stdout);
|
||||
if (!secp256k1_frost_threshold_info_validate(ctx, &thresh_pk, pubshares, N_PARTICIPANTS_AFTER, THRESHOLD)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
outputlen = sizeof(buf);
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, buf, &outputlen, &new_pubshare, SECP256K1_EC_COMPRESSED)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("New participant's public share: ");
|
||||
print_hex(buf, outputlen);
|
||||
fflush(stdout);
|
||||
|
||||
/* Sign with participant 2 and the newly enrolled participant 3. The
|
||||
* signature verifies against the group's original threshold public key:
|
||||
* enrollment does not change the group key or any existing share. */
|
||||
memcpy(signer_secshares[0], &secshares[32 * signer_ids[0]], 32);
|
||||
memcpy(signer_secshares[1], new_secshare, 32);
|
||||
signer_pubshares[0] = pubshares[signer_ids[0]];
|
||||
signer_pubshares[1] = pubshares[signer_ids[1]];
|
||||
printf("Signing with participants %u and %u...", signer_ids[0], signer_ids[1]);
|
||||
fflush(stdout);
|
||||
if (!sign_and_verify(ctx, signer_ids, signer_secshares, signer_pubshares, &thresh_pk, N_PARTICIPANTS_AFTER, msg, sizeof(msg))) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* Repair: participant 1 lost its share. The same three rounds at
|
||||
* new_id = 1 reproduce it exactly -- the share is a fixed value, f(x_1),
|
||||
* not a fresh random one.
|
||||
*
|
||||
* Note that n is N_PARTICIPANTS_AFTER here, not N_PARTICIPANTS: the group
|
||||
* grew above, and n is bound into the parameters hash, so a party still
|
||||
* using the pre-enrollment value would abort round 1.2 against the
|
||||
* others. Repair itself does not change n.
|
||||
*
|
||||
* This is also the mode where the missing authorization step bites
|
||||
* hardest: whoever convinces the helpers to run it receives participant
|
||||
* 1's actual share. */
|
||||
for (i = 0; i < N_HELPERS; i++) {
|
||||
repair_helpers[i].id = repair_ids[i];
|
||||
memcpy(repair_helpers[i].secshare, &secshares[32 * repair_ids[i]], 32);
|
||||
repair_pubshares[i] = pubshares[repair_ids[i]];
|
||||
}
|
||||
printf("Repairing participant 1's lost share...");
|
||||
fflush(stdout);
|
||||
if (!enroll(ctx, repaired_secshare, &repaired_pubshare, repair_helpers, repair_ids, repair_pubshares, &thresh_pk, 1, N_PARTICIPANTS_AFTER)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (memcmp(repaired_secshare, &secshares[32], 32) != 0) {
|
||||
printf("FAILED (the repaired share differs from the original)\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (memcmp(&repaired_pubshare, &pubshares[1], sizeof(repaired_pubshare)) != 0) {
|
||||
printf("FAILED (the repaired public share differs from the original)\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* It's best practice to try to clear secrets from memory after using them.
|
||||
* This is done because some bugs can allow an attacker to leak memory, for
|
||||
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||||
* swapping them to disk. Hence, we overwrite secret key material with
|
||||
* zeros.
|
||||
*
|
||||
* The session randomness was already wiped by shares_gen, and the
|
||||
* secnonces by frost_sign. */
|
||||
secure_erase(threshold_seckey, sizeof(threshold_seckey));
|
||||
secure_erase(secshares, sizeof(secshares));
|
||||
secure_erase(new_secshare, sizeof(new_secshare));
|
||||
secure_erase(repaired_secshare, sizeof(repaired_secshare));
|
||||
secure_erase(signer_secshares, sizeof(signer_secshares));
|
||||
for (i = 0; i < N_HELPERS; i++) {
|
||||
secure_erase(&helpers[i], sizeof(helpers[i]));
|
||||
secure_erase(&repair_helpers[i], sizeof(repair_helpers[i]));
|
||||
}
|
||||
secp256k1_context_destroy(ctx);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,725 +0,0 @@
|
||||
/*************************************************************************
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
/** A 3-of-7 group signing beside an ordinary MuSig2 cosigner.
|
||||
*
|
||||
* The group behaves as one MuSig2 participant, and the finished signature is
|
||||
* an ordinary BIP-340 signature that records nothing about the group.
|
||||
*
|
||||
* Three roles appear below and they run different code, which is the thing
|
||||
* worth keeping straight while reading:
|
||||
*
|
||||
* participant holds a share, produces a nonce and a signature share, and
|
||||
* never sees the whole key. There are seven of them.
|
||||
* coordinator moves messages around and combines them. Untrusted: every
|
||||
* check here assumes it is hostile.
|
||||
* cosigner an ordinary MuSig2 signer that knows nothing about any of
|
||||
* this and calls the plain musig API.
|
||||
*
|
||||
* The participants are wiped between the two rounds and rebuilt from storage,
|
||||
* because no secret nonce survives that gap; see reboot_participants for what
|
||||
* does survive it, which is not nothing. One call is made that is expected to
|
||||
* fail, in refusals_are_refused.
|
||||
*
|
||||
* See also include/secp256k1_iceberg.h and doc/iceberg.md.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_extrakeys.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
#include <secp256k1_musig.h>
|
||||
#include <secp256k1_iceberg.h>
|
||||
/* The trusted dealer lives in its own header, and is not part of the installed
|
||||
* API. See the note at the top of it. */
|
||||
#include <secp256k1_iceberg_dealer.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
#define N 7 /* participants in the group */
|
||||
#define T 3 /* how many of them can sign */
|
||||
#define MU (2 * T - 1) /* how many must take part in round one */
|
||||
|
||||
/* One participant's memory: one secret, and one note to itself.
|
||||
*
|
||||
* The share is the secret and never changes. `answered` is the highest session
|
||||
* label this participant has signed under. The library does not know about it and
|
||||
* could not, since it holds nothing between calls, but answering twice under one
|
||||
* label gives away the key share, so somebody has to remember, and the somebody
|
||||
* is the caller. */
|
||||
struct participant {
|
||||
secp256k1_iceberg_share share;
|
||||
unsigned char answered[32];
|
||||
};
|
||||
|
||||
/* The same participant's disk. A reboot loses the struct above, not this one,
|
||||
* and both fields have to come back. See reboot_participants. */
|
||||
struct storage {
|
||||
unsigned char share_bytes[SECP256K1_ICEBERG_SHARE_MAX_LEN];
|
||||
size_t share_len;
|
||||
unsigned char answered_bytes[32];
|
||||
};
|
||||
|
||||
/* The threshold side. Set up once, then unchanged for the life of the group. */
|
||||
struct group {
|
||||
struct participant member[N];
|
||||
struct storage disk[N];
|
||||
secp256k1_iceberg_pubshare pubshare[N];
|
||||
secp256k1_pubkey pubkey;
|
||||
};
|
||||
|
||||
/* The other side: an ordinary MuSig2 signer, which knows nothing about any of
|
||||
* the above. Everything it holds is its own, including the secret nonce that
|
||||
* the group deliberately does not have an equivalent of. */
|
||||
struct cosigner {
|
||||
secp256k1_keypair keypair;
|
||||
secp256k1_pubkey pubkey;
|
||||
secp256k1_musig_secnonce secnonce;
|
||||
};
|
||||
|
||||
/* What the two of them add up to. Computed once, at key aggregation, and the
|
||||
* only thing a verifier ever sees. */
|
||||
struct shared_key {
|
||||
secp256k1_musig_keyagg_cache keyagg_cache;
|
||||
secp256k1_xonly_pubkey output;
|
||||
};
|
||||
|
||||
/* One attempt at one signature. Everything here is public. */
|
||||
struct signing_session {
|
||||
unsigned char sid[32];
|
||||
secp256k1_iceberg_pubnonce contribution[MU];
|
||||
const secp256k1_iceberg_pubnonce *contribution_ptr[MU];
|
||||
secp256k1_musig_pubnonce group_nonce; /* the group's, after interpolating */
|
||||
secp256k1_musig_pubnonce cosigner_nonce; /* the cosigner's, as published */
|
||||
secp256k1_musig_aggnonce cosigner_aggnonce; /* the cosigners' alone */
|
||||
secp256k1_musig_session musig_session;
|
||||
};
|
||||
|
||||
static void heading(const char *text) {
|
||||
printf("\n%s\n", text);
|
||||
}
|
||||
|
||||
static void step(const char *text) {
|
||||
printf(" %-55s", text);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Deal the shares and publish the group's key.
|
||||
*
|
||||
* This is a trusted dealer: for the length of one call, this machine holds
|
||||
* enough to reconstruct the group's private key. Acceptable for testing, and
|
||||
* where one party is trusted already. A distributed key generation produces
|
||||
* the same shares without that moment ever existing, and this module does not
|
||||
* provide one. */
|
||||
static int deal_shares(const secp256k1_context *ctx, struct group *group) {
|
||||
secp256k1_iceberg_share *share_ptr[N];
|
||||
const secp256k1_iceberg_pubshare *pubshare_ptr[N];
|
||||
unsigned char seed[32];
|
||||
unsigned int k;
|
||||
int ok;
|
||||
|
||||
step("Dealing shares (secp256k1_iceberg_shares_gen)");
|
||||
if (!fill_random(seed, sizeof(seed))) {
|
||||
return 0;
|
||||
}
|
||||
for (k = 0; k < N; k++) {
|
||||
share_ptr[k] = &group->member[k].share;
|
||||
}
|
||||
ok = secp256k1_iceberg_shares_gen(ctx, share_ptr, N, T, seed);
|
||||
secure_erase(seed, sizeof(seed));
|
||||
if (!ok) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* A share is one seed per (t-1)-subset that leaves this participant out,
|
||||
* so it is large and grows quickly with the group. Each participant keeps
|
||||
* its own and nothing else. */
|
||||
step("Storing them (secp256k1_iceberg_share_serialize)");
|
||||
for (k = 0; k < N; k++) {
|
||||
group->disk[k].share_len = sizeof(group->disk[k].share_bytes);
|
||||
if (!secp256k1_iceberg_share_serialize(ctx, group->disk[k].share_bytes,
|
||||
&group->disk[k].share_len, &group->member[k].share)) {
|
||||
return 0;
|
||||
}
|
||||
/* Nothing answered yet, so the lowest possible label. Do this once, at
|
||||
* dealing; doing it again later throws the protection away. */
|
||||
memset(group->member[k].answered, 0, sizeof(group->member[k].answered));
|
||||
memcpy(group->disk[k].answered_bytes, group->member[k].answered, 32);
|
||||
}
|
||||
printf("ok, %lu bytes each\n", (unsigned long)group->disk[0].share_len);
|
||||
|
||||
step("Group public key (secp256k1_iceberg_pubkey_agg)");
|
||||
for (k = 0; k < N; k++) {
|
||||
if (!secp256k1_iceberg_pubshare_gen(ctx, &group->pubshare[k], &group->member[k].share, NULL)) {
|
||||
return 0;
|
||||
}
|
||||
pubshare_ptr[k] = &group->pubshare[k];
|
||||
}
|
||||
/* 2t-1 public shares are more than the key needs, since t of them already
|
||||
* determine it, and the surplus is the point: they have to agree, so a
|
||||
* participant that published a wrong one is caught now rather than at
|
||||
* signing time. */
|
||||
if (!secp256k1_iceberg_pubkey_agg(ctx, &group->pubkey, pubshare_ptr, MU, N, T)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Aggregate the group's key with the cosigner's, and tweak the result.
|
||||
*
|
||||
* Nothing below this point knows that one of the two keys is a group. The
|
||||
* tweak belongs to this outer session: musig_nonce_process sets its term aside
|
||||
* and musig_partial_sig_agg adds it once, at the top. No Iceberg call is even
|
||||
* told the tweak happened, which is what stops it being counted twice. */
|
||||
static int aggregate_keys(const secp256k1_context *ctx, struct group *group, struct cosigner *cosigner, struct shared_key *shared) {
|
||||
const secp256k1_pubkey *pubkeys[2];
|
||||
secp256k1_pubkey output_pk;
|
||||
unsigned char taptweak[32];
|
||||
unsigned char seckey[32];
|
||||
int ok;
|
||||
|
||||
step("Cosigner keypair (secp256k1_keypair_create)");
|
||||
ok = fill_random(seckey, sizeof(seckey))
|
||||
&& secp256k1_keypair_create(ctx, &cosigner->keypair, seckey)
|
||||
&& secp256k1_keypair_pub(ctx, &cosigner->pubkey, &cosigner->keypair);
|
||||
/* The keypair holds everything needed from here on, so the raw key does not
|
||||
* outlive this call; secp256k1_keypair_sec brings it back when required. */
|
||||
secure_erase(seckey, sizeof(seckey));
|
||||
if (!ok) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
step("Aggregating (secp256k1_musig_pubkey_agg)");
|
||||
pubkeys[0] = &group->pubkey;
|
||||
pubkeys[1] = &cosigner->pubkey;
|
||||
if (!secp256k1_musig_pubkey_agg(ctx, NULL, &shared->keyagg_cache, pubkeys, 2)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* The cache records the hash of the key list, not the list, so nothing ties
|
||||
* it to the group's key later. Run this once, here, rather than trusting a
|
||||
* cache round two cannot check. */
|
||||
step("Checking the cache (secp256k1_iceberg_keyagg_check)");
|
||||
if (!secp256k1_iceberg_keyagg_check(ctx, &shared->keyagg_cache, pubkeys, 2,
|
||||
&group->pubkey)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
step("Tweaking (secp256k1_musig_pubkey_xonly_tweak_add)");
|
||||
if (!fill_random(taptweak, sizeof(taptweak))
|
||||
|| !secp256k1_musig_pubkey_xonly_tweak_add(ctx, &output_pk, &shared->keyagg_cache, taptweak)
|
||||
|| !secp256k1_xonly_pubkey_from_pubkey(ctx, &shared->output, NULL, &output_pk)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Round one, the group's half. Note the arguments: a share and a label. No
|
||||
* message, no cosigner, nothing from anybody else. */
|
||||
static int group_round_one(const secp256k1_context *ctx, struct group *group,
|
||||
struct signing_session *session) {
|
||||
unsigned int k;
|
||||
|
||||
/* The label is the most delicate value in the scheme, and it is the
|
||||
* caller's to choose. Every participant's secret nonces are a function of
|
||||
* (its seeds, this label), and the seeds never change, so whoever picks the
|
||||
* label picks everyone's secrets.
|
||||
*
|
||||
* Here it is a fixed byte string, which is fine for an example and wrong
|
||||
* for anything else. In Lightning it is the commitment number: unique,
|
||||
* strictly increasing over the channel's life, and known before the
|
||||
* transaction is assembled, which is what lets this round run at all,
|
||||
* since the message does not exist yet. */
|
||||
step("Choosing the session label");
|
||||
memset(session->sid, 0x2c, sizeof(session->sid));
|
||||
printf("ok, ");
|
||||
print_hex(session->sid, 4);
|
||||
|
||||
step("Participant nonces (secp256k1_iceberg_nonce_gen)");
|
||||
for (k = 0; k < MU; k++) {
|
||||
if (!secp256k1_iceberg_nonce_gen(ctx, &session->contribution[k], &group->member[k].share, NULL, session->sid)) {
|
||||
return 0;
|
||||
}
|
||||
session->contribution_ptr[k] = &session->contribution[k];
|
||||
}
|
||||
printf("ok, %d of the %d participants\n", MU, N);
|
||||
|
||||
/* Verification, not addition. The contributions are points on a degree
|
||||
* t-1 polynomial in the exponent; this checks that they lie on one and
|
||||
* then interpolates. It is most of what the group costs, and it is what
|
||||
* stops a single participant biasing the group's nonce.
|
||||
*
|
||||
* NULL is the group's internal aggregate, which nothing takes back. */
|
||||
step("Combining them (secp256k1_iceberg_nonce_agg)");
|
||||
if (!secp256k1_iceberg_nonce_agg(ctx, &session->group_nonce, NULL,
|
||||
session->contribution_ptr, MU, N, T, &group->pubkey)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Round one, the cosigner's half. Ordinary MuSig2, and it does not know a group
|
||||
* exists. Runs before, after or alongside the function above. */
|
||||
static int cosigner_round_one(const secp256k1_context *ctx, struct cosigner *cosigner, struct shared_key *shared,
|
||||
struct signing_session *session, const unsigned char *msg32) {
|
||||
const secp256k1_musig_pubnonce *just_the_cosigner[1];
|
||||
unsigned char secrand[32];
|
||||
unsigned char seckey[32];
|
||||
int ok;
|
||||
|
||||
step("Cosigner nonce (secp256k1_musig_nonce_gen)");
|
||||
ok = fill_random(secrand, sizeof(secrand))
|
||||
&& secp256k1_keypair_sec(ctx, seckey, &cosigner->keypair)
|
||||
&& secp256k1_musig_nonce_gen(ctx, &cosigner->secnonce, &session->cosigner_nonce,
|
||||
secrand, seckey, &cosigner->pubkey,
|
||||
msg32, &shared->keyagg_cache, NULL);
|
||||
secure_erase(secrand, sizeof(secrand));
|
||||
secure_erase(seckey, sizeof(seckey));
|
||||
if (!ok) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Round two needs the cosigners' aggregate on its own, separately from the
|
||||
* one that includes the group. With one cosigner it is an aggregate of one,
|
||||
* which is not a special case anywhere. */
|
||||
just_the_cosigner[0] = &session->cosigner_nonce;
|
||||
if (!secp256k1_musig_nonce_agg(ctx, &session->cosigner_aggnonce, just_the_cosigner, 1)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Both halves have published. Combine them into the session everybody signs
|
||||
* against, which is where the message finally enters. */
|
||||
static int open_session(const secp256k1_context *ctx, struct shared_key *shared,
|
||||
struct signing_session *session, const unsigned char *msg32) {
|
||||
const secp256k1_musig_pubnonce *both[2];
|
||||
secp256k1_musig_aggnonce full_aggnonce;
|
||||
|
||||
step("Session (secp256k1_musig_nonce_process)");
|
||||
both[0] = &session->group_nonce;
|
||||
both[1] = &session->cosigner_nonce;
|
||||
if (!secp256k1_musig_nonce_agg(ctx, &full_aggnonce, both, 2)
|
||||
|| !secp256k1_musig_nonce_process(ctx, &session->musig_session, &full_aggnonce,
|
||||
msg32, &shared->keyagg_cache, NULL)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Forget the secret nonces, then rebuild from disk.
|
||||
*
|
||||
* A FROST signer has to keep a secret nonce alive across this line, and losing
|
||||
* it, restoring an old backup over it, or running two copies of the signer are
|
||||
* each catastrophic. An Iceberg participant keeps no secret nonce at all: its
|
||||
* nonces are a function of its share and the label, both of which it is given
|
||||
* again.
|
||||
*
|
||||
* That property is narrower than "keeps no state".
|
||||
* A participant still has to remember which labels it has already answered
|
||||
* under, because two answers under one label are two equations in its three
|
||||
* secrets and three are enough to recover its key share. That bookkeeping is
|
||||
* not secret, and it is not optional; it is simply not this library's, since
|
||||
* only the group as a whole can decide which message a label belongs to.
|
||||
*
|
||||
* So wipe the secrets and prove that much. Skip the parse below and the next
|
||||
* call is handed a wiped share, whose magic fails an ARG_CHECK, so it aborts
|
||||
* through the illegal callback rather than returning 0. */
|
||||
static int reboot_participants(const secp256k1_context *ctx, struct group *group) {
|
||||
unsigned int k;
|
||||
|
||||
step("Wiping every participant's secret material");
|
||||
for (k = 0; k < N; k++) {
|
||||
secure_erase(&group->member[k], sizeof(group->member[k]));
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
step("Rebuilding (secp256k1_iceberg_share_parse)");
|
||||
for (k = 0; k < N; k++) {
|
||||
if (!secp256k1_iceberg_share_parse(ctx, &group->member[k].share,
|
||||
group->disk[k].share_bytes,
|
||||
group->disk[k].share_len)) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(group->member[k].answered, group->disk[k].answered_bytes, 32);
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* Note what came back besides the share: `answered`. That record is not
|
||||
* secret, which is why it sits on the same disk in the clear, and it is
|
||||
* not optional, which is why it is restored here rather than left at zero.
|
||||
* A participant that forgets it will answer twice. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* One label, one answer: the half of the rule a participant can enforce alone.
|
||||
*
|
||||
* The library cannot do this for you. It holds nothing between calls, so "have I
|
||||
* answered under this label before?" is a question only the caller's storage can
|
||||
* answer. Here that storage is one 32-byte field per participant and the rule is
|
||||
* that a label must be strictly greater than the last one signed under, which is
|
||||
* free when the label is a counter. It refuses a repeat outright rather than
|
||||
* asking whether the repeat was harmless: a label is one signing attempt, so an
|
||||
* honest retry arrives under a new label and never needs the exception.
|
||||
*
|
||||
* The record reaches disk here, before the caller has a share to publish. A
|
||||
* crash between signing and storing is the same as never having stored, and the
|
||||
* next boot answers the label again.
|
||||
*
|
||||
* The other half of the rule, that no two participants answer one label on
|
||||
* different messages, cannot be checked here or anywhere else inside a
|
||||
* participant, because it is a fact about what other people were shown. See
|
||||
* doc/iceberg.md. */
|
||||
static int may_sign_under(struct participant *member, struct storage *disk, const unsigned char *sid32) {
|
||||
if (memcmp(sid32, member->answered, 32) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(member->answered, sid32, 32);
|
||||
memcpy(disk->answered_bytes, member->answered, 32);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The group's half of round two, signed by participants first, first+1 and
|
||||
* first+2.
|
||||
*
|
||||
* Taking `first` is not generality for its own sake. The point of the scheme is
|
||||
* that any t of them will do, so main runs this twice with different people and
|
||||
* compares the two signatures.
|
||||
*
|
||||
* Any t of the seven, and not only the five who were in round one. The last
|
||||
* section of main proves that by having a participant who sat round one out
|
||||
* sign anyway. What each signer does need is the whole round-one set to check
|
||||
* against, which is a different count from the number of signers. */
|
||||
static int group_signs(const secp256k1_context *ctx, struct group *group,
|
||||
struct shared_key *shared, struct signing_session *session,
|
||||
unsigned int first, const unsigned char *msg32,
|
||||
secp256k1_musig_partial_sig *group_partial_sig) {
|
||||
secp256k1_iceberg_partial_sig sig_share[T];
|
||||
const secp256k1_iceberg_partial_sig *sig_share_ptr[T];
|
||||
unsigned int k;
|
||||
|
||||
/* Note what this takes: the group's own round-one contributions, not an
|
||||
* aggregate of them, and not the cosigners', which arrive separately and
|
||||
* already aggregated. Do not read that as an optimization waiting to happen.
|
||||
* The nesting coefficient is a hash of the group's aggregate nonce, so a
|
||||
* coordinator free to invent that aggregate has a coefficient it can vary at
|
||||
* will, and three invented aggregates under one label give three equations in
|
||||
* the same three unknowns, and the third is the key. So the aggregate is
|
||||
* derived here from the contributions instead. */
|
||||
step("Signature shares (secp256k1_iceberg_partial_sign)");
|
||||
for (k = 0; k < T; k++) {
|
||||
if (!may_sign_under(&group->member[first + k], &group->disk[first + k], session->sid)) {
|
||||
printf("refused: participant %d has already answered under this label\n",
|
||||
first + k + 1);
|
||||
return 0;
|
||||
}
|
||||
/* The signer derives the aggregate from the contributions, then checks
|
||||
* the polynomial they determine against the contribution it derives for
|
||||
* itself, which ties the set to this label. It need not have been one of
|
||||
* the contributors. */
|
||||
if (!secp256k1_iceberg_partial_sign(ctx, &sig_share[k], &group->member[first + k].share, NULL,
|
||||
session->sid, session->contribution_ptr, MU,
|
||||
&group->pubkey, &shared->keyagg_cache,
|
||||
msg32, &session->cosigner_aggnonce)) {
|
||||
return 0;
|
||||
}
|
||||
sig_share_ptr[k] = &sig_share[k];
|
||||
}
|
||||
printf("ok, from participants %d, %d and %d\n", first + 1, first + 2, first + 3);
|
||||
|
||||
/* Optional, and the coordinator's to decide on. Skipping it costs nothing
|
||||
* until a share is bad, at which point the final signature simply fails
|
||||
* BIP-340 and says nothing about which of the three caused it. Checking
|
||||
* costs about what producing a share costs, per share.
|
||||
*
|
||||
* That is with exactly t shares, which is what this collects. Hand
|
||||
* secp256k1_iceberg_partial_sig_agg one more than it needs and it refuses a
|
||||
* set that disagrees with itself, cheaply and without naming anybody, for
|
||||
* the same reason pubkey_agg can above: past the threshold there is a spare
|
||||
* point to check against.
|
||||
*
|
||||
* It answers "is this share bad", not "who is lying". A share that fails
|
||||
* here may have been written by somebody other than the member it names,
|
||||
* and the same 0 comes back if this machine has the wrong message. */
|
||||
step("Checking them (secp256k1_iceberg_partial_sig_verify)");
|
||||
for (k = 0; k < T; k++) {
|
||||
if (!secp256k1_iceberg_partial_sig_verify(ctx, &sig_share[k], &group->pubshare[first + k],
|
||||
session->contribution_ptr, MU, N, T,
|
||||
&group->pubkey, &shared->keyagg_cache, msg32,
|
||||
&session->cosigner_aggnonce)) {
|
||||
printf("share from participant %d does not check out\n", first + k + 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
printf("ok, all %d\n", T);
|
||||
|
||||
step("Combining (secp256k1_iceberg_partial_sig_agg)");
|
||||
if (!secp256k1_iceberg_partial_sig_agg(ctx, group_partial_sig, sig_share_ptr, T, N, T)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The cosigner's half, which happens exactly once.
|
||||
*
|
||||
* secp256k1_musig_partial_sign consumes the secret nonce, zeroing it on
|
||||
* the way out, precisely so that signing twice with it is not something a
|
||||
* caller can do by accident. Which quorum the group fielded is not the
|
||||
* cosigner's business and does not change its answer. */
|
||||
static int cosigner_signs(const secp256k1_context *ctx, struct cosigner *cosigner,
|
||||
struct shared_key *shared, struct signing_session *session,
|
||||
secp256k1_musig_partial_sig *cosigner_partial_sig) {
|
||||
step("Cosigner's share (secp256k1_musig_partial_sign)");
|
||||
if (!secp256k1_musig_partial_sign(ctx, cosigner_partial_sig, &cosigner->secnonce,
|
||||
&cosigner->keypair, &shared->keyagg_cache,
|
||||
&session->musig_session)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* One group share plus one cosigner share makes an ordinary MuSig2 signature. */
|
||||
static int combine(const secp256k1_context *ctx, struct signing_session *session,
|
||||
const secp256k1_musig_partial_sig *group_partial_sig,
|
||||
const secp256k1_musig_partial_sig *cosigner_partial_sig,
|
||||
unsigned char *sig64) {
|
||||
const secp256k1_musig_partial_sig *both[2];
|
||||
|
||||
step("Final signature (secp256k1_musig_partial_sig_agg)");
|
||||
both[0] = group_partial_sig;
|
||||
both[1] = cosigner_partial_sig;
|
||||
if (!secp256k1_musig_partial_sig_agg(ctx, sig64, &session->musig_session, both, 2)) {
|
||||
return 0;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* One call that must be refused and one that must not, with the reason beside
|
||||
* each.
|
||||
*
|
||||
* The refusal below is a load-bearing security check that looks like a bug from
|
||||
* outside; the call after it is the case that looks refusable and must not
|
||||
* be. */
|
||||
static int refusals_are_refused(const secp256k1_context *ctx, struct group *group,
|
||||
struct shared_key *shared, struct signing_session *session,
|
||||
const unsigned char *msg32) {
|
||||
secp256k1_iceberg_partial_sig sig_share;
|
||||
|
||||
step("Signing over contributions from another session");
|
||||
{
|
||||
/* The contributions below are a real sharing, internally consistent
|
||||
* and passing the degree check, but they were produced under a
|
||||
* different label. Accepting them would give whoever supplied them a
|
||||
* coefficient in the signing equation that this participant cannot
|
||||
* check, which is worth a signature share to an adversary and nothing
|
||||
* to anybody else.
|
||||
*
|
||||
* The signer catches it by interpolating the contributions and
|
||||
* evaluating the result at its own index: the polynomial does not pass
|
||||
* through the contribution it derives locally for the label it is
|
||||
* signing under. */
|
||||
secp256k1_iceberg_pubnonce elsewhere[MU];
|
||||
const secp256k1_iceberg_pubnonce *elsewhere_ptrs[MU];
|
||||
unsigned char other_sid[32];
|
||||
unsigned int k;
|
||||
|
||||
memcpy(other_sid, session->sid, sizeof(other_sid));
|
||||
other_sid[0] ^= 1;
|
||||
for (k = 0; k < MU; k++) {
|
||||
if (!secp256k1_iceberg_nonce_gen(ctx, &elsewhere[k], &group->member[k].share,
|
||||
NULL, other_sid)) {
|
||||
return 0;
|
||||
}
|
||||
elsewhere_ptrs[k] = &elsewhere[k];
|
||||
}
|
||||
/* The set is a perfectly good sharing. What it is not is a sharing of
|
||||
* *this* label, and only the signer can tell, because only the signer
|
||||
* holds the share that says what its own contribution should have
|
||||
* been. */
|
||||
if (secp256k1_iceberg_partial_sign(ctx, &sig_share, &group->member[0].share, NULL,
|
||||
session->sid, elsewhere_ptrs, MU, &group->pubkey,
|
||||
&shared->keyagg_cache, msg32,
|
||||
&session->cosigner_aggnonce)) {
|
||||
printf("FAILED: it signed, and it should not have\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
printf("refused, correctly\n");
|
||||
|
||||
/* The other half of that check is a thing it must NOT cost. */
|
||||
step("Signing by a member who sat round one out");
|
||||
{
|
||||
/* Participants 6 and 7 were not among the 2t-1 who produced nonces.
|
||||
* They can still sign, because a contribution is a function of the
|
||||
* share and the label, so an absent member can derive what its
|
||||
* contribution would have been and check the polynomial against it
|
||||
* without ever having been one of the contributors.
|
||||
*
|
||||
* This is the property that lets a quorum change between the rounds,
|
||||
* which is most of why the scheme works this way: keys sit in
|
||||
* cold storage and members are routinely absent rather than hostile. */
|
||||
if (!secp256k1_iceberg_partial_sign(ctx, &sig_share, &group->member[N - 1].share, NULL,
|
||||
session->sid, session->contribution_ptr, MU,
|
||||
&group->pubkey, &shared->keyagg_cache, msg32,
|
||||
&session->cosigner_aggnonce)) {
|
||||
printf("FAILED: it refused, and it should not have\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
printf("signed, correctly\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void print_caveats(void) {
|
||||
heading("WHAT THIS EXAMPLE IS NOT");
|
||||
printf(" A trusted dealer deals the shares. Real deployments want a\n");
|
||||
printf(" distributed key generation, which this module does not provide.\n");
|
||||
printf("\n");
|
||||
printf(" Everything is passed as structs in one process. The wire formats\n");
|
||||
printf(" exist: 34 bytes for a public share, 67 for a nonce contribution,\n");
|
||||
printf(" 33 for a signature share. Nothing here uses them, so the\n");
|
||||
printf(" network is the part you still have to write.\n");
|
||||
printf("\n");
|
||||
printf(" Structs in one process also satisfy constraint 2 for free. Over a\n");
|
||||
printf(" network the channel must be authenticated and a contribution taken\n");
|
||||
printf(" only from the member its index names; without that, an adversary\n");
|
||||
printf(" supplying t of the 2t-1 picks the group's nonce and every check\n");
|
||||
printf(" here still passes.\n");
|
||||
printf("\n");
|
||||
printf(" Verifying a share tells you whether it satisfies the equation,\n");
|
||||
printf(" not who is at fault: partial signatures are forgeable, so a share\n");
|
||||
printf(" that fails may have been written by somebody else.\n");
|
||||
printf("\n");
|
||||
printf(" may_sign_under above is half the rule, and the easy half: it\n");
|
||||
printf(" stops one participant answering twice. Nothing anywhere stops two\n");
|
||||
printf(" participants answering one label on different messages. Two answers\n");
|
||||
printf(" under one label are two equations in a participant's three\n");
|
||||
printf(" secrets; three recover its key share.\n");
|
||||
printf("\n");
|
||||
printf(" The label is an argument, rather than something derived from the\n");
|
||||
printf(" message, because round one has to run before the message exists.\n");
|
||||
printf(" In Lightning the nonce is fixed a round-trip before the\n");
|
||||
printf(" transaction is assembled, and the commitment number is what\n");
|
||||
printf(" Lightning supplies instead.\n");
|
||||
printf("\n");
|
||||
printf(" So half of that discipline is yours, never answering twice under\n");
|
||||
printf(" one label, and half is the group's: agree which message a label\n");
|
||||
printf(" belongs to before anyone answers. The unforgeability proof, which\n");
|
||||
printf(" is not yet peer-reviewed, assumes both. See doc/iceberg.md.\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
secp256k1_context *ctx;
|
||||
struct group group;
|
||||
struct cosigner cosigner;
|
||||
struct shared_key shared;
|
||||
struct signing_session session;
|
||||
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
|
||||
secp256k1_musig_partial_sig group_partial_sig, cosigner_partial_sig;
|
||||
unsigned char sig[64], sig_from_the_others[64];
|
||||
unsigned int k;
|
||||
int ok;
|
||||
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
|
||||
printf("Iceberg: a %d-of-%d group signing as one MuSig2 participant.\n\n", T, N);
|
||||
printf(" round one needs %d of the %d participants; round two needs %d\n", MU, N, MU);
|
||||
printf(" online again, not necessarily the same ones, and combines %d\n", T);
|
||||
printf(" signature shares. See doc/iceberg.md for why those differ.\n");
|
||||
|
||||
heading("Setup: once, by a dealer who is then not needed again");
|
||||
ok = deal_shares(ctx, &group);
|
||||
|
||||
if (ok) {
|
||||
heading("Key aggregation: the group is now just a public key");
|
||||
ok = aggregate_keys(ctx, &group, &cosigner, &shared);
|
||||
}
|
||||
if (ok) {
|
||||
/* The two halves are written in this order because something has to
|
||||
* go first on the page. Swap the two calls and the example still
|
||||
* passes, since neither needs anything the other produces, which is why
|
||||
* two Iceberg groups can sign with each other. */
|
||||
heading("Round one: needs 2t-1 participants, in no particular order");
|
||||
ok = group_round_one(ctx, &group, &session)
|
||||
&& cosigner_round_one(ctx, &cosigner, &shared, &session, msg)
|
||||
&& open_session(ctx, &shared, &session, msg);
|
||||
}
|
||||
if (ok) {
|
||||
heading("The gap: every participant forgets its secret nonces");
|
||||
ok = reboot_participants(ctx, &group);
|
||||
}
|
||||
if (ok) {
|
||||
heading("Round two: t shares, from any members, not just round one's");
|
||||
ok = group_signs(ctx, &group, &shared, &session, 0, msg, &group_partial_sig)
|
||||
&& cosigner_signs(ctx, &cosigner, &shared, &session, &cosigner_partial_sig)
|
||||
&& combine(ctx, &session, &group_partial_sig, &cosigner_partial_sig, sig);
|
||||
}
|
||||
if (ok) {
|
||||
step("Verifying (secp256k1_schnorrsig_verify)");
|
||||
ok = secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &shared.output);
|
||||
if (ok) {
|
||||
printf("ok\n ");
|
||||
print_hex(sig, sizeof(sig));
|
||||
printf(" An ordinary BIP-340 signature. Nothing in it records a group.\n");
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
heading("The same signature, from different people");
|
||||
/* Participants 4, 5 and 6 this time, with no overlap with 1, 2 and 3, and
|
||||
* 6 was not in round one either. Both of those matter. Disjoint,
|
||||
* because nobody may answer twice under one label. The library holds
|
||||
* nothing between calls and cannot detect that, so may_sign_under above
|
||||
* is what refuses it. And 6 absent from round one, because a member that
|
||||
* was away can still work out what its contribution would have been.
|
||||
*
|
||||
* Only the group signs again. The cosigner's share is reused, since it
|
||||
* has one secret nonce and spending it twice would be nonce reuse. */
|
||||
ok = group_signs(ctx, &group, &shared, &session, T, msg, &group_partial_sig)
|
||||
&& combine(ctx, &session, &group_partial_sig, &cosigner_partial_sig, sig_from_the_others);
|
||||
}
|
||||
if (ok) {
|
||||
step("Comparing the two signatures");
|
||||
ok = memcmp(sig, sig_from_the_others, sizeof(sig)) == 0;
|
||||
printf(ok ? "byte-identical\n" : "the two quorums disagreed\n");
|
||||
}
|
||||
if (ok) {
|
||||
heading("What the module checks, and what it leaves to you");
|
||||
ok = refusals_are_refused(ctx, &group, &shared, &session, msg);
|
||||
}
|
||||
if (!ok) {
|
||||
printf("FAILED\n");
|
||||
}
|
||||
print_caveats();
|
||||
|
||||
/* Clear the secrets: a bug elsewhere that leaks memory, or an OS that swaps
|
||||
* it to disk, should not find them lying around. The group's shares are the
|
||||
* long-term secret here, and the cosigner's keypair and secret nonce are
|
||||
* the rest. */
|
||||
for (k = 0; k < N; k++) {
|
||||
secure_erase(&group.member[k], sizeof(group.member[k]));
|
||||
secure_erase(&group.disk[k], sizeof(group.disk[k]));
|
||||
}
|
||||
secure_erase(&cosigner, sizeof(cosigner));
|
||||
secp256k1_context_destroy(ctx);
|
||||
return ok ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
261
examples/musig.c
261
examples/musig.c
@@ -1,261 +0,0 @@
|
||||
/*************************************************************************
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
/** This file demonstrates how to use the MuSig module to create a
|
||||
* 3-of-3 multisignature. Additionally, see the documentation in
|
||||
* include/secp256k1_musig.h and doc/musig.md.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_extrakeys.h>
|
||||
#include <secp256k1_musig.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
struct signer_secrets {
|
||||
secp256k1_keypair keypair;
|
||||
secp256k1_musig_secnonce secnonce;
|
||||
};
|
||||
|
||||
struct signer {
|
||||
secp256k1_pubkey pubkey;
|
||||
secp256k1_musig_pubnonce pubnonce;
|
||||
secp256k1_musig_partial_sig partial_sig;
|
||||
};
|
||||
|
||||
/* Number of public keys involved in creating the aggregate signature */
|
||||
#define N_SIGNERS 3
|
||||
/* Create a key pair, store it in signer_secrets->keypair and signer->pubkey */
|
||||
static int create_keypair(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer) {
|
||||
unsigned char seckey[32];
|
||||
|
||||
if (!fill_random(seckey, sizeof(seckey))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return 0;
|
||||
}
|
||||
/* Try to create a keypair with a valid context. This only fails if the
|
||||
* secret key is zero or out of range (greater than secp256k1's order). Note
|
||||
* that the probability of this occurring is negligible with a properly
|
||||
* functioning random number generator. */
|
||||
if (!secp256k1_keypair_create(ctx, &signer_secrets->keypair, seckey)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_keypair_pub(ctx, &signer->pubkey, &signer_secrets->keypair)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secure_erase(seckey, sizeof(seckey));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Tweak the pubkey corresponding to the provided keyagg cache, update the cache
|
||||
* and return the tweaked aggregate pk. */
|
||||
static int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *agg_pk, secp256k1_musig_keyagg_cache *cache) {
|
||||
secp256k1_pubkey output_pk;
|
||||
/* For BIP 32 tweaking the plain_tweak is set to a hash as defined in BIP
|
||||
* 32. */
|
||||
unsigned char plain_tweak[32] = "this could be a BIP32 tweak....";
|
||||
/* For Taproot tweaking the xonly_tweak is set to the TapTweak hash as
|
||||
* defined in BIP 341 */
|
||||
unsigned char xonly_tweak[32] = "this could be a Taproot tweak..";
|
||||
|
||||
|
||||
/* Plain tweaking which, for example, allows deriving multiple child
|
||||
* public keys from a single aggregate key using BIP32 */
|
||||
if (!secp256k1_musig_pubkey_ec_tweak_add(ctx, NULL, cache, plain_tweak)) {
|
||||
return 0;
|
||||
}
|
||||
/* Note that we did not provide an output_pk argument, because the
|
||||
* resulting pk is also saved in the cache and so if one is just interested
|
||||
* in signing, the output_pk argument is unnecessary. On the other hand, if
|
||||
* one is not interested in signing, the same output_pk can be obtained by
|
||||
* calling `secp256k1_musig_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_musig_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, agg_pk, NULL, &output_pk)) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Sign a message hash with the given key pairs and store the result in sig */
|
||||
static int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const secp256k1_musig_keyagg_cache *cache, const unsigned char *msg32, unsigned char *sig64) {
|
||||
int i;
|
||||
const secp256k1_musig_pubnonce *pubnonces[N_SIGNERS];
|
||||
const secp256k1_musig_partial_sig *partial_sigs[N_SIGNERS];
|
||||
/* The same for all signers */
|
||||
secp256k1_musig_session session;
|
||||
secp256k1_musig_aggnonce agg_pubnonce;
|
||||
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
unsigned char seckey[32];
|
||||
unsigned char session_secrand[32];
|
||||
/* Create random session ID. It is absolutely necessary that the session ID
|
||||
* is unique for every call of secp256k1_musig_nonce_gen. Otherwise
|
||||
* it's trivial for an attacker to extract the secret key! */
|
||||
if (!fill_random(session_secrand, sizeof(session_secrand))) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_keypair_sec(ctx, seckey, &signer_secrets[i].keypair)) {
|
||||
return 0;
|
||||
}
|
||||
/* Initialize session and create secret nonce for signing and public
|
||||
* nonce to send to the other signers. */
|
||||
if (!secp256k1_musig_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_secrand, seckey, &signer[i].pubkey, msg32, NULL, NULL)) {
|
||||
return 0;
|
||||
}
|
||||
pubnonces[i] = &signer[i].pubnonce;
|
||||
|
||||
secure_erase(seckey, sizeof(seckey));
|
||||
}
|
||||
|
||||
/* Communication round 1: Every signer sends their pubnonce to the
|
||||
* coordinator. The coordinator runs secp256k1_musig_nonce_agg and sends
|
||||
* agg_pubnonce to each signer */
|
||||
if (!secp256k1_musig_nonce_agg(ctx, &agg_pubnonce, pubnonces, N_SIGNERS)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Every signer creates a partial signature */
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
/* Initialize the signing session by processing the aggregate nonce */
|
||||
if (!secp256k1_musig_nonce_process(ctx, &session, &agg_pubnonce, msg32, 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_secrand to
|
||||
* create a secnonce). If you do, you effectively reuse the nonce and
|
||||
* leak the secret key. */
|
||||
if (!secp256k1_musig_partial_sign(ctx, &signer[i].partial_sig, &signer_secrets[i].secnonce, &signer_secrets[i].keypair, cache, &session)) {
|
||||
return 0;
|
||||
}
|
||||
partial_sigs[i] = &signer[i].partial_sig;
|
||||
}
|
||||
/* Communication round 2: Every signer sends their partial signature to the
|
||||
* coordinator, who verifies the partial signatures and aggregates them. */
|
||||
for (i = 0; i < N_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_musig_partial_sig_verify(ctx, &signer[i].partial_sig, &signer[i].pubnonce, &signer[i].pubkey, cache, &session)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return secp256k1_musig_partial_sig_agg(ctx, sig64, &session, partial_sigs, N_SIGNERS);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
secp256k1_context* ctx;
|
||||
int i;
|
||||
struct signer_secrets signer_secrets[N_SIGNERS];
|
||||
struct signer signers[N_SIGNERS];
|
||||
const secp256k1_pubkey *pubkeys_ptr[N_SIGNERS];
|
||||
secp256k1_xonly_pubkey agg_pk;
|
||||
secp256k1_musig_keyagg_cache cache;
|
||||
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg";
|
||||
unsigned char sig[64];
|
||||
|
||||
/* Create a secp256k1 context */
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
printf("Creating key pairs......");
|
||||
fflush(stdout);
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
if (!create_keypair(ctx, &signer_secrets[i], &signers[i])) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
pubkeys_ptr[i] = &signers[i].pubkey;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* The aggregate public key produced by secp256k1_musig_pubkey_agg depends
|
||||
* on the order of the provided public keys. If there is no canonical order
|
||||
* of the signers, the individual public keys can optionally be sorted with
|
||||
* secp256k1_ec_pubkey_sort to ensure that the aggregate public key is
|
||||
* independent of the order of signers. */
|
||||
printf("Sorting public keys.....");
|
||||
fflush(stdout);
|
||||
if (!secp256k1_ec_pubkey_sort(ctx, pubkeys_ptr, N_SIGNERS)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
printf("Combining public keys...");
|
||||
fflush(stdout);
|
||||
/* If you just want to aggregate and not sign, you can call
|
||||
* secp256k1_musig_pubkey_agg with the keyagg_cache argument set to NULL
|
||||
* while providing a non-NULL agg_pk argument. */
|
||||
if (!secp256k1_musig_pubkey_agg(ctx, NULL, &cache, pubkeys_ptr, N_SIGNERS)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
printf("Tweaking................");
|
||||
fflush(stdout);
|
||||
/* Optionally tweak the aggregate key */
|
||||
if (!tweak(ctx, &agg_pk, &cache)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
printf("Signing message.........");
|
||||
fflush(stdout);
|
||||
if (!sign(ctx, signer_secrets, signers, &cache, msg, sig)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
printf("Verifying signature.....");
|
||||
fflush(stdout);
|
||||
if (!secp256k1_schnorrsig_verify(ctx, sig, msg, 32, &agg_pk)) {
|
||||
printf("FAILED\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
printf("ok\n");
|
||||
|
||||
/* It's best practice to try to clear secrets from memory after using them.
|
||||
* This is done because some bugs can allow an attacker to leak memory, for
|
||||
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||||
* swapping them to disk. Hence, we overwrite secret key material with zeros.
|
||||
*
|
||||
* Here we are preventing these writes from being optimized out, as any good compiler
|
||||
* will remove any writes that aren't used. */
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
secure_erase(&signer_secrets[i], sizeof(signer_secrets[i]));
|
||||
}
|
||||
secp256k1_context_destroy(ctx);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*************************************************************************
|
||||
* Written in 2020-2022 by Elichai Turkel *
|
||||
* To the extent possible under law, the author(s) have dedicated all *
|
||||
* copyright and related and neighboring rights to the software in this *
|
||||
* file to the public domain worldwide. This software is distributed *
|
||||
* without any warranty. For the CC0 Public Domain Dedication, see *
|
||||
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
|
||||
*************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_extrakeys.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
|
||||
#include "examples_util.h"
|
||||
|
||||
int main(void) {
|
||||
unsigned char msg[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'};
|
||||
unsigned char msg_hash[32];
|
||||
unsigned char tag[] = {'m', 'y', '_', 'f', 'a', 'n', 'c', 'y', '_', 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l'};
|
||||
unsigned char seckey[32];
|
||||
unsigned char randomize[32];
|
||||
unsigned char auxiliary_rand[32];
|
||||
unsigned char serialized_pubkey[32];
|
||||
unsigned char signature[64];
|
||||
int is_signature_valid, is_signature_valid2;
|
||||
int return_val;
|
||||
secp256k1_xonly_pubkey pubkey;
|
||||
secp256k1_keypair keypair;
|
||||
/* Before we can call actual API functions, we need to create a "context". */
|
||||
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
if (!fill_random(randomize, sizeof(randomize))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* Randomizing the context is recommended to protect against side-channel
|
||||
* leakage See `secp256k1_context_randomize` in secp256k1.h for more
|
||||
* information about it. This should never fail. */
|
||||
return_val = secp256k1_context_randomize(ctx, randomize);
|
||||
assert(return_val);
|
||||
|
||||
/*** Key Generation ***/
|
||||
if (!fill_random(seckey, sizeof(seckey))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
/* Try to create a keypair with a valid context. This only fails if the
|
||||
* secret key is zero or out of range (greater than secp256k1's order). Note
|
||||
* that the probability of this occurring is negligible with a properly
|
||||
* functioning random number generator. */
|
||||
if (!secp256k1_keypair_create(ctx, &keypair, seckey)) {
|
||||
printf("Generated secret key is invalid. This indicates an issue with the random number generator.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Extract the X-only public key from the keypair. We pass NULL for
|
||||
* `pk_parity` as the parity isn't needed for signing or verification.
|
||||
* `secp256k1_keypair_xonly_pub` supports returning the parity for
|
||||
* other use cases such as tests or verifying Taproot tweaks.
|
||||
* This should never fail with a valid context and public key. */
|
||||
return_val = secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, &keypair);
|
||||
assert(return_val);
|
||||
|
||||
/* Serialize the public key. Should always return 1 for a valid public key. */
|
||||
return_val = secp256k1_xonly_pubkey_serialize(ctx, serialized_pubkey, &pubkey);
|
||||
assert(return_val);
|
||||
|
||||
/*** Signing ***/
|
||||
|
||||
/* Instead of signing (possibly very long) messages directly, we sign a
|
||||
* 32-byte hash of the message in this example.
|
||||
*
|
||||
* We use secp256k1_tagged_sha256 to create this hash. This function expects
|
||||
* a context-specific "tag", which restricts the context in which the signed
|
||||
* messages should be considered valid. For example, if protocol A mandates
|
||||
* to use the tag "my_fancy_protocol" and protocol B mandates to use the tag
|
||||
* "my_boring_protocol", then signed messages from protocol A will never be
|
||||
* valid in protocol B (and vice versa), even if keys are reused across
|
||||
* protocols. This implements "domain separation", which is considered good
|
||||
* practice. It avoids attacks in which users are tricked into signing a
|
||||
* message that has intended consequences in the intended context (e.g.,
|
||||
* protocol A) but would have unintended consequences if it were valid in
|
||||
* some other context (e.g., protocol B). */
|
||||
return_val = secp256k1_tagged_sha256(ctx, msg_hash, tag, sizeof(tag), msg, sizeof(msg));
|
||||
assert(return_val);
|
||||
|
||||
/* Generate 32 bytes of randomness to use with BIP-340 schnorr signing. */
|
||||
if (!fill_random(auxiliary_rand, sizeof(auxiliary_rand))) {
|
||||
printf("Failed to generate randomness\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Generate a Schnorr signature.
|
||||
*
|
||||
* We use the secp256k1_schnorrsig_sign32 function that provides a simple
|
||||
* interface for signing 32-byte messages (which in our case is a hash of
|
||||
* the actual message). BIP-340 recommends passing 32 bytes of randomness
|
||||
* to the signing function to improve security against side-channel attacks.
|
||||
* Signing with a valid context, a 32-byte message, a verified keypair, and
|
||||
* any 32 bytes of auxiliary random data should never fail. */
|
||||
return_val = secp256k1_schnorrsig_sign32(ctx, signature, msg_hash, &keypair, auxiliary_rand);
|
||||
assert(return_val);
|
||||
|
||||
/*** Verification ***/
|
||||
|
||||
/* Deserialize the public key. This will return 0 if the public key can't
|
||||
* be parsed correctly */
|
||||
if (!secp256k1_xonly_pubkey_parse(ctx, &pubkey, serialized_pubkey)) {
|
||||
printf("Failed parsing the public key\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* Compute the tagged hash on the received messages using the same tag as the signer. */
|
||||
return_val = secp256k1_tagged_sha256(ctx, msg_hash, tag, sizeof(tag), msg, sizeof(msg));
|
||||
assert(return_val);
|
||||
|
||||
/* Verify a signature. This will return 1 if it's valid and 0 if it's not. */
|
||||
is_signature_valid = secp256k1_schnorrsig_verify(ctx, signature, msg_hash, 32, &pubkey);
|
||||
|
||||
|
||||
printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false");
|
||||
printf("Secret Key: ");
|
||||
print_hex(seckey, sizeof(seckey));
|
||||
printf("Public Key: ");
|
||||
print_hex(serialized_pubkey, sizeof(serialized_pubkey));
|
||||
printf("Signature: ");
|
||||
print_hex(signature, sizeof(signature));
|
||||
|
||||
/* This will clear everything from the context and free the memory */
|
||||
secp256k1_context_destroy(ctx);
|
||||
|
||||
/* Bonus example: if all we need is signature verification (and no key
|
||||
generation or signing), we don't need to use a context created via
|
||||
secp256k1_context_create(). We can simply use the static (i.e., global)
|
||||
context secp256k1_context_static. See its description in
|
||||
include/secp256k1.h for details. */
|
||||
is_signature_valid2 = secp256k1_schnorrsig_verify(secp256k1_context_static,
|
||||
signature, msg_hash, 32, &pubkey);
|
||||
assert(is_signature_valid2 == is_signature_valid);
|
||||
|
||||
/* It's best practice to try to clear secrets from memory after using them.
|
||||
* This is done because some bugs can allow an attacker to leak memory, for
|
||||
* example through "out of bounds" array access (see Heartbleed), or the OS
|
||||
* swapping them to disk. Hence, we overwrite the secret key buffer with zeros.
|
||||
*
|
||||
* Here we are preventing these writes from being optimized out, as any good compiler
|
||||
* will remove any writes that aren't used. */
|
||||
secure_erase(seckey, sizeof(seckey));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
#ifndef SECP256K1_BPPP_H
|
||||
# define SECP256K1_BPPP_H
|
||||
|
||||
# include "secp256k1.h"
|
||||
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** Opaque structure representing a large number of NUMS generators */
|
||||
typedef struct secp256k1_bppp_generators secp256k1_bppp_generators;
|
||||
|
||||
/** Allocates and initializes a list of NUMS generators.
|
||||
* Returns a list of generators, or calls the error callback if the allocation fails.
|
||||
* Args: ctx: pointer to a context object
|
||||
* n: number of NUMS generators to produce.
|
||||
*
|
||||
* TODO: In a followup range-proof PR, this is would still require 16 + 8 = 24 NUMS
|
||||
* points. We will later use G = H0(required for compatibility with pedersen_commitment DS)
|
||||
* in a separate commit to make review easier.
|
||||
*/
|
||||
SECP256K1_API secp256k1_bppp_generators *secp256k1_bppp_generators_create(
|
||||
const secp256k1_context *ctx,
|
||||
size_t n
|
||||
) SECP256K1_ARG_NONNULL(1);
|
||||
|
||||
/** Allocates a list of generators from a static array
|
||||
* Returns a list of generators or NULL in case of failure.
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: data: data that came from `secp256k1_bppp_generators_serialize`
|
||||
* data_len: the length of the `data` buffer
|
||||
*/
|
||||
SECP256K1_API secp256k1_bppp_generators *secp256k1_bppp_generators_parse(
|
||||
const secp256k1_context *ctx,
|
||||
const unsigned char *data,
|
||||
size_t data_len
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
|
||||
|
||||
/** Serializes a list of generators to an array
|
||||
* Returns 1 on success, 0 if the provided array was not large enough
|
||||
* Args: ctx: pointer to a context object
|
||||
* gen: pointer to the generator set to be serialized
|
||||
* Out: data: pointer to buffer into which the generators will be serialized
|
||||
* In/Out: data_len: the length of the `data` buffer. Should be at least
|
||||
* k = 33 * num_gens. Will be set to k on successful return
|
||||
*
|
||||
* TODO: For ease of review, this setting G = H0 is not included in this commit. We will
|
||||
* add it in the follow-up rangeproof PR.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_bppp_generators_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_bppp_generators *gen,
|
||||
unsigned char *data,
|
||||
size_t *data_len
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Destroys a list of NUMS generators, freeing allocated memory
|
||||
* Args: ctx: pointer to a context object
|
||||
* gen: pointer to the generator set to be destroyed
|
||||
* (can be NULL, in which case this function is a no-op)
|
||||
*/
|
||||
SECP256K1_API void secp256k1_bppp_generators_destroy(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_bppp_generators *gen
|
||||
) SECP256K1_ARG_NONNULL(1);
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
#endif
|
||||
@@ -1,675 +0,0 @@
|
||||
#ifndef SECP256K1_CHILLDKG_H
|
||||
#define SECP256K1_CHILLDKG_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
#include "secp256k1_extrakeys.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** This module implements ChillDKG, a distributed key generation (DKG)
|
||||
* protocol for FROST, as specified by the bip-frost-dkg BIP draft
|
||||
* (https://github.com/BlockstreamResearch/bip-frost-dkg, version 0.3.0-dev).
|
||||
*
|
||||
* 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!
|
||||
*
|
||||
* Moreover, the bip-frost-dkg BIP is still a draft: tagged hashes, wire
|
||||
* formats, and protocol details may change in future BIP versions. There is
|
||||
* no guarantee that this implementation will remain compatible with the
|
||||
* final specification.
|
||||
*
|
||||
* The output of a ChillDKG session (a secret share, the threshold public
|
||||
* key, and the public shares of all participants) is designed to be used
|
||||
* directly with the FROST signing module (see include/secp256k1_frost.h).
|
||||
*
|
||||
* A DKG session involves n participants (identified by uint32 identifiers
|
||||
* 0..n-1) and an untrusted coordinator. The number of participants n must
|
||||
* not exceed SECP256K1_CHILLDKG_MAX_PARTICIPANTS. The message flow is:
|
||||
* 1. Every participant runs secp256k1_chilldkg_participant_step1 and sends
|
||||
* the resulting pmsg1 to the coordinator.
|
||||
* 2. The coordinator runs secp256k1_chilldkg_coordinator_step1 on all
|
||||
* pmsg1s and broadcasts the resulting cmsg1 to all participants.
|
||||
* 3. Every participant runs secp256k1_chilldkg_participant_step2 and sends
|
||||
* the resulting signature (pmsg2) to the coordinator.
|
||||
* 4. The coordinator runs secp256k1_chilldkg_coordinator_finalize on all
|
||||
* pmsg2s and broadcasts the resulting certificate (cmsg2) to all
|
||||
* participants.
|
||||
* 5. Every participant runs secp256k1_chilldkg_participant_finalize to
|
||||
* obtain the DKG output and the recovery data.
|
||||
*
|
||||
* It is recommended to read the documentation in this include file carefully.
|
||||
* Further notes on API usage can be found in src/modules/chilldkg/chilldkg.md.
|
||||
*/
|
||||
|
||||
/** The maximum number of participants n in a ChillDKG session. The state
|
||||
* objects of this module are fixed-size and do not use dynamic allocation,
|
||||
* so a compile-time cap is required. This matches the FROST module's
|
||||
* SECP256K1_FROST_MAX_PARTICIPANTS. */
|
||||
#define SECP256K1_CHILLDKG_MAX_PARTICIPANTS 128
|
||||
|
||||
/** Fault report of the ChillDKG protocol functions, mapping the exception
|
||||
* taxonomy of the reference implementation.
|
||||
*
|
||||
* For SECP256K1_CHILLDKG_FAULTY_PARTICIPANT and
|
||||
* SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR, the fault_index
|
||||
* output of the failing function is set to the identifier of the (suspected)
|
||||
* faulty participant. For the other fault codes, fault_index is set to
|
||||
* UINT32_MAX, except where documented otherwise. */
|
||||
typedef enum {
|
||||
/** No fault; the step succeeded. */
|
||||
SECP256K1_CHILLDKG_OK = 0,
|
||||
/** The coordinator is faulty. */
|
||||
SECP256K1_CHILLDKG_FAULTY_COORDINATOR = 1,
|
||||
/** The participant with the given fault_index is faulty. */
|
||||
SECP256K1_CHILLDKG_FAULTY_PARTICIPANT = 2,
|
||||
/** The participant with the given fault_index or the coordinator is
|
||||
* faulty. */
|
||||
SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR = 3,
|
||||
/** Some unknown participant or the coordinator is faulty; the
|
||||
* investigation procedure of the protocol is necessary to determine a
|
||||
* suspected participant. */
|
||||
SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR = 4,
|
||||
/** The caller provided invalid input (e.g., an invalid host secret key or
|
||||
* invalid session parameters). */
|
||||
SECP256K1_CHILLDKG_INVALID_INPUT = 5
|
||||
} secp256k1_chilldkg_fault;
|
||||
|
||||
/** Opaque data structures
|
||||
*
|
||||
* The exact representation of data inside the opaque data structures is
|
||||
* implementation defined and not guaranteed to be portable between different
|
||||
* platforms or versions. The data structures can be safely copied/moved.
|
||||
*/
|
||||
|
||||
/** Opaque data structure that holds a participant's session state after
|
||||
* secp256k1_chilldkg_participant_step1.
|
||||
*
|
||||
* The state does not contain secret key material (the secret shares it
|
||||
* relates to are encrypted in pmsg1), but it must not be reused: it must be
|
||||
* passed only to a single secp256k1_chilldkg_participant_step2 call.
|
||||
*
|
||||
* Guaranteed to be 4306 bytes in size.
|
||||
*/
|
||||
typedef struct secp256k1_chilldkg_participant_state1 {
|
||||
unsigned char data[4 + 12 + 33 + 33 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
|
||||
} secp256k1_chilldkg_participant_state1;
|
||||
|
||||
/** Opaque data structure that holds a participant's session state after
|
||||
* secp256k1_chilldkg_participant_step2.
|
||||
*
|
||||
* This structure contains the participant's secret share; it MUST be kept
|
||||
* secret and MUST NOT be copied. It must not be reused: it must be passed
|
||||
* only to a single secp256k1_chilldkg_participant_finalize call.
|
||||
*
|
||||
* Guaranteed to be 21073 bytes in size.
|
||||
*/
|
||||
typedef struct secp256k1_chilldkg_participant_state2 {
|
||||
unsigned char data[12 + 4 + 131 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS + 32 + 33 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
|
||||
} secp256k1_chilldkg_participant_state2;
|
||||
|
||||
/** Opaque data structure that holds the investigation data output by
|
||||
* secp256k1_chilldkg_participant_step2 when it returns
|
||||
* SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR, to be passed
|
||||
* to secp256k1_chilldkg_participant_investigate together with the
|
||||
* coordinator's investigation message.
|
||||
*
|
||||
* This structure contains secret data (the untweaked secret share and the
|
||||
* decryption pads); it MUST be kept secret.
|
||||
*
|
||||
* Guaranteed to be 4205 bytes in size.
|
||||
*/
|
||||
typedef struct secp256k1_chilldkg_participant_inv_data {
|
||||
unsigned char data[12 + 32 + 33 + 32 + 32 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
|
||||
} secp256k1_chilldkg_participant_inv_data;
|
||||
|
||||
/** Opaque data structure that holds the coordinator's session state after
|
||||
* secp256k1_chilldkg_coordinator_step1, to be passed to
|
||||
* secp256k1_chilldkg_coordinator_finalize (it must not be reused).
|
||||
*
|
||||
* This structure contains no secret key material; it can be copied freely
|
||||
* (e.g., to persist it between the two coordinator steps).
|
||||
*
|
||||
* Guaranteed to be 21041 bytes in size.
|
||||
*/
|
||||
typedef struct secp256k1_chilldkg_coordinator_state {
|
||||
unsigned char data[12 + 4 + 131 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS + 33 + 33 * SECP256K1_CHILLDKG_MAX_PARTICIPANTS];
|
||||
} secp256k1_chilldkg_coordinator_state;
|
||||
|
||||
/** Compute the participant's host public key from the host secret key.
|
||||
*
|
||||
* The host public key is the long-term cryptographic identity of the
|
||||
* participant. This function interprets hostseckey32 as a big-endian integer
|
||||
* and computes the corresponding "plain" public key in compressed
|
||||
* serialization (33 bytes, starting with 0x02 or 0x03), equivalent to
|
||||
* IndividualPubkey as defined in BIP 327.
|
||||
*
|
||||
* Returns: 1 on success, 0 if the host secret key is invalid (zero or not
|
||||
* less than the group order). On failure, hostpubkey33 is set to
|
||||
* zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: hostpubkey33: pointer to a 33-byte array to store the host public key
|
||||
* In: hostseckey32: pointer to the 32-byte host secret key
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_chilldkg_hostpubkey_gen(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *hostpubkey33,
|
||||
const unsigned char *hostseckey32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Return a hash of the session parameters for out-of-band comparison.
|
||||
*
|
||||
* If all participants have obtained an identical parameters hash (as can be
|
||||
* verified out of band), then they all agree on all host public keys and the
|
||||
* threshold t.
|
||||
*
|
||||
* Returns: 1 on success, 0 if the session parameters are invalid (not
|
||||
* 1 <= t <= n <= SECP256K1_CHILLDKG_MAX_PARTICIPANTS, an invalid
|
||||
* host public key, or a duplicate host public key). On failure,
|
||||
* hash32 is set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: hash32: pointer to a 32-byte array to store the parameters
|
||||
* hash
|
||||
* In: hostpubkeys33: pointer to an array of n_participants host public
|
||||
* keys (33 bytes each, compressed serialization)
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_chilldkg_params_hash(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *hash32,
|
||||
const unsigned char *hostpubkeys33,
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Length of a participant's first message (pmsg1): 33*t + 32*n + 97 bytes.
|
||||
* Returns 0 if the parameters are out of range. */
|
||||
SECP256K1_API size_t secp256k1_chilldkg_participant_msg1_len(
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
);
|
||||
|
||||
/** Length of the coordinator's first message (cmsg1): 162*n + 33*(t-1) bytes.
|
||||
* Returns 0 if the parameters are out of range. */
|
||||
SECP256K1_API size_t secp256k1_chilldkg_coordinator_msg1_len(
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
);
|
||||
|
||||
/** Length of a participant's second message (pmsg2): 64 bytes. */
|
||||
SECP256K1_API size_t secp256k1_chilldkg_participant_msg2_len(void);
|
||||
|
||||
/** Length of the coordinator's second message (cmsg2, the certificate):
|
||||
* 64*n bytes. Returns 0 if the parameters are out of range. */
|
||||
SECP256K1_API size_t secp256k1_chilldkg_coordinator_msg2_len(
|
||||
size_t n_participants
|
||||
);
|
||||
|
||||
/** Length of the recovery data output by
|
||||
* secp256k1_chilldkg_participant_finalize: 4 + 33*t + 162*n bytes.
|
||||
* Returns 0 if the parameters are out of range. */
|
||||
SECP256K1_API size_t secp256k1_chilldkg_recovery_data_len(
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
);
|
||||
|
||||
/** Perform a participant's first step of a ChillDKG session.
|
||||
*
|
||||
* Returns: 1 on success, 0 on invalid input (invalid host secret key, host
|
||||
* secret key not matching any host public key, invalid session
|
||||
* parameters, or all-zero randomness). On failure, pmsg1 and the
|
||||
* state are set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: state1: pointer to a state1 object to be passed to
|
||||
* secp256k1_chilldkg_participant_step2 (must not be
|
||||
* reused)
|
||||
* pmsg1: pointer to a 33*t + 32*n + 97 byte array (see
|
||||
* secp256k1_chilldkg_participant_msg1_len) to store
|
||||
* the message to be sent to the coordinator
|
||||
* In: hostseckey32: pointer to the 32-byte host secret key
|
||||
* hostpubkeys33: pointer to an array of n host public keys (33 bytes
|
||||
* each); all participants must agree on the order
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
* random32: pointer to 32 bytes of FRESH randomness
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_chilldkg_participant_step1(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_chilldkg_participant_state1 *state1,
|
||||
unsigned char *pmsg1,
|
||||
const unsigned char *hostseckey32,
|
||||
const unsigned char *hostpubkeys33,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
const unsigned char *random32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
/** Perform a participant's second step of a ChillDKG session.
|
||||
*
|
||||
* Verifies the coordinator's first message, computes the DKG output, and
|
||||
* produces the CertEq signature over the session transcript.
|
||||
*
|
||||
* **Warning:** After sending the produced signature to the coordinator, the
|
||||
* caller **must not** erase the hostseckey, even if the coordinator reply
|
||||
* needed for secp256k1_chilldkg_participant_finalize is not received (some
|
||||
* other participant may deem the session successful and use the resulting
|
||||
* threshold public key).
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK on success, otherwise a fault code:
|
||||
* SECP256K1_CHILLDKG_INVALID_INPUT if the host secret key is
|
||||
* invalid or does not match the one used in step 1;
|
||||
* SECP256K1_CHILLDKG_FAULTY_COORDINATOR,
|
||||
* SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR, or
|
||||
* SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR on
|
||||
* protocol faults. On failure, sig64 and the state are set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: state2: pointer to a state2 object to be passed to
|
||||
* secp256k1_chilldkg_participant_finalize (must not be
|
||||
* reused)
|
||||
* sig64: pointer to a 64-byte array to store the CertEq
|
||||
* signature (pmsg2) to be sent to the coordinator
|
||||
* fault_index: pointer to a uint32 that receives the identifier of
|
||||
* the (suspected) faulty participant where applicable,
|
||||
* and UINT32_MAX otherwise
|
||||
* inv_data: pointer to a participant_inv_data object that
|
||||
* receives the investigation data if (and only if) the
|
||||
* return value is
|
||||
* SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR,
|
||||
* or NULL if the caller does not intend to run
|
||||
* secp256k1_chilldkg_participant_investigate
|
||||
* In: state1: pointer to the state1 object output by
|
||||
* secp256k1_chilldkg_participant_step1
|
||||
* hostseckey32: pointer to the 32-byte host secret key (must be the
|
||||
* same as in step 1)
|
||||
* cmsg1: pointer to the coordinator's first message
|
||||
* (162*n + 33*(t-1) bytes, see
|
||||
* secp256k1_chilldkg_coordinator_msg1_len)
|
||||
* aux_rand32: pointer to 32 bytes of auxiliary randomness for the
|
||||
* CertEq signature (see BIP 340)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_participant_step2(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_chilldkg_participant_state2 *state2,
|
||||
unsigned char *sig64,
|
||||
uint32_t *fault_index,
|
||||
secp256k1_chilldkg_participant_inv_data *inv_data,
|
||||
const secp256k1_chilldkg_participant_state1 *state1,
|
||||
const unsigned char *hostseckey32,
|
||||
const unsigned char *cmsg1,
|
||||
const unsigned char *aux_rand32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9);
|
||||
|
||||
/** Perform a participant's final step of a ChillDKG session.
|
||||
*
|
||||
* Re-verifies all n CertEq signatures of the certificate and outputs the
|
||||
* DKG output and the recovery data. If this function returns
|
||||
* SECP256K1_CHILLDKG_OK, this participant deems the DKG session successful.
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK on success,
|
||||
* SECP256K1_CHILLDKG_FAULTY_COORDINATOR if the certificate contains
|
||||
* an invalid signature. As diagnostic information (deviating from
|
||||
* the reference implementation, which does not report it),
|
||||
* fault_index receives the index of the first invalid signature in
|
||||
* the latter case. On failure, all outputs are set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: secshare32: pointer to a 32-byte array to store the (tweaked)
|
||||
* secret share
|
||||
* thresh_pk33: pointer to a 33-byte array to store the threshold
|
||||
* public key (compressed serialization)
|
||||
* pubshares33: pointer to an array of n 33-byte elements to store
|
||||
* the public shares of all participants
|
||||
* recovery: pointer to a 4 + 33*t + 162*n byte array (see
|
||||
* secp256k1_chilldkg_recovery_data_len) to store the
|
||||
* recovery data
|
||||
* fault_index: pointer to a uint32 (see above)
|
||||
* In: state2: pointer to the state2 object output by
|
||||
* secp256k1_chilldkg_participant_step2
|
||||
* cmsg2: pointer to the coordinator's second message (the
|
||||
* certificate, 64*n bytes)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_participant_finalize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *secshare32,
|
||||
unsigned char *thresh_pk33,
|
||||
unsigned char *pubshares33,
|
||||
unsigned char *recovery,
|
||||
uint32_t *fault_index,
|
||||
const secp256k1_chilldkg_participant_state2 *state2,
|
||||
const unsigned char *cmsg2
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
/** Perform the coordinator's first step of a ChillDKG session.
|
||||
*
|
||||
* Parses all n participant messages and aggregates them into the message to
|
||||
* broadcast to all participants. The proofs of possession contained in the
|
||||
* pmsg1s are NOT verified here; the participants verify them in step 2 (this
|
||||
* mirrors the reference implementation).
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK on success,
|
||||
* SECP256K1_CHILLDKG_INVALID_INPUT on invalid session parameters,
|
||||
* or SECP256K1_CHILLDKG_FAULTY_PARTICIPANT (with fault_index set to
|
||||
* the sender) if a participant message is malformed (invalid
|
||||
* commitment encoding, or an encrypted share that overflows the
|
||||
* group order). On failure, cmsg1 and the state are set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: state: pointer to a coordinator_state object to be passed
|
||||
* to secp256k1_chilldkg_coordinator_finalize (must not
|
||||
* be reused)
|
||||
* cmsg1: pointer to a 162*n + 33*(t-1) byte array (see
|
||||
* secp256k1_chilldkg_coordinator_msg1_len) to store
|
||||
* the message to be broadcast to all participants
|
||||
* fault_index: pointer to a uint32 that receives the identifier of
|
||||
* the faulty participant where applicable, and
|
||||
* UINT32_MAX otherwise
|
||||
* In: pmsgs1: array of n pointers to the participants' first
|
||||
* messages (33*t + 32*n + 97 bytes each)
|
||||
* hostpubkeys33: pointer to an array of n host public keys (33 bytes
|
||||
* each); must be identical (in content and order) to
|
||||
* the arrays used by the participants
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_coordinator_step1(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_chilldkg_coordinator_state *state,
|
||||
unsigned char *cmsg1,
|
||||
uint32_t *fault_index,
|
||||
const unsigned char *const *pmsgs1,
|
||||
const unsigned char *hostpubkeys33,
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
|
||||
|
||||
/** Perform the coordinator's final step of a ChillDKG session.
|
||||
*
|
||||
* Collects the n CertEq signatures into the certificate and verifies all of
|
||||
* them. If this function returns SECP256K1_CHILLDKG_OK, the coordinator
|
||||
* deems the DKG session successful.
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK on success, or
|
||||
* SECP256K1_CHILLDKG_FAULTY_PARTICIPANT (with fault_index set to
|
||||
* the signer) if a CertEq signature is invalid. On failure, all
|
||||
* outputs are set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: cmsg2: pointer to a 64*n byte array to store the
|
||||
* certificate, to be broadcast to all participants
|
||||
* thresh_pk33: pointer to a 33-byte array to store the threshold
|
||||
* public key (compressed serialization)
|
||||
* pubshares33: pointer to an array of n 33-byte elements to store
|
||||
* the public shares of all participants
|
||||
* recovery: pointer to a 4 + 33*t + 162*n byte array (see
|
||||
* secp256k1_chilldkg_recovery_data_len) to store the
|
||||
* recovery data
|
||||
* fault_index: pointer to a uint32 (see above)
|
||||
* In: state: pointer to the coordinator_state object output by
|
||||
* secp256k1_chilldkg_coordinator_step1
|
||||
* pmsgs2: array of n pointers to the participants' second
|
||||
* messages (64 bytes each)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_coordinator_finalize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *cmsg2,
|
||||
unsigned char *thresh_pk33,
|
||||
unsigned char *pubshares33,
|
||||
unsigned char *recovery,
|
||||
uint32_t *fault_index,
|
||||
const secp256k1_chilldkg_coordinator_state *state,
|
||||
const unsigned char *const *pmsgs2
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
/** Length of a coordinator investigation message (cinv): 65*n bytes (n
|
||||
* encrypted partial secret shares of 32 bytes each, followed by n partial
|
||||
* public shares of 33 bytes each). Returns 0 if n is out of range. */
|
||||
SECP256K1_API size_t secp256k1_chilldkg_investigation_msg_len(
|
||||
size_t n_participants
|
||||
);
|
||||
|
||||
/** Recover the DKG output of a participant from recovery data.
|
||||
*
|
||||
* This function serves two purposes: recovering from a failure of
|
||||
* secp256k1_chilldkg_participant_finalize after obtaining the recovery data
|
||||
* from another participant or the coordinator, and reproducing the DKG
|
||||
* outputs on a new device (e.g., after data loss).
|
||||
*
|
||||
* The recovery data (see secp256k1_chilldkg_recovery_data_len) is
|
||||
* self-delimiting: the number of participants n and the threshold t are
|
||||
* derived from its length and contents.
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK on success,
|
||||
* SECP256K1_CHILLDKG_INVALID_INPUT if the recovery data is invalid
|
||||
* (this includes an invalid certificate) or if the host secret key
|
||||
* is invalid or does not match any host public key in the recovery
|
||||
* data (RecoveryDataError and HostSeckeyError in the reference
|
||||
* implementation), or
|
||||
* SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR (with
|
||||
* fault_index set) if a pubnonce in the recovery data is invalid.
|
||||
* On failure, secshare32 and thresh_pk33 are set to zero and
|
||||
* *n_participants_out and *threshold_out are set to 0; pubshares33
|
||||
* and hostpubkeys33_out are set to zero whenever n could be
|
||||
* determined.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: secshare32: pointer to a 32-byte array to store the
|
||||
* (tweaked) secret share
|
||||
* thresh_pk33: pointer to a 33-byte array to store the
|
||||
* threshold public key (compressed serialization)
|
||||
* pubshares33: pointer to an array capable of holding
|
||||
* 33*SECP256K1_CHILLDKG_MAX_PARTICIPANTS bytes
|
||||
* to store the public shares (33 bytes each; the
|
||||
* first n entries are filled)
|
||||
* hostpubkeys33_out: pointer to an array capable of holding
|
||||
* 33*SECP256K1_CHILLDKG_MAX_PARTICIPANTS bytes
|
||||
* to store the host public keys of the recovered
|
||||
* session (the first n entries are filled)
|
||||
* n_participants_out: pointer to a size_t to store the number of
|
||||
* participants n of the recovered session
|
||||
* threshold_out: pointer to a uint32 to store the threshold t
|
||||
* of the recovered session
|
||||
* fault_index: pointer to a uint32 that receives the identifier
|
||||
* of the suspected participant where applicable,
|
||||
* and UINT32_MAX otherwise
|
||||
* In: hostseckey32: pointer to the 32-byte host secret key
|
||||
* recovery: pointer to the recovery data
|
||||
* recovery_len: length of the recovery data
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_participant_recover(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *secshare32,
|
||||
unsigned char *thresh_pk33,
|
||||
unsigned char *pubshares33,
|
||||
unsigned char *hostpubkeys33_out,
|
||||
size_t *n_participants_out,
|
||||
uint32_t *threshold_out,
|
||||
uint32_t *fault_index,
|
||||
const unsigned char *hostseckey32,
|
||||
const unsigned char *recovery,
|
||||
size_t recovery_len
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10);
|
||||
|
||||
/** Recover the DKG output of the coordinator from recovery data.
|
||||
*
|
||||
* Like secp256k1_chilldkg_participant_recover, but for the coordinator, who
|
||||
* has no secret share.
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK on success,
|
||||
* SECP256K1_CHILLDKG_INVALID_INPUT if the recovery data is invalid.
|
||||
* On failure, thresh_pk33 is set to zero and *n_participants_out
|
||||
* and *threshold_out are set to 0; pubshares33 and
|
||||
* hostpubkeys33_out are set to zero whenever n could be determined.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: thresh_pk33: pointer to a 33-byte array to store the
|
||||
* threshold public key (compressed serialization)
|
||||
* pubshares33: pointer to an array capable of holding
|
||||
* 33*SECP256K1_CHILLDKG_MAX_PARTICIPANTS bytes
|
||||
* to store the public shares (the first n
|
||||
* entries are filled)
|
||||
* hostpubkeys33_out: pointer to an array capable of holding
|
||||
* 33*SECP256K1_CHILLDKG_MAX_PARTICIPANTS bytes
|
||||
* to store the host public keys of the recovered
|
||||
* session (the first n entries are filled)
|
||||
* n_participants_out: pointer to a size_t to store n
|
||||
* threshold_out: pointer to a uint32 to store t
|
||||
* In: recovery: pointer to the recovery data
|
||||
* recovery_len: length of the recovery data
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_coordinator_recover(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *thresh_pk33,
|
||||
unsigned char *pubshares33,
|
||||
unsigned char *hostpubkeys33_out,
|
||||
size_t *n_participants_out,
|
||||
uint32_t *threshold_out,
|
||||
const unsigned char *recovery,
|
||||
size_t recovery_len
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7);
|
||||
|
||||
/** Sign recovery data to create a recovery acknowledgment.
|
||||
*
|
||||
* The acknowledgment signature (64 bytes) is a BIP 340 signature over
|
||||
* pad33("BIP DKG/recovery acknowledgment") || u32be(participant_id) ||
|
||||
* recovery_data under the participant's host key. Acks can be collected in
|
||||
* an optional acknowledgment round to confirm that all participants have
|
||||
* received the recovery data.
|
||||
*
|
||||
* Returns: 1 on success, 0 on invalid input (invalid host secret key, host
|
||||
* secret key not matching any host public key, invalid session
|
||||
* parameters, or recovery data that is invalid or does not match
|
||||
* the session parameters). On failure, sig64 is set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: sig64: pointer to a 64-byte array to store the
|
||||
* acknowledgment signature
|
||||
* In: hostseckey32: pointer to the 32-byte host secret key
|
||||
* hostpubkeys33: pointer to an array of n host public keys (33 bytes
|
||||
* each)
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
* recovery: pointer to the recovery data
|
||||
* recovery_len: length of the recovery data
|
||||
* aux_rand32: pointer to 32 bytes of auxiliary randomness (see
|
||||
* BIP 340)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_chilldkg_recovery_ack_sign(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *sig64,
|
||||
const unsigned char *hostseckey32,
|
||||
const unsigned char *hostpubkeys33,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
const unsigned char *recovery,
|
||||
size_t recovery_len,
|
||||
const unsigned char *aux_rand32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(9);
|
||||
|
||||
/** Verify recovery acknowledgment signatures from all participants.
|
||||
*
|
||||
* Note that a failure of this function does NOT mean the DKG failed
|
||||
* (reaching this point implies the DKG itself was successful). It only means
|
||||
* it cannot be confirmed that all participants have a copy of the recovery
|
||||
* data.
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK if all n acknowledgment signatures are
|
||||
* valid, SECP256K1_CHILLDKG_INVALID_INPUT on invalid session
|
||||
* parameters or recovery data, or SECP256K1_CHILLDKG_FAULTY_PARTICIPANT
|
||||
* (with fault_index set to the signer) if an acknowledgment
|
||||
* signature is invalid (InvalidRecoveryAckError in the reference
|
||||
* implementation).
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: fault_index: pointer to a uint32 that receives the identifier of
|
||||
* the participant whose signature is invalid, where
|
||||
* applicable, and UINT32_MAX otherwise
|
||||
* In: hostpubkeys33: pointer to an array of n host public keys (33 bytes
|
||||
* each)
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
* recovery: pointer to the recovery data
|
||||
* recovery_len: length of the recovery data
|
||||
* ack_sigs64: array of n pointers to the acknowledgment
|
||||
* signatures (64 bytes each), in the same order as
|
||||
* hostpubkeys33
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_recovery_acks_verify(
|
||||
const secp256k1_context *ctx,
|
||||
uint32_t *fault_index,
|
||||
const unsigned char *hostpubkeys33,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
const unsigned char *recovery,
|
||||
size_t recovery_len,
|
||||
const unsigned char *const *ack_sigs64
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
/** Generate the investigation message for a single participant.
|
||||
*
|
||||
* The investigation message (65*n bytes, see
|
||||
* secp256k1_chilldkg_investigation_msg_len) allows the given participant to
|
||||
* investigate who is to blame for a failed ChillDKG session (see
|
||||
* secp256k1_chilldkg_participant_investigate). The message contains no
|
||||
* confidential information and can be safely broadcast. (Unlike the
|
||||
* reference implementation, which computes the investigation messages for
|
||||
* all participants at once, this function computes one message per call.)
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_OK on success,
|
||||
* SECP256K1_CHILLDKG_INVALID_INPUT on invalid session parameters or
|
||||
* participant_id >= n, or SECP256K1_CHILLDKG_FAULTY_PARTICIPANT
|
||||
* (with fault_index set to the sender) if a participant message is
|
||||
* malformed. On failure, cinv is set to zero.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: cinv: pointer to a 65*n byte array to store the
|
||||
* investigation message for the given participant
|
||||
* fault_index: pointer to a uint32 (see above)
|
||||
* In: pmsgs1: array of n pointers to the participants' first
|
||||
* messages (33*t + 32*n + 97 bytes each)
|
||||
* hostpubkeys33: pointer to an array of n host public keys (33 bytes
|
||||
* each)
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
* participant_id: the participant the investigation message is for
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_coordinator_investigate(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *cinv,
|
||||
uint32_t *fault_index,
|
||||
const unsigned char *const *pmsgs1,
|
||||
const unsigned char *hostpubkeys33,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
uint32_t participant_id
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Investigate who is to blame for a failed ChillDKG session.
|
||||
*
|
||||
* This function can be called when secp256k1_chilldkg_participant_step2
|
||||
* returned SECP256K1_CHILLDKG_UNKNOWN_FAULTY_PARTICIPANT_OR_COORDINATOR. It
|
||||
* narrows down the suspected faulty parties by analyzing the investigation
|
||||
* message provided by the coordinator.
|
||||
*
|
||||
* Returns: SECP256K1_CHILLDKG_FAULTY_COORDINATOR if the coordinator is
|
||||
* faulty, SECP256K1_CHILLDKG_FAULTY_PARTICIPANT_OR_COORDINATOR
|
||||
* (with fault_index set to the suspected participant) if another
|
||||
* participant or the coordinator is faulty, or
|
||||
* SECP256K1_CHILLDKG_INVALID_INPUT if all inputs are consistent
|
||||
* (i.e., the function was called even though no fault occurred).
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: fault_index: pointer to a uint32 (see above)
|
||||
* In: inv_data: pointer to the participant_inv_data object output by
|
||||
* secp256k1_chilldkg_participant_step2
|
||||
* cinv: pointer to the coordinator's investigation message
|
||||
* for this participant (65*n bytes, see
|
||||
* secp256k1_chilldkg_investigation_msg_len)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT secp256k1_chilldkg_fault secp256k1_chilldkg_participant_investigate(
|
||||
const secp256k1_context *ctx,
|
||||
uint32_t *fault_index,
|
||||
const secp256k1_chilldkg_participant_inv_data *inv_data,
|
||||
const unsigned char *cinv
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -7,51 +7,43 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** A pointer to a function that hashes an EC point to obtain an ECDH secret
|
||||
/** A pointer to a function that applies hash function to a point
|
||||
*
|
||||
* Returns: 1 if the point was successfully hashed.
|
||||
* 0 will cause secp256k1_ecdh to fail and return 0.
|
||||
* Other return values are not allowed, and the behaviour of
|
||||
* secp256k1_ecdh is undefined for other return values.
|
||||
* Out: output: pointer to an array to be filled by the function
|
||||
* In: x32: pointer to a 32-byte x coordinate
|
||||
* y32: pointer to a 32-byte y coordinate
|
||||
* data: arbitrary data pointer that is passed through
|
||||
* Returns: 1 if a point was successfully hashed. 0 will cause ecdh to fail
|
||||
* Out: output: pointer to an array to be filled by the function
|
||||
* In: x: pointer to a 32-byte x coordinate
|
||||
* y: pointer to a 32-byte y coordinate
|
||||
* data: Arbitrary data pointer that is passed through
|
||||
*/
|
||||
typedef int (*secp256k1_ecdh_hash_function)(
|
||||
unsigned char *output,
|
||||
const unsigned char *x32,
|
||||
const unsigned char *y32,
|
||||
const unsigned char *x,
|
||||
const unsigned char *y,
|
||||
void *data
|
||||
);
|
||||
|
||||
/** An implementation of SHA256 hash function that applies to compressed public key.
|
||||
* Populates the output parameter with 32 bytes. */
|
||||
SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256;
|
||||
/** An implementation of SHA256 hash function that applies to compressed public key. */
|
||||
SECP256K1_API extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_sha256;
|
||||
|
||||
/** A default ECDH hash function (currently equal to secp256k1_ecdh_hash_function_sha256).
|
||||
* Populates the output parameter with 32 bytes. */
|
||||
SECP256K1_API const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default;
|
||||
/** A default ecdh hash function (currently equal to secp256k1_ecdh_hash_function_sha256). */
|
||||
SECP256K1_API extern const secp256k1_ecdh_hash_function secp256k1_ecdh_hash_function_default;
|
||||
|
||||
/** Compute an EC Diffie-Hellman secret in constant time
|
||||
*
|
||||
* Returns: 1: exponentiation was successful
|
||||
* 0: scalar was invalid (zero or overflow) or hashfp returned 0
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: output: pointer to an array to be filled by hashfp.
|
||||
* In: pubkey: pointer to a secp256k1_pubkey containing an initialized public key.
|
||||
* seckey: a 32-byte scalar with which to multiply the point.
|
||||
* hashfp: pointer to a hash function. If NULL,
|
||||
* secp256k1_ecdh_hash_function_sha256 is used
|
||||
* (in which case, 32 bytes will be written to output).
|
||||
* data: arbitrary data pointer that is passed through to hashfp
|
||||
* (can be NULL for secp256k1_ecdh_hash_function_sha256).
|
||||
* 0: scalar was invalid (zero or overflow)
|
||||
* Args: ctx: pointer to a context object (cannot be NULL)
|
||||
* Out: output: pointer to an array to be filled by the function
|
||||
* In: pubkey: a pointer to a secp256k1_pubkey containing an
|
||||
* initialized public key
|
||||
* privkey: a 32-byte scalar with which to multiply the point
|
||||
* hashfp: pointer to a hash function. If NULL, secp256k1_ecdh_hash_function_sha256 is used
|
||||
* data: Arbitrary data pointer that is passed through
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdh(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *output,
|
||||
const secp256k1_pubkey *pubkey,
|
||||
const unsigned char *seckey,
|
||||
const unsigned char *privkey,
|
||||
secp256k1_ecdh_hash_function hashfp,
|
||||
void *data
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
#ifndef SECP256K1_ECDSA_ADAPTOR_H
|
||||
#define SECP256K1_ECDSA_ADAPTOR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "secp256k1.h"
|
||||
|
||||
/** This module implements single signer ECDSA adaptor signatures following
|
||||
* "One-Time Verifiably Encrypted Signatures A.K.A. Adaptor Signatures" by
|
||||
* Lloyd Fournier
|
||||
* (https://lists.linuxfoundation.org/pipermail/lightning-dev/2019-November/002316.html
|
||||
* and https://github.com/LLFourn/one-time-VES/blob/master/main.pdf).
|
||||
*
|
||||
* WARNING! DANGER AHEAD!
|
||||
* As mentioned in Lloyd Fournier's paper, the adaptor signature leaks the
|
||||
* Elliptic-curve Diffie–Hellman (ECDH) key between the signing key and the
|
||||
* encryption key. This is not a problem for ECDSA adaptor signatures
|
||||
* themselves, but may result in a complete loss of security when they are
|
||||
* composed with other schemes. More specifically, let us refer to the
|
||||
* signer's public key as X = x*G, and to the encryption key as Y = y*G.
|
||||
* Given X, Y and the adaptor signature, it is trivial to compute Y^x = X^y.
|
||||
*
|
||||
* A defense is to not reuse the signing key of ECDSA adaptor signatures in
|
||||
* protocols that rely on the hardness of the CDH problem, e.g., Diffie-Hellman
|
||||
* key exchange and ElGamal encryption. In general, it is a well-established
|
||||
* cryptographic practice to seperate keys for different purposes whenever
|
||||
* possible.
|
||||
*/
|
||||
|
||||
/** A pointer to a function to deterministically generate a nonce.
|
||||
*
|
||||
* Same as secp256k1_nonce_function_hardened with the exception of using the
|
||||
* compressed 33-byte encoding for the pubkey argument.
|
||||
*
|
||||
* Returns: 1 if a nonce was successfully generated. 0 will cause signing to
|
||||
* return an error.
|
||||
* Out: nonce32: pointer to a 32-byte array to be filled by the function
|
||||
* In: msg32: the 32-byte message hash being verified
|
||||
* key32: pointer to a 32-byte secret key
|
||||
* pk33: the 33-byte serialized pubkey corresponding to key32
|
||||
* algo: pointer to an array describing the signature algorithm
|
||||
* algolen: the length of the algo array
|
||||
* data: arbitrary data pointer that is passed through
|
||||
*
|
||||
* Except for test cases, this function should compute some cryptographic hash of
|
||||
* the message, the key, the pubkey, the algorithm description, and data.
|
||||
*/
|
||||
typedef int (*secp256k1_nonce_function_hardened_ecdsa_adaptor)(
|
||||
unsigned char *nonce32,
|
||||
const unsigned char *msg32,
|
||||
const unsigned char *key32,
|
||||
const unsigned char *pk33,
|
||||
const unsigned char *algo,
|
||||
size_t algolen,
|
||||
void *data
|
||||
);
|
||||
|
||||
/** A modified BIP-340 nonce generation function. If a data pointer is passed, it is
|
||||
* assumed to be a pointer to 32 bytes of auxiliary random data as defined in BIP-340.
|
||||
* The hash will be tagged with algo after removing all terminating null bytes.
|
||||
*/
|
||||
SECP256K1_API const secp256k1_nonce_function_hardened_ecdsa_adaptor secp256k1_nonce_function_ecdsa_adaptor;
|
||||
|
||||
/** Encrypted Signing
|
||||
*
|
||||
* Creates an adaptor signature, which includes a proof to verify the adaptor
|
||||
* signature.
|
||||
* WARNING: Make sure you have read and understood the WARNING at the top of
|
||||
* this file and applied the suggested countermeasures.
|
||||
*
|
||||
* Returns: 1 on success, 0 on failure
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: adaptor_sig162: pointer to 162 byte to store the returned signature
|
||||
* In: seckey32: pointer to 32 byte secret key that will be used for
|
||||
* signing
|
||||
* enckey: pointer to the encryption public key
|
||||
* msg32: pointer to the 32-byte message hash to sign
|
||||
* noncefp: pointer to a nonce generation function. If NULL,
|
||||
* secp256k1_nonce_function_ecdsa_adaptor is used
|
||||
* ndata: pointer to arbitrary data used by the nonce generation
|
||||
* function (can be NULL). If it is non-NULL and
|
||||
* secp256k1_nonce_function_ecdsa_adaptor is used, then
|
||||
* ndata must be a pointer to 32-byte auxiliary randomness
|
||||
* as per BIP-340.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_adaptor_encrypt(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *adaptor_sig162,
|
||||
unsigned char *seckey32,
|
||||
const secp256k1_pubkey *enckey,
|
||||
const unsigned char *msg32,
|
||||
secp256k1_nonce_function_hardened_ecdsa_adaptor noncefp,
|
||||
void *ndata
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Encryption Verification
|
||||
*
|
||||
* Verifies that the adaptor decryption key can be extracted from the adaptor signature
|
||||
* and the completed ECDSA signature.
|
||||
*
|
||||
* Returns: 1 on success, 0 on failure
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: adaptor_sig162: pointer to 162-byte signature to verify
|
||||
* pubkey: pointer to the public key corresponding to the secret key
|
||||
* used for signing
|
||||
* msg32: pointer to the 32-byte message hash being verified
|
||||
* enckey: pointer to the adaptor encryption public key
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_adaptor_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const unsigned char *adaptor_sig162,
|
||||
const secp256k1_pubkey *pubkey,
|
||||
const unsigned char *msg32,
|
||||
const secp256k1_pubkey *enckey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Signature Decryption
|
||||
*
|
||||
* Derives an ECDSA signature from an adaptor signature and an adaptor decryption key.
|
||||
*
|
||||
* Returns: 1 on success, 0 on failure
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: sig: pointer to the ECDSA signature to create
|
||||
* In: deckey32: pointer to 32-byte decryption secret key for the adaptor
|
||||
* encryption public key
|
||||
* adaptor_sig162: pointer to 162-byte adaptor sig
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_adaptor_decrypt(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_ecdsa_signature *sig,
|
||||
const unsigned char *deckey32,
|
||||
const unsigned char *adaptor_sig162
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Decryption Key Recovery
|
||||
*
|
||||
* Extracts the adaptor decryption key from the complete signature and the adaptor
|
||||
* signature.
|
||||
*
|
||||
* Returns: 1 on success, 0 on failure
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: deckey32: pointer to 32-byte adaptor decryption key for the adaptor
|
||||
* encryption public key
|
||||
* In: sig: pointer to ECDSA signature to recover the adaptor decryption
|
||||
* key from
|
||||
* adaptor_sig162: pointer to adaptor signature to recover the adaptor
|
||||
* decryption key from
|
||||
* enckey: pointer to the adaptor encryption public key
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_adaptor_recover(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *deckey32,
|
||||
const secp256k1_ecdsa_signature *sig,
|
||||
const unsigned char *adaptor_sig162,
|
||||
const secp256k1_pubkey *enckey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_ECDSA_ADAPTOR_H */
|
||||
@@ -1,234 +0,0 @@
|
||||
#ifndef SECP256K1_ECDSA_S2C_H
|
||||
#define SECP256K1_ECDSA_S2C_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
|
||||
/** This module implements the sign-to-contract scheme for ECDSA signatures, as
|
||||
* well as the "ECDSA Anti-Exfil Protocol" that is based on sign-to-contract
|
||||
* and is specified further down. The sign-to-contract scheme allows creating a
|
||||
* signature that also commits to some data. This works by offsetting the public
|
||||
* nonce point of the signature R by hash(R, data)*G where G is the secp256k1
|
||||
* group generator.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Data structure that holds a sign-to-contract ("s2c") opening information.
|
||||
* Sign-to-contract allows a signer to commit to some data as part of a signature. It
|
||||
* can be used as an Out-argument in certain signing functions.
|
||||
*
|
||||
* The exact representation of data inside is implementation defined and not
|
||||
* guaranteed to be portable between different platforms or versions. It is
|
||||
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
|
||||
* If you need to convert to a format suitable for storage, transmission, or
|
||||
* comparison, use secp256k1_ecdsa_s2c_opening_serialize and secp256k1_ecdsa_s2c_opening_parse.
|
||||
*/
|
||||
typedef struct secp256k1_ecdsa_s2c_opening {
|
||||
unsigned char data[64];
|
||||
} secp256k1_ecdsa_s2c_opening;
|
||||
|
||||
/** Parse a sign-to-contract opening.
|
||||
*
|
||||
* Returns: 1 if the opening could be parsed
|
||||
* 0 if the opening could not be parsed
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: opening: pointer to an opening object. If 1 is returned, it is set to a
|
||||
* parsed version of input. If not, its value is unspecified.
|
||||
* In: input33: pointer to 33-byte array with a serialized opening
|
||||
*
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_ecdsa_s2c_opening *opening,
|
||||
const unsigned char *input33
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a sign-to-contract opening into a byte sequence.
|
||||
*
|
||||
* Returns: 1 if the opening was successfully serialized.
|
||||
* 0 if the opening could not be serialized
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: output33: pointer to a 33-byte array to place the serialized opening in
|
||||
* In: opening: pointer to an initialized `secp256k1_ecdsa_s2c_opening`
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *output33,
|
||||
const secp256k1_ecdsa_s2c_opening *opening
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Same as secp256k1_ecdsa_sign, but s2c_data32 is committed to inside the nonce
|
||||
*
|
||||
* Returns: 1: signature created
|
||||
* 0: the nonce generation function failed, or the private key was invalid.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: sig: pointer to an array where the signature will be placed (cannot be NULL)
|
||||
* s2c_opening: if non-NULL, pointer to an secp256k1_ecdsa_s2c_opening structure to populate
|
||||
* In: msg32: the 32-byte message hash being signed (cannot be NULL)
|
||||
* seckey: pointer to a 32-byte secret key (cannot be NULL)
|
||||
* s2c_data32: pointer to a 32-byte data to commit to in the nonce (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_s2c_sign(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_ecdsa_signature *sig,
|
||||
secp256k1_ecdsa_s2c_opening *s2c_opening,
|
||||
const unsigned char *msg32,
|
||||
const unsigned char *seckey,
|
||||
const unsigned char *s2c_data32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
|
||||
|
||||
/** Verify a sign-to-contract commitment.
|
||||
*
|
||||
* Returns: 1: the signature contains a commitment to data32 (though it does
|
||||
* not necessarily need to be a valid siganture!)
|
||||
* 0: incorrect opening
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: sig: the signature containing the sign-to-contract commitment (cannot be NULL)
|
||||
* data32: the 32-byte data that was committed to (cannot be NULL)
|
||||
* opening: pointer to the opening created during signing (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_verify_commit(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_ecdsa_signature *sig,
|
||||
const unsigned char *data32,
|
||||
const secp256k1_ecdsa_s2c_opening *opening
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
|
||||
/** ECDSA Anti-Exfil Protocol
|
||||
*
|
||||
* The ecdsa_anti_exfil_* functions can be used to prevent a signing device from
|
||||
* exfiltrating the secret signing keys through biased signature nonces. The general
|
||||
* idea is that a host provides additional randomness to the signing device client
|
||||
* and the client commits to the randomness in the nonce using sign-to-contract.
|
||||
*
|
||||
* The following scheme is described by Stepan Snigirev here:
|
||||
* https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-February/017655.html
|
||||
* and by Pieter Wuille (as "Scheme 6") here:
|
||||
* https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-March/017667.html
|
||||
*
|
||||
* In order to ensure the host cannot trick the signing device into revealing its
|
||||
* keys, or the signing device to bias the nonce despite the host's contributions,
|
||||
* the host and client must engage in a commit-reveal protocol as follows:
|
||||
* 1. The host draws randomness `rho` and computes a sha256 commitment to it using
|
||||
* `secp256k1_ecdsa_anti_exfil_host_commit`. It sends this to the signing device.
|
||||
* 2. The signing device computes a public nonce `R` using the host's commitment
|
||||
* as auxiliary randomness, using `secp256k1_ecdsa_anti_exfil_signer_commit`.
|
||||
* The signing device sends the resulting `R` to the host as a s2c_opening.
|
||||
*
|
||||
* If, at any point from this step onward, the hardware device fails, it is
|
||||
* okay to restart the protocol using **exactly the same `rho`** and checking
|
||||
* that the hardware device proposes **exactly the same** `R`. Otherwise, the
|
||||
* hardware device may be selectively aborting and thereby biasing the set of
|
||||
* nonces that are used in actual signatures.
|
||||
*
|
||||
* It takes many (>100) such aborts before there is a plausible attack, given
|
||||
* current knowledge in 2020. However such aborts accumulate even across a total
|
||||
* replacement of all relevant devices (but not across replacement of the actual
|
||||
* signing keys with new independently random ones).
|
||||
*
|
||||
* In case the hardware device cannot be made to sign with the given `rho`, `R`
|
||||
* pair, wallet authors should alert the user and present a very scary message
|
||||
* implying that if this happens more than even a few times, say 20 or more times
|
||||
* EVER, they should change hardware vendors and perhaps sweep their coins.
|
||||
*
|
||||
* 3. The host replies with `rho` generated in step 1.
|
||||
* 4. The device signs with `secp256k1_anti_exfil_sign`, using `rho` as `host_data32`,
|
||||
* and sends the signature to the host.
|
||||
* 5. The host verifies that the signature's public nonce matches the opening from
|
||||
* step 2 and its original randomness `rho`, using `secp256k1_anti_exfil_host_verify`.
|
||||
*
|
||||
* Rationale:
|
||||
* - The reason for having a host commitment is to allow the signing device to
|
||||
* deterministically derive a unique nonce even if the host restarts the protocol
|
||||
* using the same message and keys. Otherwise the signer might reuse the original
|
||||
* nonce in two iterations of the protocol with different `rho`, which leaks the
|
||||
* the secret key.
|
||||
* - The signer does not need to check that the host commitment matches the host's
|
||||
* claimed `rho`. Instead it re-derives the commitment (and its original `R`) from
|
||||
* the provided `rho`. If this differs from the original commitment, the result
|
||||
* will be an invalid `s2c_opening`, but since `R` was unique there is no risk to
|
||||
* the signer's secret keys. Because of this, the signing device does not need to
|
||||
* maintain any state about the progress of the protocol.
|
||||
*/
|
||||
|
||||
/** Create the initial host commitment to `rho`. Part of the ECDSA Anti-Exfil Protocol.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
* Args: ctx: pointer to a context object (cannot be NULL)
|
||||
* Out: rand_commitment32: pointer to 32-byte array to store the returned commitment (cannot be NULL)
|
||||
* In: rand32: the 32-byte randomness to commit to (cannot be NULL). It must come from
|
||||
* a cryptographically secure RNG. As per the protocol, this value must not
|
||||
* be revealed to the client until after the host has received the client
|
||||
* commitment.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_anti_exfil_host_commit(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *rand_commitment32,
|
||||
const unsigned char *rand32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Compute signer's original nonce. Part of the ECDSA Anti-Exfil Protocol.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: s2c_opening: pointer to an s2c_opening where the signer's public nonce will be
|
||||
* placed. (cannot be NULL)
|
||||
* In: msg32: the 32-byte message hash to be signed (cannot be NULL)
|
||||
* seckey32: the 32-byte secret key used for signing (cannot be NULL)
|
||||
* rand_commitment32: the 32-byte randomness commitment from the host (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_anti_exfil_signer_commit(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_ecdsa_s2c_opening *s2c_opening,
|
||||
const unsigned char *msg32,
|
||||
const unsigned char *seckey32,
|
||||
const unsigned char *rand_commitment32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Same as secp256k1_ecdsa_sign, but commits to host randomness in the nonce. Part of the
|
||||
* ECDSA Anti-Exfil Protocol.
|
||||
*
|
||||
* Returns: 1: signature created
|
||||
* 0: the nonce generation function failed, or the private key was invalid.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: sig: pointer to an array where the signature will be placed (cannot be NULL)
|
||||
* In: msg32: the 32-byte message hash being signed (cannot be NULL)
|
||||
* seckey: pointer to a 32-byte secret key (cannot be NULL)
|
||||
* host_data32: pointer to 32-byte host-provided randomness (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_anti_exfil_sign(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_ecdsa_signature *sig,
|
||||
const unsigned char *msg32,
|
||||
const unsigned char *seckey,
|
||||
const unsigned char *host_data32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Verify a signature was correctly constructed using the ECDSA Anti-Exfil Protocol.
|
||||
*
|
||||
* Returns: 1: the signature is valid and contains a commitment to host_data32
|
||||
* 0: incorrect opening
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: sig: the signature produced by the signer (cannot be NULL)
|
||||
* msghash32: the 32-byte message hash being verified (cannot be NULL)
|
||||
* pubkey: pointer to the signer's public key (cannot be NULL)
|
||||
* host_data32: the 32-byte data provided by the host (cannot be NULL)
|
||||
* opening: the s2c opening provided by the signer (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_anti_exfil_host_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_ecdsa_signature *sig,
|
||||
const unsigned char *msg32,
|
||||
const secp256k1_pubkey *pubkey,
|
||||
const unsigned char *host_data32,
|
||||
const secp256k1_ecdsa_s2c_opening *opening
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_ECDSA_S2C_H */
|
||||
@@ -1,200 +0,0 @@
|
||||
#ifndef SECP256K1_ELLSWIFT_H
|
||||
#define SECP256K1_ELLSWIFT_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This module provides an implementation of ElligatorSwift as well as a
|
||||
* version of x-only ECDH using it (including compatibility with BIP324).
|
||||
*
|
||||
* ElligatorSwift is described in https://eprint.iacr.org/2022/759 by
|
||||
* Chavez-Saab, Rodriguez-Henriquez, and Tibouchi. It permits encoding
|
||||
* uniformly chosen public keys as 64-byte arrays which are indistinguishable
|
||||
* from uniformly random arrays.
|
||||
*
|
||||
* Let f be the function from pairs of field elements to point X coordinates,
|
||||
* defined as follows (all operations modulo p = 2^256 - 2^32 - 977)
|
||||
* f(u,t):
|
||||
* - Let C = 0xa2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f852,
|
||||
* a square root of -3.
|
||||
* - If u=0, set u=1 instead.
|
||||
* - If t=0, set t=1 instead.
|
||||
* - If u^3 + t^2 + 7 = 0, multiply t by 2.
|
||||
* - Let X = (u^3 + 7 - t^2) / (2 * t)
|
||||
* - Let Y = (X + t) / (C * u)
|
||||
* - Return the first in [u + 4 * Y^2, (-X/Y - u) / 2, (X/Y - u) / 2] that is an
|
||||
* X coordinate on the curve (at least one of them is, for any u and t).
|
||||
*
|
||||
* Then an ElligatorSwift encoding of x consists of the 32-byte big-endian
|
||||
* encodings of field elements u and t concatenated, where f(u,t) = x.
|
||||
* The encoding algorithm is described in the paper, and effectively picks a
|
||||
* uniformly random pair (u,t) among those which encode x.
|
||||
*
|
||||
* If the Y coordinate is relevant, it is given the same parity as t.
|
||||
*
|
||||
* Changes w.r.t. the paper:
|
||||
* - The u=0, t=0, and u^3+t^2+7=0 conditions result in decoding to the point
|
||||
* at infinity in the paper. Here they are remapped to finite points.
|
||||
* - The paper uses an additional encoding bit for the parity of y. Here the
|
||||
* parity of t is used (negating t does not affect the decoded x coordinate,
|
||||
* so this is possible).
|
||||
*
|
||||
* For mathematical background about the scheme, see the doc/ellswift.md file.
|
||||
*/
|
||||
|
||||
/** A pointer to a function used by secp256k1_ellswift_xdh to hash the shared X
|
||||
* coordinate along with the encoded public keys to a uniform shared secret.
|
||||
*
|
||||
* Returns: 1 if a shared secret was successfully computed.
|
||||
* 0 will cause secp256k1_ellswift_xdh to fail and return 0.
|
||||
* Other return values are not allowed, and the behaviour of
|
||||
* secp256k1_ellswift_xdh is undefined for other return values.
|
||||
* Out: output: pointer to an array to be filled by the function
|
||||
* In: x32: pointer to the 32-byte serialized X coordinate
|
||||
* of the resulting shared point (will not be NULL)
|
||||
* ell_a64: pointer to the 64-byte encoded public key of party A
|
||||
* (will not be NULL)
|
||||
* ell_b64: pointer to the 64-byte encoded public key of party B
|
||||
* (will not be NULL)
|
||||
* data: arbitrary data pointer that is passed through
|
||||
*/
|
||||
typedef int (*secp256k1_ellswift_xdh_hash_function)(
|
||||
unsigned char *output,
|
||||
const unsigned char *x32,
|
||||
const unsigned char *ell_a64,
|
||||
const unsigned char *ell_b64,
|
||||
void *data
|
||||
);
|
||||
|
||||
/** An implementation of an secp256k1_ellswift_xdh_hash_function which uses
|
||||
* SHA256(prefix64 || ell_a64 || ell_b64 || x32), where prefix64 is the 64-byte
|
||||
* array pointed to by data. */
|
||||
SECP256K1_API const secp256k1_ellswift_xdh_hash_function secp256k1_ellswift_xdh_hash_function_prefix;
|
||||
|
||||
/** An implementation of an secp256k1_ellswift_xdh_hash_function compatible with
|
||||
* BIP324. It returns H_tag(ell_a64 || ell_b64 || x32), where H_tag is the
|
||||
* BIP340 tagged hash function with tag "bip324_ellswift_xonly_ecdh". Equivalent
|
||||
* to secp256k1_ellswift_xdh_hash_function_prefix with prefix64 set to
|
||||
* SHA256("bip324_ellswift_xonly_ecdh")||SHA256("bip324_ellswift_xonly_ecdh").
|
||||
* The data argument is ignored. */
|
||||
SECP256K1_API const secp256k1_ellswift_xdh_hash_function secp256k1_ellswift_xdh_hash_function_bip324;
|
||||
|
||||
/** Construct a 64-byte ElligatorSwift encoding of a given pubkey.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: ell64: pointer to a 64-byte array to be filled
|
||||
* In: pubkey: pointer to a secp256k1_pubkey containing an
|
||||
* initialized public key
|
||||
* rnd32: pointer to 32 bytes of randomness
|
||||
*
|
||||
* It is recommended that rnd32 consists of 32 uniformly random bytes, not
|
||||
* known to any adversary trying to detect whether public keys are being
|
||||
* encoded, though 16 bytes of randomness (padded to an array of 32 bytes,
|
||||
* e.g., with zeros) suffice to make the result indistinguishable from
|
||||
* uniform. The randomness in rnd32 must not be a deterministic function of
|
||||
* the pubkey (it can be derived from the private key, though).
|
||||
*
|
||||
* It is not guaranteed that the computed encoding is stable across versions
|
||||
* of the library, even if all arguments to this function (including rnd32)
|
||||
* are the same.
|
||||
*
|
||||
* This function runs in variable time.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ellswift_encode(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *ell64,
|
||||
const secp256k1_pubkey *pubkey,
|
||||
const unsigned char *rnd32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Decode a 64-bytes ElligatorSwift encoded public key.
|
||||
*
|
||||
* Returns: always 1
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: pubkey: pointer to a secp256k1_pubkey that will be filled
|
||||
* In: ell64: pointer to a 64-byte array to decode
|
||||
*
|
||||
* This function runs in variable time.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ellswift_decode(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_pubkey *pubkey,
|
||||
const unsigned char *ell64
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Compute an ElligatorSwift public key for a secret key.
|
||||
*
|
||||
* Returns: 1: secret was valid, public key was stored.
|
||||
* 0: secret was invalid, try again.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: ell64: pointer to a 64-byte array to receive the ElligatorSwift
|
||||
* public key
|
||||
* In: seckey32: pointer to a 32-byte secret key
|
||||
* auxrnd32: (optional) pointer to 32 bytes of randomness
|
||||
*
|
||||
* Constant time in seckey and auxrnd32, but not in the resulting public key.
|
||||
*
|
||||
* It is recommended that auxrnd32 contains 32 uniformly random bytes, though
|
||||
* it is optional (and does result in encodings that are indistinguishable from
|
||||
* uniform even without any auxrnd32). It differs from the (mandatory) rnd32
|
||||
* argument to secp256k1_ellswift_encode in this regard.
|
||||
*
|
||||
* This function can be used instead of calling secp256k1_ec_pubkey_create
|
||||
* followed by secp256k1_ellswift_encode. It is safer, as it uses the secret
|
||||
* key as entropy for the encoding (supplemented with auxrnd32, if provided).
|
||||
*
|
||||
* Like secp256k1_ellswift_encode, this function does not guarantee that the
|
||||
* computed encoding is stable across versions of the library, even if all
|
||||
* arguments (including auxrnd32) are the same.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ellswift_create(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *ell64,
|
||||
const unsigned char *seckey32,
|
||||
const unsigned char *auxrnd32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Given a private key, and ElligatorSwift public keys sent in both directions,
|
||||
* compute a shared secret using x-only Elliptic Curve Diffie-Hellman (ECDH).
|
||||
*
|
||||
* Returns: 1: shared secret was successfully computed
|
||||
* 0: secret was invalid or hashfp returned 0
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: output: pointer to an array to be filled by hashfp.
|
||||
* In: ell_a64: pointer to the 64-byte encoded public key of party A
|
||||
* (will not be NULL)
|
||||
* ell_b64: pointer to the 64-byte encoded public key of party B
|
||||
* (will not be NULL)
|
||||
* seckey32: pointer to our 32-byte secret key
|
||||
* party: boolean indicating which party we are: zero if we are
|
||||
* party A, non-zero if we are party B. seckey32 must be
|
||||
* the private key corresponding to that party's ell_?64.
|
||||
* This correspondence is not checked.
|
||||
* hashfp: pointer to a hash function.
|
||||
* data: arbitrary data pointer passed through to hashfp.
|
||||
*
|
||||
* Constant time in seckey32.
|
||||
*
|
||||
* This function is more efficient than decoding the public keys, and performing
|
||||
* ECDH on them.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ellswift_xdh(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *output,
|
||||
const unsigned char *ell_a64,
|
||||
const unsigned char *ell_b64,
|
||||
const unsigned char *seckey32,
|
||||
int party,
|
||||
secp256k1_ellswift_xdh_hash_function hashfp,
|
||||
void *data
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_ELLSWIFT_H */
|
||||
@@ -1,250 +0,0 @@
|
||||
#ifndef SECP256K1_EXTRAKEYS_H
|
||||
#define SECP256K1_EXTRAKEYS_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Opaque data structure that holds a parsed and valid "x-only" public key.
|
||||
* An x-only pubkey encodes a point whose Y coordinate is even. It is
|
||||
* serialized using only its X coordinate (32 bytes). See BIP-340 for more
|
||||
* information about x-only pubkeys.
|
||||
*
|
||||
* The exact representation of data inside is implementation defined and not
|
||||
* guaranteed to be portable between different platforms or versions. It is
|
||||
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
|
||||
* If you need to convert to a format suitable for storage, transmission, use
|
||||
* use secp256k1_xonly_pubkey_serialize and secp256k1_xonly_pubkey_parse. To
|
||||
* compare keys, use secp256k1_xonly_pubkey_cmp.
|
||||
*/
|
||||
typedef struct secp256k1_xonly_pubkey {
|
||||
unsigned char data[64];
|
||||
} secp256k1_xonly_pubkey;
|
||||
|
||||
/** Opaque data structure that holds a keypair consisting of a secret and a
|
||||
* public key.
|
||||
*
|
||||
* The exact representation of data inside is implementation defined and not
|
||||
* guaranteed to be portable between different platforms or versions. It is
|
||||
* however guaranteed to be 96 bytes in size, and can be safely copied/moved.
|
||||
*/
|
||||
typedef struct secp256k1_keypair {
|
||||
unsigned char data[96];
|
||||
} secp256k1_keypair;
|
||||
|
||||
/** Parse a 32-byte sequence into a xonly_pubkey object.
|
||||
*
|
||||
* Returns: 1 if the public key was fully valid.
|
||||
* 0 if the public key could not be parsed or is invalid.
|
||||
*
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a
|
||||
* parsed version of input. If not, it's set to an invalid value.
|
||||
* In: input32: pointer to a serialized xonly_pubkey.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_xonly_pubkey *pubkey,
|
||||
const unsigned char *input32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize an xonly_pubkey object into a 32-byte sequence.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
*
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: output32: pointer to a 32-byte array to place the serialized key in.
|
||||
* In: pubkey: pointer to a secp256k1_xonly_pubkey containing an initialized public key.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_xonly_pubkey_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *output32,
|
||||
const secp256k1_xonly_pubkey *pubkey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Compare two x-only public keys using lexicographic order
|
||||
*
|
||||
* Returns: <0 if the first public key is less than the second
|
||||
* >0 if the first public key is greater than the second
|
||||
* 0 if the two public keys are equal
|
||||
* Args: ctx: pointer to a context object.
|
||||
* In: pubkey1: first public key to compare
|
||||
* pubkey2: second public key to compare
|
||||
*/
|
||||
SECP256K1_API int secp256k1_xonly_pubkey_cmp(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_xonly_pubkey *pk1,
|
||||
const secp256k1_xonly_pubkey *pk2
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
*
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: xonly_pubkey: pointer to an x-only public key object for placing the converted public key.
|
||||
* pk_parity: Ignored if NULL. Otherwise, pointer to an integer that
|
||||
* will be set to 1 if the point encoded by xonly_pubkey is
|
||||
* the negation of the pubkey and set to 0 otherwise.
|
||||
* In: pubkey: pointer to a public key that is converted.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_xonly_pubkey_from_pubkey(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_xonly_pubkey *xonly_pubkey,
|
||||
int *pk_parity,
|
||||
const secp256k1_pubkey *pubkey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Tweak an x-only public key by adding the generator multiplied with tweak32
|
||||
* to it.
|
||||
*
|
||||
* Note that the resulting point can not in general be represented by an x-only
|
||||
* pubkey because it may have an odd Y coordinate. Instead, the output_pubkey
|
||||
* is a normal secp256k1_pubkey.
|
||||
*
|
||||
* 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.
|
||||
* In: internal_pubkey: pointer to an x-only pubkey to apply the tweak to.
|
||||
* tweak32: pointer to a 32-byte tweak, which must be valid
|
||||
* according to secp256k1_ec_seckey_verify or 32 zero
|
||||
* bytes. For uniformly random 32-byte tweaks, the chance of
|
||||
* being invalid is negligible (around 1 in 2^128).
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_pubkey *output_pubkey,
|
||||
const secp256k1_xonly_pubkey *internal_pubkey,
|
||||
const unsigned char *tweak32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Checks that a tweaked pubkey is the result of calling
|
||||
* secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.
|
||||
*
|
||||
* The tweaked pubkey is represented by its 32-byte x-only serialization and
|
||||
* its pk_parity, which can both be obtained by converting the result of
|
||||
* tweak_add to a secp256k1_xonly_pubkey.
|
||||
*
|
||||
* Note that this alone does _not_ verify that the tweaked pubkey is a
|
||||
* commitment. If the tweak is not chosen in a specific way, the tweaked pubkey
|
||||
* can easily be the result of a different internal_pubkey and tweak.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or the tweaked pubkey is not the
|
||||
* result of tweaking the internal_pubkey with tweak32. 1 otherwise.
|
||||
* Args: ctx: pointer to a context object.
|
||||
* In: tweaked_pubkey32: pointer to a serialized xonly_pubkey.
|
||||
* tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization
|
||||
* is passed in as tweaked_pubkey32). This must match the
|
||||
* pk_parity value that is returned when calling
|
||||
* secp256k1_xonly_pubkey with the tweaked pubkey, or
|
||||
* this function will fail.
|
||||
* internal_pubkey: pointer to an x-only public key object to apply the tweak to.
|
||||
* tweak32: pointer to a 32-byte tweak.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add_check(
|
||||
const secp256k1_context *ctx,
|
||||
const unsigned char *tweaked_pubkey32,
|
||||
int tweaked_pk_parity,
|
||||
const secp256k1_xonly_pubkey *internal_pubkey,
|
||||
const unsigned char *tweak32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Compute the keypair for a valid secret key.
|
||||
*
|
||||
* See the documentation of `secp256k1_ec_seckey_verify` for more information
|
||||
* about the validity of secret keys.
|
||||
*
|
||||
* Returns: 1: secret key is valid
|
||||
* 0: secret key is invalid
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static).
|
||||
* Out: keypair: pointer to the created keypair.
|
||||
* In: seckey: pointer to a 32-byte secret key.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_create(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_keypair *keypair,
|
||||
const unsigned char *seckey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Get the secret key from a keypair.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: seckey: pointer to a 32-byte buffer for the secret key.
|
||||
* In: keypair: pointer to a keypair.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_keypair_sec(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *seckey,
|
||||
const secp256k1_keypair *keypair
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Get the public key from a keypair.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: pubkey: pointer to a pubkey object, set to the keypair public key.
|
||||
* In: keypair: pointer to a keypair.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_keypair_pub(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_pubkey *pubkey,
|
||||
const secp256k1_keypair *keypair
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Get the x-only public key from a keypair.
|
||||
*
|
||||
* This is the same as calling secp256k1_keypair_pub and then
|
||||
* secp256k1_xonly_pubkey_from_pubkey.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: pubkey: pointer to an xonly_pubkey object, set to the keypair
|
||||
* public key after converting it to an xonly_pubkey.
|
||||
* pk_parity: Ignored if NULL. Otherwise, pointer to an integer that will be set to the
|
||||
* pk_parity argument of secp256k1_xonly_pubkey_from_pubkey.
|
||||
* In: keypair: pointer to a keypair.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_keypair_xonly_pub(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_xonly_pubkey *pubkey,
|
||||
int *pk_parity,
|
||||
const secp256k1_keypair *keypair
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Tweak a keypair by adding tweak32 to the secret key and updating the public
|
||||
* key accordingly.
|
||||
*
|
||||
* Calling this function and then secp256k1_keypair_pub results in the same
|
||||
* public key as calling secp256k1_keypair_xonly_pub and then
|
||||
* secp256k1_xonly_pubkey_tweak_add.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or the resulting keypair would be
|
||||
* invalid (only when the tweak is the negation of the keypair's
|
||||
* secret key). 1 otherwise.
|
||||
*
|
||||
* Args: ctx: pointer to a context object.
|
||||
* In/Out: keypair: pointer to a keypair to apply the tweak to. Will be set to
|
||||
* an invalid value if this function returns 0.
|
||||
* In: tweak32: pointer to a 32-byte tweak, which must be valid according to
|
||||
* secp256k1_ec_seckey_verify or 32 zero bytes. For uniformly
|
||||
* random 32-byte tweaks, the chance of being invalid is
|
||||
* negligible (around 1 in 2^128).
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_xonly_tweak_add(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_keypair *keypair,
|
||||
const unsigned char *tweak32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_EXTRAKEYS_H */
|
||||
@@ -1,644 +0,0 @@
|
||||
#ifndef SECP256K1_FROST_H
|
||||
#define SECP256K1_FROST_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
#include "secp256k1_extrakeys.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** This module implements BIP 445 "FROST Signing Protocol for BIP340
|
||||
* Signatures" (https://github.com/siv2r/bip-frost-signing), a FROST3-based
|
||||
* threshold Schnorr signature scheme.
|
||||
*
|
||||
* 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!
|
||||
*
|
||||
* Since distributed key generation is out of scope for BIP 445, this module
|
||||
* provides a trusted dealer for key generation
|
||||
* (secp256k1_frost_trusted_dealer_keygen). Participants are identified by
|
||||
* uint32 identifiers 0..n-1 (participant id i sits at polynomial x-coordinate
|
||||
* i+1). The total number of participants n must not exceed
|
||||
* SECP256K1_FROST_MAX_PARTICIPANTS.
|
||||
*
|
||||
* A signing session involves u signers (threshold <= u <= n) and,
|
||||
* optionally, a coordinator:
|
||||
* 1. Every signer runs secp256k1_frost_nonce_gen and sends the pubnonce to
|
||||
* the coordinator.
|
||||
* 2. The coordinator aggregates the pubnonces with
|
||||
* secp256k1_frost_nonce_agg and sends the aggnonce to the signers.
|
||||
* 3. Everyone runs secp256k1_frost_session_init on the same session
|
||||
* parameters (aggnonce, signer ids, tweak cache, message).
|
||||
* 4. Every signer runs secp256k1_frost_sign and sends the partial signature
|
||||
* to the coordinator.
|
||||
* 5. The coordinator verifies partial signatures with
|
||||
* secp256k1_frost_partial_sig_verify and aggregates them with
|
||||
* secp256k1_frost_partial_sig_agg.
|
||||
*
|
||||
* It is recommended to read the documentation in this include file carefully.
|
||||
* Further notes on API usage can be found in src/modules/frost/frost.md.
|
||||
*/
|
||||
|
||||
/** The maximum number of participants n in a FROST setup. See BIP 445 for the
|
||||
* security rationale behind this bound. */
|
||||
#define SECP256K1_FROST_MAX_PARTICIPANTS 128
|
||||
|
||||
/** Opaque data structures
|
||||
*
|
||||
* The exact representation of data inside the opaque data structures is
|
||||
* implementation defined and not guaranteed to be portable between different
|
||||
* platforms or versions. With the exception of `secp256k1_frost_secnonce`,
|
||||
* the data structures can be safely copied/moved. 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 holds a signer's _secret_ nonce.
|
||||
*
|
||||
* Guaranteed to be 68 bytes in size.
|
||||
*
|
||||
* WARNING: This structure MUST NOT be copied or read or written to directly.
|
||||
* A signer who is online throughout the whole process and can keep this
|
||||
* structure in memory can use the provided API functions for a safe standard
|
||||
* workflow.
|
||||
*
|
||||
* Copying this data structure can result in nonce reuse which will leak the
|
||||
* signer's secret share.
|
||||
*/
|
||||
typedef struct secp256k1_frost_secnonce {
|
||||
unsigned char data[68];
|
||||
} secp256k1_frost_secnonce;
|
||||
|
||||
/** Opaque data structure that holds a signer's public nonce.
|
||||
*
|
||||
* Guaranteed to be 132 bytes in size. Serialized and parsed with
|
||||
* `frost_pubnonce_serialize` and `frost_pubnonce_parse`.
|
||||
*/
|
||||
typedef struct secp256k1_frost_pubnonce {
|
||||
unsigned char data[132];
|
||||
} secp256k1_frost_pubnonce;
|
||||
|
||||
/** Opaque data structure that holds an aggregate public nonce.
|
||||
*
|
||||
* Guaranteed to be 132 bytes in size. Serialized and parsed with
|
||||
* `frost_aggnonce_serialize` and `frost_aggnonce_parse`.
|
||||
*/
|
||||
typedef struct secp256k1_frost_aggnonce {
|
||||
unsigned char data[132];
|
||||
} secp256k1_frost_aggnonce;
|
||||
|
||||
/** Opaque data structure that caches the threshold public key and the state
|
||||
* of public key tweaking.
|
||||
*
|
||||
* Initialized with `frost_tweak_cache_init` from the (untweaked) threshold
|
||||
* public key and required for `frost_session_init`. Tweaks are applied to
|
||||
* the cache with `frost_pubkey_xonly_tweak_add` and
|
||||
* `frost_pubkey_ec_tweak_add`.
|
||||
*
|
||||
* Guaranteed to be 165 bytes in size. No serialization and parsing functions
|
||||
* (yet).
|
||||
*/
|
||||
typedef struct secp256k1_frost_tweak_cache {
|
||||
unsigned char data[165];
|
||||
} secp256k1_frost_tweak_cache;
|
||||
|
||||
/** Opaque data structure that holds a FROST signing session.
|
||||
*
|
||||
* The session is signer-agnostic: the same session object can be used by a
|
||||
* coordinator to verify the partial signatures of all signers. This
|
||||
* structure is not required to be kept secret for the signing protocol to
|
||||
* be secure. Guaranteed to be 137 bytes in size. No serialization and
|
||||
* parsing functions (yet).
|
||||
*/
|
||||
typedef struct secp256k1_frost_session {
|
||||
unsigned char data[137];
|
||||
} 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 secp256k1_frost_partial_sig {
|
||||
unsigned char data[36];
|
||||
} secp256k1_frost_partial_sig;
|
||||
|
||||
/** Parse a signer's public nonce.
|
||||
*
|
||||
* Returns: 1 when the nonce could be parsed, 0 otherwise.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: nonce: pointer to a nonce object
|
||||
* In: in66: pointer to the 66-byte nonce to be parsed
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubnonce_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_pubnonce *nonce,
|
||||
const unsigned char *in66
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a signer's public nonce
|
||||
*
|
||||
* Returns: 1 always
|
||||
* 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);
|
||||
|
||||
/** Parse an aggregate public nonce.
|
||||
*
|
||||
* In contrast to `frost_pubnonce_parse`, this function accepts the point at
|
||||
* infinity (encoded as 33 zero bytes) for either nonce component, as
|
||||
* specified by BIP 445 NonceAgg.
|
||||
*
|
||||
* Returns: 1 when the nonce could be parsed, 0 otherwise.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: nonce: pointer to a nonce object
|
||||
* In: in66: pointer to the 66-byte nonce to be parsed
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_aggnonce_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_aggnonce *nonce,
|
||||
const unsigned char *in66
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize an aggregate public nonce
|
||||
*
|
||||
* A nonce component that is the point at infinity is encoded as 33 zero
|
||||
* bytes, as specified by BIP 445.
|
||||
*
|
||||
* Returns: 1 always
|
||||
* 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_aggnonce_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *out66,
|
||||
const secp256k1_frost_aggnonce *nonce
|
||||
) 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
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_partial_sig *sig,
|
||||
const unsigned char *in32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a FROST partial signature
|
||||
*
|
||||
* Returns: 1 always
|
||||
* 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);
|
||||
|
||||
/** Generate threshold key material with a trusted dealer.
|
||||
*
|
||||
* Implements the trusted dealer key generation of BIP 445: from the given
|
||||
* threshold secret key, the dealer derives the secret share of every
|
||||
* participant and the corresponding public shares. The dealer must transmit
|
||||
* each secret share to its participant over a secure channel and erase all
|
||||
* secret key material afterwards.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: secshares32: pointer to an n_participants*32-byte array to store
|
||||
* the secret shares; participant with id i receives
|
||||
* secshares32[i*32..(i+1)*32]
|
||||
* thresh_pk: pointer to a pubkey object to store the threshold
|
||||
* public key (full point, parity is meaningful)
|
||||
* pubshares: pointer to an array of n_participants pubkey
|
||||
* objects to store the public shares; entry i belongs
|
||||
* to the participant with id i
|
||||
* In: n_participants: total number of participants n. Must be between 1
|
||||
* and SECP256K1_FROST_MAX_PARTICIPANTS.
|
||||
* threshold: threshold t. Must be between 1 and n_participants.
|
||||
* threshold_seckey32: pointer to the 32-byte threshold secret key
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_trusted_dealer_keygen(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *secshares32,
|
||||
secp256k1_pubkey *thresh_pk,
|
||||
secp256k1_pubkey *pubshares,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
const unsigned char *threshold_seckey32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7);
|
||||
|
||||
/** Validate threshold key material.
|
||||
*
|
||||
* Implements ValidateThresholdInfo of BIP 445: checks that the public shares
|
||||
* lie on a single polynomial and that they are consistent with the threshold
|
||||
* public key. Note that this validates functional compatibility of the key
|
||||
* material; it does NOT validate the security of the key generation that
|
||||
* produced it.
|
||||
*
|
||||
* Returns: 1 if the key material is valid and consistent, 0 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: thresh_pk: pointer to the threshold public key
|
||||
* pubshares: array of n_participants pubkeys; entry i is the
|
||||
* public share of the participant with id i
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_threshold_info_validate(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_pubkey *thresh_pk,
|
||||
const secp256k1_pubkey *pubshares,
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Initialize a tweak cache from the threshold public key.
|
||||
*
|
||||
* The tweak cache is required for creating a signing session with
|
||||
* `frost_session_init`, even if no tweaks are applied.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: cache: pointer to the tweak cache to initialize
|
||||
* In: thresh_pk: pointer to the (untweaked) threshold public key
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_tweak_cache_init(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_tweak_cache *cache,
|
||||
const secp256k1_pubkey *thresh_pk
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Get the current (tweaked) threshold public key from a tweak cache.
|
||||
*
|
||||
* This is the BIP340 x-only public key that final signatures of sessions
|
||||
* created with this cache verify against.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: tweaked_pk: pointer to an xonly_pubkey object to store the tweaked
|
||||
* threshold public key
|
||||
* In: cache: pointer to the tweak cache
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_tweaked_pubkey_get(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_xonly_pubkey *tweaked_pk,
|
||||
const secp256k1_frost_tweak_cache *cache
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Apply an x-only tweak to the tweak cache.
|
||||
*
|
||||
* Implements ApplyTweak of BIP 445 with is_xonly = true, i.e. BIP 341
|
||||
* ("Taproot") tweaking: the current public key is negated if it has odd Y
|
||||
* before the tweak is applied.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or the tweaked key would be the
|
||||
* point at infinity, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: tweaked_pk: pointer to an xonly_pubkey object to store the tweaked
|
||||
* threshold public key. If you do not need it, this arg can
|
||||
* be NULL.
|
||||
* In: cache: pointer to the tweak cache
|
||||
* tweak32: pointer to the 32-byte tweak
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_xonly_tweak_add(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_xonly_pubkey *tweaked_pk,
|
||||
secp256k1_frost_tweak_cache *cache,
|
||||
const unsigned char *tweak32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Apply a plain tweak to the tweak cache.
|
||||
*
|
||||
* Implements ApplyTweak of BIP 445 with is_xonly = false, i.e. ordinary
|
||||
* (BIP 32-style) tweaking: the current public key is not negated before the
|
||||
* tweak is applied.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or the tweaked key would be the
|
||||
* point at infinity, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: tweaked_pk: pointer to an xonly_pubkey object to store the tweaked
|
||||
* threshold public key. If you do not need it, this arg can
|
||||
* be NULL.
|
||||
* In: cache: pointer to the tweak cache
|
||||
* tweak32: pointer to the 32-byte tweak
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_pubkey_ec_tweak_add(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_xonly_pubkey *tweaked_pk,
|
||||
secp256k1_frost_tweak_cache *cache,
|
||||
const unsigned char *tweak32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Create a FROST nonce (pair) and its public counterpart.
|
||||
*
|
||||
* Implements NonceGen of BIP 445. This function must only be called once per
|
||||
* signing session; never reuse a secnonce for a second signature, as this
|
||||
* leaks the secret share. To prevent reuse, this function wipes
|
||||
* session_secrand32 before returning, and `frost_sign` wipes the secnonce.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: secnonce: pointer to a secnonce object that will be
|
||||
* required for `frost_sign`
|
||||
* pubnonce: pointer to a pubnonce object to be sent to the
|
||||
* coordinator
|
||||
* In/Out: session_secrand32: pointer to 32 bytes of fresh randomness. Must
|
||||
* be unique for every call. It is wiped before
|
||||
* this function returns.
|
||||
* In: secshare: pointer to the signer's 32-byte secret share,
|
||||
* or NULL. Providing the secret share adds
|
||||
* defense-in-depth against bad randomness (the
|
||||
* randomness is masked with the share, see
|
||||
* BIP 445 "Modifications to Nonce Generation").
|
||||
* pubshare: pointer to the signer's public share, or NULL
|
||||
* thresh_pk32: pointer to the 32-byte x-only encoding of the
|
||||
* threshold public key the signature will verify
|
||||
* against (i.e. after applying tweaks, if any),
|
||||
* or NULL
|
||||
* msg: pointer to the message to be signed, or NULL if
|
||||
* the message is not known yet
|
||||
* msglen: length of msg. Must be 0 if msg is NULL.
|
||||
* extra_in: pointer to additional data to bind into the
|
||||
* nonce derivation, or NULL
|
||||
* extra_in_len: length of extra_in. Must be 0 if extra_in is
|
||||
* NULL and at most 2^32 - 1 (the nonce hash
|
||||
* commits to it in a 4-byte length prefix).
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_gen(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_secnonce *secnonce,
|
||||
secp256k1_frost_pubnonce *pubnonce,
|
||||
unsigned char *session_secrand32,
|
||||
const unsigned char *secshare32,
|
||||
const secp256k1_pubkey *pubshare,
|
||||
const unsigned char *thresh_pk32,
|
||||
const unsigned char *msg,
|
||||
size_t msglen,
|
||||
const unsigned char *extra_in,
|
||||
size_t extra_in_len
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Aggregate the public nonces of the signers.
|
||||
*
|
||||
* Implements NonceAgg of BIP 445. Note that the aggregate nonce may contain
|
||||
* the point at infinity (if the corresponding column sums to it); this is
|
||||
* not an error.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or one of the pubnonces is
|
||||
* malformed, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: aggnonce: pointer to an aggnonce object
|
||||
* error_index: if non-NULL, set to the index of the offending
|
||||
* pubnonce on failure
|
||||
* In: pubnonces: input array of pointers to pubnonces. The pubnonce at
|
||||
* index i must belong to the signer with ids[i] passed
|
||||
* to `frost_session_init`.
|
||||
* n_pubnonces: length of the pubnonces array. Must be greater than
|
||||
* 0.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_nonce_agg(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_aggnonce *aggnonce,
|
||||
size_t *error_index,
|
||||
const secp256k1_frost_pubnonce *const *pubnonces,
|
||||
size_t n_pubnonces
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Initialize a FROST signing session.
|
||||
*
|
||||
* Implements GetSessionValues of BIP 445: computes the nonce coefficient,
|
||||
* the final nonce, and the BIP340 challenge. All signers and the coordinator
|
||||
* must call this function with identical arguments (aside from ctx and
|
||||
* session). The session is signer-agnostic; the coordinator can use it to
|
||||
* verify the partial signatures of all signers.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid (including inconsistent key
|
||||
* material or duplicate signer ids), 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: session: pointer to a session object
|
||||
* In: aggnonce: pointer to the aggregate nonce from
|
||||
* `frost_nonce_agg`
|
||||
* ids: array of the u signer identifiers. Every id must
|
||||
* be unique and smaller than n_participants.
|
||||
* pubshares: array of u pubkeys with the public shares of the
|
||||
* signers (entry i belongs to ids[i]), or NULL if the
|
||||
* pubshares are unknown. If provided, they are
|
||||
* validated against the threshold public key.
|
||||
* n_signers: number of signers u. Must be between threshold and
|
||||
* n_participants.
|
||||
* n_participants: total number of participants n. Must be at most
|
||||
* SECP256K1_FROST_MAX_PARTICIPANTS.
|
||||
* threshold: threshold t. Must be between 1 and n_participants.
|
||||
* tweak_cache: pointer to the tweak cache holding the threshold
|
||||
* public key and all tweaks applied to it
|
||||
* msg: pointer to the message to sign, or NULL if
|
||||
* msglen is 0
|
||||
* msglen: length of msg
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_session_init(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_session *session,
|
||||
const secp256k1_frost_aggnonce *aggnonce,
|
||||
const uint32_t *ids,
|
||||
const secp256k1_pubkey *pubshares,
|
||||
size_t n_signers,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
const secp256k1_frost_tweak_cache *tweak_cache,
|
||||
const unsigned char *msg,
|
||||
size_t msglen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10);
|
||||
|
||||
/** Produce a partial signature.
|
||||
*
|
||||
* Implements Sign of BIP 445. Fails if my_id is not in the session's
|
||||
* signer set. If pubshares is non-NULL, it must be the same array that was
|
||||
* passed to `frost_session_init`, and the secret share is checked against
|
||||
* the signer's public share (recommended; pass NULL only if the pubshares
|
||||
* are unavailable). The secnonce is wiped by this function; calling it
|
||||
* again with the same secnonce fails.
|
||||
*
|
||||
* The same ids array that was passed to `frost_session_init` must be passed
|
||||
* here.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or signing fails, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: partial_sig: pointer to a partial_sig object
|
||||
* In: secnonce: pointer to the signer's secnonce from
|
||||
* `frost_nonce_gen`
|
||||
* secshare32: pointer to the signer's 32-byte secret share
|
||||
* session: pointer to the session
|
||||
* ids: array of the u signer identifiers (identical to
|
||||
* session_init)
|
||||
* pubshares: array of u pubkeys with the signers' public shares
|
||||
* (identical to session_init), or NULL
|
||||
* n_signers: number of signers u
|
||||
* my_id: this signer's identifier
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_sign(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_partial_sig *partial_sig,
|
||||
secp256k1_frost_secnonce *secnonce,
|
||||
const unsigned char *secshare32,
|
||||
const secp256k1_frost_session *session,
|
||||
const uint32_t *ids,
|
||||
const secp256k1_pubkey *pubshares,
|
||||
size_t n_signers,
|
||||
uint32_t my_id
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
|
||||
|
||||
/** Produce a partial signature with a deterministically derived nonce.
|
||||
*
|
||||
* Implements DeterministicSign of BIP 445. This function combines nonce
|
||||
* generation and signing into one step for a signer that is online
|
||||
* throughout the whole session. The nonce is derived deterministically from
|
||||
* the secret share, the signer set, the other signers' aggregate nonce, the
|
||||
* tweaked threshold public key, and the message; no secnonce object is
|
||||
* involved.
|
||||
*
|
||||
* A sole signer (u = 1) passes aggothernonce = NULL. Otherwise aggothernonce
|
||||
* is the aggregate of all _other_ signers' pubnonces (obtainable via
|
||||
* `frost_nonce_agg`). Unlike an aggnonce passed to `frost_session_init`, the
|
||||
* aggothernonce must not contain the point at infinity (BIP 445 feeds it
|
||||
* through NonceAgg as a pubnonce contribution, and a pubnonce's components
|
||||
* are never the point at infinity); if it does, this function fails.
|
||||
*
|
||||
* WARNING: the derivation above is the whole of what the nonce depends on. It
|
||||
* does NOT commit to the pubshares, to the untweaked threshold public key, or
|
||||
* to which tweaks the cache accumulated -- only to the x-only encoding of the
|
||||
* _tweaked_ threshold public key (this is BIP 445's det_nonce_hash, not a
|
||||
* deviation). Two tweak caches can therefore agree on that x-only key and
|
||||
* still disagree on the sign g*gacc that multiplies the secret share, because
|
||||
* Q and -Q have the same x-coordinate: a cache initialized from the threshold
|
||||
* public key and one initialized from its negation are the simplest example.
|
||||
* Two calls that differ only in that way emit the SAME pubnonce and two
|
||||
* partial signatures that differ only in the sign of the secret-share term,
|
||||
* which is two equations in the nonce and the secret share -- the secret
|
||||
* share falls out of the pair.
|
||||
*
|
||||
* The caller must therefore treat the tweak cache and the pubshares as fixed
|
||||
* key material belonging to the group, established once at key generation,
|
||||
* and never as per-session parameters accepted from a coordinator or any
|
||||
* other peer. Given that, repeating a call reproduces a byte-identical result
|
||||
* and is harmless, which is the point of a deterministic nonce.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or signing fails, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: partial_sig: pointer to a partial_sig object
|
||||
* pubnonce: pointer to a pubnonce object holding this signer's
|
||||
* public nonce, to be sent to the coordinator
|
||||
* In: secshare32: pointer to the signer's 32-byte secret share
|
||||
* my_id: this signer's identifier
|
||||
* aggothernonce: pointer to the aggregate of the other signers'
|
||||
* public nonces, or NULL for a sole signer
|
||||
* ids: array of the u signer identifiers
|
||||
* pubshares: array of u pubkeys with the signers' public
|
||||
* shares, or NULL
|
||||
* n_signers: number of signers u
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
* tweak_cache: pointer to the tweak cache
|
||||
* msg: pointer to the message to sign, or NULL if
|
||||
* msglen is 0
|
||||
* msglen: length of msg
|
||||
* aux_rand32: pointer to 32 bytes of auxiliary randomness mixed
|
||||
* into the nonce derivation, or NULL
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_deterministic_sign(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_partial_sig *partial_sig,
|
||||
secp256k1_frost_pubnonce *pubnonce,
|
||||
const unsigned char *secshare32,
|
||||
uint32_t my_id,
|
||||
const secp256k1_frost_aggnonce *aggothernonce,
|
||||
const uint32_t *ids,
|
||||
const secp256k1_pubkey *pubshares,
|
||||
size_t n_signers,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
const secp256k1_frost_tweak_cache *tweak_cache,
|
||||
const unsigned char *msg,
|
||||
size_t msglen,
|
||||
const unsigned char *aux_rand32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(12) SECP256K1_ARG_NONNULL(13);
|
||||
|
||||
/** Verify a partial signature.
|
||||
*
|
||||
* Implements PartialSigVerify of BIP 445. The same ids array that was passed
|
||||
* to `frost_session_init` must be passed here; signer_index is the index
|
||||
* into that array identifying the signer whose partial signature is
|
||||
* verified.
|
||||
*
|
||||
* Returns: 1 if the partial signature is valid, 0 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: partial_sig: pointer to the partial signature
|
||||
* pubnonce: pointer to the signer's public nonce
|
||||
* pubshare: pointer to the signer's public share
|
||||
* session: pointer to the session
|
||||
* ids: array of the u signer identifiers (identical to
|
||||
* session_init)
|
||||
* n_signers: number of signers u
|
||||
* signer_index: index of the signer in the ids array
|
||||
*/
|
||||
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 uint32_t *ids,
|
||||
size_t n_signers,
|
||||
size_t signer_index
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
|
||||
|
||||
/** Aggregate partial signatures into a BIP340 signature.
|
||||
*
|
||||
* Implements PartialSigAgg of BIP 445. The number of partial signatures must
|
||||
* equal the number of signers u given to `frost_session_init`. The partial
|
||||
* signature at index i must belong to the signer with ids[i].
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or a partial signature cannot be
|
||||
* parsed, 1 otherwise. Note that 1 does NOT mean that the resulting
|
||||
* signature verifies; invalid partial signatures are only detected
|
||||
* by `frost_partial_sig_verify`.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: sig64: pointer to a 64-byte array to store the final
|
||||
* BIP340 signature
|
||||
* error_index: if non-NULL, set to the index of the offending
|
||||
* partial signature on failure
|
||||
* In: session: pointer to the session
|
||||
* partial_sigs: input array of pointers to partial signatures
|
||||
* n_sigs: length of the partial_sigs array. Must equal
|
||||
* n_signers from `frost_session_init`.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_partial_sig_agg(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *sig64,
|
||||
size_t *error_index,
|
||||
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(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,406 +0,0 @@
|
||||
#ifndef SECP256K1_FROST_ENROLLMENT_H
|
||||
#define SECP256K1_FROST_ENROLLMENT_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
#include "secp256k1_frost.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** This module implements FROST enrollment, the protocol of Section 4.1.1 of
|
||||
* the FROST paper (https://eprint.iacr.org/2017/1155.pdf). It converts a
|
||||
* (t, n) FROST group into a (t, n+1) group without re-running key generation
|
||||
* and without any participant revealing its secret share. The same mechanism
|
||||
* repairs a lost share: running it with the target identifier of an existing
|
||||
* participant reproduces exactly that participant's share.
|
||||
*
|
||||
* 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!
|
||||
*
|
||||
* Unlike the frost module, this protocol has no BIP. It is specified only by
|
||||
* the paper and by the reference proof of concept at
|
||||
* https://github.com/siv2r/frost-enrollment. The tagged hash strings and the
|
||||
* parameter serialization used here are this module's own, frozen by the
|
||||
* regression vectors in src/modules/frost_enrollment/vectors.h.
|
||||
*
|
||||
* Identifiers follow the frost module: participants are identified by uint32
|
||||
* values 0..n-1, and participant id i sits at polynomial x-coordinate i+1.
|
||||
* The party receiving a share is identified by `new_id`, which selects the
|
||||
* mode:
|
||||
*
|
||||
* - enrollment: new_id == n_participants. The group grows to n+1
|
||||
* participants, so n_participants must be strictly smaller than
|
||||
* SECP256K1_FROST_MAX_PARTICIPANTS.
|
||||
* - repair: new_id < n_participants. The share of an existing participant
|
||||
* is reproduced; n_participants may be SECP256K1_FROST_MAX_PARTICIPANTS.
|
||||
*
|
||||
* In both modes new_id must not appear in the helper id set.
|
||||
*
|
||||
* A run involves u helpers (threshold <= u <= n_participants), all of which
|
||||
* must be existing participants, plus the target participant:
|
||||
*
|
||||
* 1. Round 1.1: every helper runs
|
||||
* `secp256k1_frost_enrollment_shares_gen`, keeps the output share at
|
||||
* its own position and sends each other output share, together with the
|
||||
* returned parameters hash, to the helper it is aligned with.
|
||||
* 2. Round 1.2: every helper runs
|
||||
* `secp256k1_frost_enrollment_share_agg` over the share it kept and the
|
||||
* shares it received. The function recomputes the parameters hash and
|
||||
* compares every received hash against it, then sums the shares into a
|
||||
* single value that is sent to the target participant along with the
|
||||
* hash.
|
||||
* 3. Round 2: the target participant runs
|
||||
* `secp256k1_frost_enrollment_secshare_gen` over the u received values.
|
||||
* It recomputes the parameters hash from the group key and parameters it
|
||||
* believes, sums the values into its secret share, and verifies the
|
||||
* result against the expected public share obtained from
|
||||
* `secp256k1_frost_enrollment_pubshare_derive`.
|
||||
*
|
||||
* Afterwards, in enrollment mode, all participants must consistently update
|
||||
* their record of n from n to n+1, and extend their table of public shares
|
||||
* with the output of `secp256k1_frost_enrollment_pubshare_derive`.
|
||||
*
|
||||
* SECURITY: the values exchanged in rounds 1 and 2 are additive shares of
|
||||
* real secret shares. They MUST be transmitted over confidential and
|
||||
* authenticated channels. Like the chilldkg module, this module handles bytes
|
||||
* only; transport is the caller's responsibility. Furthermore, the protocol
|
||||
* contains no authorization step: any party that convinces t helpers to run
|
||||
* it at a given identifier receives a valid share for that identifier. In
|
||||
* repair mode that is an existing participant's actual share. Deciding who
|
||||
* may be enrolled is a caller-side precondition.
|
||||
*
|
||||
* It is recommended to read the documentation in this include file carefully.
|
||||
* Further notes on API usage can be found in
|
||||
* src/modules/frost_enrollment/frost_enrollment.md.
|
||||
*/
|
||||
|
||||
/** Compute the enrollment parameters hash.
|
||||
*
|
||||
* The hash is
|
||||
*
|
||||
* out32 = tagged_hash("FROST enrollment/params_hash",
|
||||
* cbytes(thresh_pk) || ser32(n_participants) ||
|
||||
* ser32(threshold) || ser32(new_id) || ser32(n_ids) ||
|
||||
* ser32(sorted_ids[0]) || ... ||
|
||||
* ser32(sorted_ids[n_ids-1]))
|
||||
*
|
||||
* where cbytes is the 33-byte compressed serialization, ser32 is the 32-bit
|
||||
* big-endian encoding and sorted_ids is `ids` in ascending order. Sorting
|
||||
* makes the hash independent of the order in which a caller lists the helper
|
||||
* set; the alignment of every other array in this API follows the caller's
|
||||
* own `ids` order.
|
||||
*
|
||||
* Binding the threshold public key is what makes the hash identify a GROUP
|
||||
* rather than a tuple of numbers: two unrelated groups that happen to share
|
||||
* (t, n, ids, new_id) produce different hashes.
|
||||
*
|
||||
* Every party computes this value itself. The hash checks performed by
|
||||
* `secp256k1_frost_enrollment_share_agg` and
|
||||
* `secp256k1_frost_enrollment_secshare_gen` compare a received hash against a
|
||||
* freshly recomputed one; they are never an equality test between two
|
||||
* caller-supplied strings.
|
||||
*
|
||||
* This function operates on public data only.
|
||||
*
|
||||
* This is also the natural place to pre-validate a parameter tuple: it
|
||||
* enforces exactly the same constraints as the four functions below, and
|
||||
* nothing else. Note that an unusable `thresh_pk` object is API MISUSE, not
|
||||
* an invalid parameter: like every other entry point in the library, this
|
||||
* function reports it through the illegal-argument callback rather than by
|
||||
* returning 0.
|
||||
*
|
||||
* Returns: 0 if the parameters are invalid, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: out32: pointer to a 32-byte array for the hash. Set to zero
|
||||
* if this function returns 0.
|
||||
* In: thresh_pk: pointer to the threshold public key of the group
|
||||
* ids: array of the u helper identifiers. Every id must be
|
||||
* unique, smaller than n_participants and different
|
||||
* from new_id; the order is irrelevant.
|
||||
* n_ids: number of helpers u. Must be between threshold and
|
||||
* n_participants.
|
||||
* new_id: identifier of the participant receiving the share.
|
||||
* Must equal n_participants (enrollment) or be smaller
|
||||
* than it (repair).
|
||||
* n_participants: total number of participants n. Must be at most
|
||||
* SECP256K1_FROST_MAX_PARTICIPANTS, and strictly
|
||||
* smaller in enrollment mode.
|
||||
* threshold: threshold t. Must be at least 2 (see
|
||||
* frost_enrollment.md) and at most n_participants.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_params_hash(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *out32,
|
||||
const secp256k1_pubkey *thresh_pk,
|
||||
const uint32_t *ids,
|
||||
size_t n_ids,
|
||||
uint32_t new_id,
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Round 1.1: generate a helper's enrollment shares.
|
||||
*
|
||||
* Computes v = lambda_my_id(x_new) * secshare, where lambda is the Lagrange
|
||||
* basis polynomial of my_id over the helper set evaluated at the target
|
||||
* x-coordinate, and splits v into u additive shares that sum to v.
|
||||
*
|
||||
* `shares32_out` is aligned with `ids`: entry j is destined for the helper
|
||||
* with identifier ids[j] and must be sent to it over a confidential,
|
||||
* authenticated channel, together with `params_hash32_out`. The entry at
|
||||
* my_id's own position is not sent anywhere; it is passed back into
|
||||
* `secp256k1_frost_enrollment_share_agg` in round 1.2.
|
||||
*
|
||||
* The masking shares are derived from `session_secrand32` by tagged hashing,
|
||||
* masked with the secret share as defense in depth against a broken random
|
||||
* number generator (as `secp256k1_frost_nonce_gen` does). The derivation
|
||||
* binds the parameters hash, and therefore the group key and the whole
|
||||
* parameter tuple, for DOMAIN SEPARATION: two runs that share a seed but
|
||||
* differ in group or parameters must not produce the same shares, because
|
||||
* differencing the round-1.2 outputs would then leak share information. This
|
||||
* binding cannot detect a parameter disagreement between helpers — these
|
||||
* values are per-helper private randomness that nothing cross-checks — which
|
||||
* is what the parameters hash comparison in round 1.2 is for.
|
||||
*
|
||||
* `session_secrand32` must be fresh uniformly random 32 bytes for every run.
|
||||
* It is wiped before this function returns. `secshare32` is left unmodified.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: shares32_out: pointer to an array of u*32 bytes for the enrollment
|
||||
* shares, aligned with `ids`. Not written at all when
|
||||
* the parameter tuple is rejected, since its extent is
|
||||
* only known to be u*32 once n_ids has been validated
|
||||
* against the rest of the tuple; zeroed on every
|
||||
* failure detected after that point.
|
||||
* params_hash32_out: pointer to a 32-byte array for the parameters hash,
|
||||
* identical to what
|
||||
* `secp256k1_frost_enrollment_params_hash` returns for
|
||||
* the same arguments. Set to zero if this function
|
||||
* returns 0.
|
||||
* In/Out:
|
||||
* session_secrand32: pointer to a 32-byte array of fresh randomness. Must
|
||||
* not be reused across runs. Wiped by this function.
|
||||
* In: secshare32: pointer to the 32-byte secret share of my_id
|
||||
* thresh_pk: pointer to the threshold public key of the group
|
||||
* ids: array of the u helper identifiers. Every id must be
|
||||
* unique, smaller than n_participants and different
|
||||
* from new_id; the order is irrelevant but fixes the
|
||||
* alignment of `shares32_out`.
|
||||
* n_ids: number of helpers u. Must be between threshold and
|
||||
* n_participants.
|
||||
* my_id: own identifier. Must appear in `ids`.
|
||||
* new_id: identifier of the participant receiving the share.
|
||||
* Must equal n_participants (enrollment) or be smaller
|
||||
* than it (repair).
|
||||
* n_participants: total number of participants n. Must be at most
|
||||
* SECP256K1_FROST_MAX_PARTICIPANTS, and strictly
|
||||
* smaller in enrollment mode.
|
||||
* threshold: threshold t. Must be at least 2 (see
|
||||
* frost_enrollment.md) and at most n_participants.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_shares_gen(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *shares32_out,
|
||||
unsigned char *params_hash32_out,
|
||||
unsigned char *session_secrand32,
|
||||
const unsigned char *secshare32,
|
||||
const secp256k1_pubkey *thresh_pk,
|
||||
const uint32_t *ids,
|
||||
size_t n_ids,
|
||||
uint32_t my_id,
|
||||
uint32_t new_id,
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7);
|
||||
|
||||
/** Round 1.2: check parameter agreement and aggregate a helper's enrollment
|
||||
* shares.
|
||||
*
|
||||
* The function recomputes its own parameters hash from `thresh_pk` and the
|
||||
* parameter tuple it is given, and compares every entry of
|
||||
* `received_params_hashes32` against it. It then sums the shares.
|
||||
*
|
||||
* `mismatch_id` reports fault attribution for BOTH ways a specific helper's
|
||||
* contribution can be at fault:
|
||||
*
|
||||
* - its parameters hash disagrees with the recomputed one, meaning that
|
||||
* helper ran round 1.1 on a different parameter tuple or in a different
|
||||
* group;
|
||||
* - its entry in `all_shares32` is not a valid scalar (it is not smaller
|
||||
* than the group order), meaning the value was corrupted in transit or
|
||||
* fabricated.
|
||||
*
|
||||
* In both cases the function returns 0 and, if `mismatch_id` is not NULL,
|
||||
* stores the IDENTIFIER of the responsible helper there -- not an array
|
||||
* index, which would be ambiguous because identifiers need not be 0..u-1.
|
||||
* The two causes are not distinguished, so a caller should not report one of
|
||||
* them specifically. Note that the second cause can name the CALLER'S OWN
|
||||
* identifier, since the share kept locally is summed along with the rest.
|
||||
*
|
||||
* `mismatch_id` is set to UINT32_MAX when the failure has neither cause,
|
||||
* which covers every invalid-parameter and API-misuse case.
|
||||
*
|
||||
* Note the deliberately OPPOSITE own-slot conventions of the two u*32 input
|
||||
* buffers, both of which are aligned with `ids`:
|
||||
*
|
||||
* - `all_shares32`: the entry at my_id's position IS read. It is the share
|
||||
* `secp256k1_frost_enrollment_shares_gen` kept locally.
|
||||
* - `received_params_hashes32`: the entry at my_id's position is NEVER
|
||||
* read, and may be left zero. The own hash is recomputed, never taken
|
||||
* from a buffer.
|
||||
*
|
||||
* The asymmetry is the point: it is what makes this a recomputation check
|
||||
* rather than an equality test among caller-supplied strings, so a caller
|
||||
* cannot launder a mismatch into a pass by filling its own slot with a
|
||||
* received value.
|
||||
*
|
||||
* `sigma32_out` must be sent to the target participant over a confidential,
|
||||
* authenticated channel, together with the parameters hash.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or a parameters hash disagrees,
|
||||
* 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: sigma32_out: pointer to a 32-byte array for the aggregated share.
|
||||
* Set to zero if this function returns 0.
|
||||
* mismatch_id: pointer to an identifier to store the first helper
|
||||
* whose parameters hash disagrees, or NULL
|
||||
* In: all_shares32: pointer to an array of u*32 bytes, aligned with
|
||||
* `ids`: the share kept locally at my_id's position and
|
||||
* the shares received from the other helpers at theirs
|
||||
* received_params_hashes32: pointer to an array of u*32 bytes, aligned
|
||||
* with `ids`, holding the parameters hash received from
|
||||
* each other helper. The entry at my_id's position is
|
||||
* ignored.
|
||||
* thresh_pk: pointer to the threshold public key of the group
|
||||
* ids: array of the u helper identifiers, in the same order
|
||||
* as in round 1.1
|
||||
* n_ids: number of helpers u
|
||||
* my_id: own identifier. Must appear in `ids`.
|
||||
* new_id: identifier of the participant receiving the share
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_share_agg(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *sigma32_out,
|
||||
uint32_t *mismatch_id,
|
||||
const unsigned char *all_shares32,
|
||||
const unsigned char *received_params_hashes32,
|
||||
const secp256k1_pubkey *thresh_pk,
|
||||
const uint32_t *ids,
|
||||
size_t n_ids,
|
||||
uint32_t my_id,
|
||||
uint32_t new_id,
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7);
|
||||
|
||||
/** Derive the public share at the target identifier.
|
||||
*
|
||||
* Computes sum_i lambda_i(x_new) * pubshares[i], the value of the group's
|
||||
* public-share polynomial at the target participant's x-coordinate. This is
|
||||
* the public counterpart of what the protocol produces, and it is used both
|
||||
* to verify the new secret share in round 2 and to extend the group's table
|
||||
* of public shares from n to n+1 entries after an enrollment.
|
||||
*
|
||||
* This function operates on public data only.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or the result is the point at
|
||||
* infinity, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: new_pubshare_out: pointer to a pubkey object for the derived public
|
||||
* share. Set to zero if this function returns 0.
|
||||
* In: pubshares: array of u pubkeys, aligned with `ids`, holding the
|
||||
* public share of each helper
|
||||
* ids: array of the u helper identifiers
|
||||
* n_ids: number of helpers u
|
||||
* new_id: identifier of the participant receiving the share
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_pubshare_derive(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_pubkey *new_pubshare_out,
|
||||
const secp256k1_pubkey *pubshares,
|
||||
const uint32_t *ids,
|
||||
size_t n_ids,
|
||||
uint32_t new_id,
|
||||
size_t n_participants,
|
||||
uint32_t threshold
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Round 2: derive the target participant's secret share.
|
||||
*
|
||||
* In order:
|
||||
*
|
||||
* 1. If `expected_params_hash32` is not NULL, the parameters hash is
|
||||
* recomputed from `thresh_pk` and the parameter tuple the target
|
||||
* participant believes, and compared against it. The round-1.2 check
|
||||
* covers helper against helper only; without this check, all helpers
|
||||
* could agree on parameters that differ from the ones the target
|
||||
* expects, or belong to a different group entirely, and the discrepancy
|
||||
* would surface only as an unexplained failure of the public-share
|
||||
* check.
|
||||
* 2. The u values are summed into `secshare32_out`.
|
||||
* 3. If `expected_pubshare` is not NULL, secshare*G is compared against it.
|
||||
*
|
||||
* `expected_pubshare` is load-bearing: it is the only check that a helper
|
||||
* contributed a correct value. Pass NULL only if the resulting share is
|
||||
* validated by other means.
|
||||
*
|
||||
* PRECONDITION, documented but not enforced: `thresh_pk` must come from a
|
||||
* source the target participant authenticates independently of the helpers,
|
||||
* and `expected_pubshare` must be derived from public shares validated
|
||||
* against it with `secp256k1_frost_threshold_info_validate`. Otherwise both
|
||||
* checks are circular: t colluding helpers can present a consistent but
|
||||
* fabricated polynomial, and every check in this function passes on a
|
||||
* worthless share. See frost_enrollment.md and examples/frost_enrollment.c.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or a check fails, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: secshare32_out: pointer to a 32-byte array for the secret share. Set
|
||||
* to zero if this function returns 0.
|
||||
* In: sigmas32: pointer to an array of u*32 bytes, aligned with
|
||||
* `ids`, holding the value received from each helper
|
||||
* thresh_pk: pointer to the INDEPENDENTLY AUTHENTICATED threshold
|
||||
* public key of the group
|
||||
* ids: array of the u helper identifiers, in the same order
|
||||
* as `sigmas32`
|
||||
* n_ids: number of helpers u
|
||||
* new_id: own identifier, the one the share is being derived
|
||||
* for
|
||||
* n_participants: total number of participants n
|
||||
* threshold: threshold t
|
||||
* expected_params_hash32: pointer to the 32-byte parameters hash received
|
||||
* from the helpers, or NULL to skip the comparison
|
||||
* expected_pubshare: pointer to the expected public share, from
|
||||
* `secp256k1_frost_enrollment_pubshare_derive`, or NULL
|
||||
* to skip the verification (not recommended). When it
|
||||
* is non-NULL, ctx must have been initialized for
|
||||
* signing; when it is NULL, no context capability
|
||||
* beyond the default is required.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_frost_enrollment_secshare_gen(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *secshare32_out,
|
||||
const unsigned char *sigmas32,
|
||||
const secp256k1_pubkey *thresh_pk,
|
||||
const uint32_t *ids,
|
||||
size_t n_ids,
|
||||
uint32_t new_id,
|
||||
size_t n_participants,
|
||||
uint32_t threshold,
|
||||
const unsigned char *expected_params_hash32,
|
||||
const secp256k1_pubkey *expected_pubshare
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_FROST_ENROLLMENT_H */
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SECP256K1_GENERATOR_H
|
||||
# define SECP256K1_GENERATOR_H
|
||||
#ifndef _SECP256K1_GENERATOR_
|
||||
# define _SECP256K1_GENERATOR_
|
||||
|
||||
# include "secp256k1.h"
|
||||
|
||||
@@ -17,48 +17,43 @@ extern "C" {
|
||||
* If you need to convert to a format suitable for storage, transmission, or
|
||||
* comparison, use secp256k1_generator_serialize and secp256k1_generator_parse.
|
||||
*/
|
||||
typedef struct secp256k1_generator {
|
||||
typedef struct {
|
||||
unsigned char data[64];
|
||||
} secp256k1_generator;
|
||||
|
||||
/**
|
||||
* Static constant generator 'h' maintained for historical reasons.
|
||||
*/
|
||||
SECP256K1_API const secp256k1_generator *secp256k1_generator_h;
|
||||
|
||||
/** Parse a 33-byte generator byte sequence into a generator object.
|
||||
*
|
||||
* Returns: 1 if input contains a valid generator.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Args: ctx: a secp256k1 context object.
|
||||
* Out: gen: pointer to the output generator object
|
||||
* In: input: pointer to a 33-byte serialized generator
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_generator_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_generator *gen,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_generator* gen,
|
||||
const unsigned char *input
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a 33-byte generator into a serialized byte sequence.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: output: pointer to a 33-byte byte array
|
||||
* In: gen: pointer to a generator object
|
||||
* Args: ctx: a secp256k1 context object.
|
||||
* Out: output: a pointer to a 33-byte byte array
|
||||
* In: gen: a pointer to a generator
|
||||
*/
|
||||
SECP256K1_API int secp256k1_generator_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *output,
|
||||
const secp256k1_generator *gen
|
||||
const secp256k1_generator* gen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Generate a generator for the curve.
|
||||
*
|
||||
* Returns: 0 in the highly unlikely case the seed is not acceptable,
|
||||
* 1 otherwise.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: gen: pointer to a the new generator object
|
||||
* In: seed32: 32-byte seed
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: gen: a generator object
|
||||
* In: seed32: a 32-byte seed
|
||||
*
|
||||
* If successful a valid generator will be placed in gen. The produced
|
||||
* generators are distributed uniformly over the curve, and will not have a
|
||||
@@ -66,8 +61,8 @@ SECP256K1_API int secp256k1_generator_serialize(
|
||||
* or to the base generator G.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_generator_generate(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_generator *gen,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_generator* gen,
|
||||
const unsigned char *seed32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
@@ -75,165 +70,22 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_generator_generate(
|
||||
*
|
||||
* Returns: 0 in the highly unlikely case the seed is not acceptable or when
|
||||
* blind is out of range. 1 otherwise.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: gen: pointer to a generator object
|
||||
* In: seed32: 32-byte seed
|
||||
* blind32: 32-byte secret value to blind the generator with.
|
||||
* Args: ctx: a secp256k1 context object, initialized for signing
|
||||
* Out: gen: a generator object
|
||||
* In: seed32: a 32-byte seed
|
||||
* blind32: a 32-byte secret value to blind the generator with.
|
||||
*
|
||||
* The result is equivalent to first calling secp256k1_generator_generate,
|
||||
* converting the result to a public key, calling secp256k1_ec_pubkey_tweak_add,
|
||||
* and then converting back to generator form.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_generator_generate_blinded(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_generator *gen,
|
||||
const unsigned char *seed32,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_generator* gen,
|
||||
const unsigned char *key32,
|
||||
const unsigned char *blind32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Opaque data structure that stores a Pedersen commitment
|
||||
*
|
||||
* The exact representation of data inside is implementation defined and not
|
||||
* guaranteed to be portable between different platforms or versions. It is
|
||||
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
|
||||
* If you need to convert to a format suitable for storage, transmission, or
|
||||
* comparison, use secp256k1_pedersen_commitment_serialize and
|
||||
* secp256k1_pedersen_commitment_parse.
|
||||
*/
|
||||
typedef struct secp256k1_pedersen_commitment {
|
||||
unsigned char data[64];
|
||||
} secp256k1_pedersen_commitment;
|
||||
|
||||
/** Parse a 33-byte commitment into a commitment object.
|
||||
*
|
||||
* Returns: 1 if input contains a valid commitment.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: commit: pointer to the output commitment object
|
||||
* In: input: pointer to a 33-byte serialized commitment key
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_commitment_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_pedersen_commitment *commit,
|
||||
const unsigned char *input
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a commitment object into a serialized byte sequence.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: output: pointer to a 33-byte byte array
|
||||
* In: commit: pointer to a secp256k1_pedersen_commitment containing an
|
||||
* initialized commitment
|
||||
*/
|
||||
SECP256K1_API int secp256k1_pedersen_commitment_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *output,
|
||||
const secp256k1_pedersen_commitment *commit
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Generate a pedersen commitment.
|
||||
* Returns 1: Commitment successfully created.
|
||||
* 0: Error. The blinding factor is larger than the group order
|
||||
* (probability for random 32 byte number < 2^-127) or results in the
|
||||
* point at infinity. Retry with a different factor.
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* blind: pointer to a 32-byte blinding factor (cannot be NULL)
|
||||
* value: unsigned 64-bit integer value to commit to.
|
||||
* gen: additional generator 'h'
|
||||
* Out: commit: pointer to the commitment (cannot be NULL)
|
||||
*
|
||||
* Blinding factors can be generated and verified in the same way as secp256k1 private keys for ECDSA.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_commit(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_pedersen_commitment *commit,
|
||||
const unsigned char *blind,
|
||||
uint64_t value,
|
||||
const secp256k1_generator *gen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Computes the sum of multiple positive and negative blinding factors.
|
||||
* Returns 1: Sum successfully computed.
|
||||
* 0: Error. A blinding factor is larger than the group order
|
||||
* (probability for random 32 byte number < 2^-127). Retry with
|
||||
* different factors.
|
||||
* In: ctx: pointer to a context object (cannot be NULL)
|
||||
* blinds: pointer to pointers to 32-byte character arrays for blinding factors. (cannot be NULL)
|
||||
* n: number of factors pointed to by blinds.
|
||||
* npositive: how many of the initial factors should be treated with a positive sign.
|
||||
* Out: blind_out: pointer to a 32-byte array for the sum (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_blind_sum(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *blind_out,
|
||||
const unsigned char * const *blinds,
|
||||
size_t n,
|
||||
size_t npositive
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Verify a tally of pedersen commitments
|
||||
* Returns 1: commitments successfully sum to zero.
|
||||
* 0: Commitments do not sum to zero or other error.
|
||||
* In: ctx: pointer to a context object (cannot be NULL)
|
||||
* commits: pointer to array of pointers to the commitments. (cannot be NULL if pcnt is non-zero)
|
||||
* pcnt: number of commitments pointed to by commits.
|
||||
* ncommits: pointer to array of pointers to the negative commitments. (cannot be NULL if ncnt is non-zero)
|
||||
* ncnt: number of commitments pointed to by ncommits.
|
||||
*
|
||||
* This computes sum(commit[0..pcnt)) - sum(ncommit[0..ncnt)) == 0.
|
||||
*
|
||||
* A pedersen commitment is xG + vA where G and A are generators for the secp256k1 group and x is a blinding factor,
|
||||
* while v is the committed value. For a collection of commitments to sum to zero, for each distinct generator
|
||||
* A all blinding factors and all values must sum to zero.
|
||||
*
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_verify_tally(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_pedersen_commitment * const *commits,
|
||||
size_t pcnt,
|
||||
const secp256k1_pedersen_commitment * const *ncommits,
|
||||
size_t ncnt
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Sets the final Pedersen blinding factor correctly when the generators themselves
|
||||
* have blinding factors.
|
||||
*
|
||||
* Consider a generator of the form A' = A + rG, where A is the "real" generator
|
||||
* but A' is the generator provided to verifiers. Then a Pedersen commitment
|
||||
* P = vA' + r'G really has the form vA + (vr + r')G. To get all these (vr + r')
|
||||
* to sum to zero for multiple commitments, we take three arrays consisting of
|
||||
* the `v`s, `r`s, and `r'`s, respectively called `value`s, `generator_blind`s
|
||||
* and `blinding_factor`s, and sum them.
|
||||
*
|
||||
* The function then subtracts the sum of all (vr + r') from the last element
|
||||
* of the `blinding_factor` array, setting the total sum to zero.
|
||||
*
|
||||
* Returns 1: Blinding factor successfully computed.
|
||||
* 0: Error. A blinding_factor or generator_blind are larger than the group
|
||||
* order (probability for random 32 byte number < 2^-127). Retry with
|
||||
* different values.
|
||||
*
|
||||
* In: ctx: pointer to a context object
|
||||
* value: array of asset values, `v` in the above paragraph.
|
||||
* May not be NULL unless `n_total` is 0.
|
||||
* generator_blind: array of asset blinding factors, `r` in the above paragraph
|
||||
* May not be NULL unless `n_total` is 0.
|
||||
* n_total: Total size of the above arrays
|
||||
* n_inputs: How many of the initial array elements represent commitments that
|
||||
* will be negated in the final sum
|
||||
* In/Out: blinding_factor: array of commitment blinding factors, `r'` in the above paragraph
|
||||
* May not be NULL unless `n_total` is 0.
|
||||
* the last value will be modified to get the total sum to zero.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_blind_generator_blind_sum(
|
||||
const secp256k1_context *ctx,
|
||||
const uint64_t *value,
|
||||
const unsigned char * const *generator_blind,
|
||||
unsigned char * const *blinding_factor,
|
||||
size_t n_total,
|
||||
size_t n_inputs
|
||||
);
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
@@ -1,748 +0,0 @@
|
||||
#ifndef SECP256K1_ICEBERG_H
|
||||
#define SECP256K1_ICEBERG_H
|
||||
|
||||
#include "secp256k1_musig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/** This module implements Iceberg, a threshold scheme that lets a group of
|
||||
* parties stand in for a single MuSig2 (BIP 327) participant.
|
||||
*
|
||||
* WARNING: EXPERIMENTAL. Neither the scheme nor this implementation has been
|
||||
* reviewed by anyone outside the project, and should not be used to protect
|
||||
* anything of value.
|
||||
*
|
||||
* The group produces one ordinary MuSig2 public nonce and one ordinary MuSig2
|
||||
* partial signature, so cosigners cannot tell a group is involved and need no
|
||||
* changes.
|
||||
*
|
||||
* The order of calls, once per group and then once per signature:
|
||||
*
|
||||
* setup a distributed key generation produces one share each, then
|
||||
* pubshare_gen and pubkey_agg give the group's public key,
|
||||
* which goes into musig_pubkey_agg beside the cosigners'
|
||||
* round 1 each member of the quorum runs nonce_gen; nonce_agg turns
|
||||
* those into one ordinary musig_pubnonce, and from there
|
||||
* upwards this is plain MuSig2
|
||||
* round 2 each member runs partial_sign, which takes the message, the
|
||||
* cosigners' nonce and the round-one contributions;
|
||||
* partial_sig_agg turns the results into one ordinary
|
||||
* musig_partial_sig
|
||||
*
|
||||
* You can find an example demonstrating the iceberg module in
|
||||
* examples/iceberg.c. Further notes on API usage can be found in
|
||||
* doc/iceberg.md.
|
||||
*
|
||||
* Nonces are derived from a session label, sid32 below, rather than stored,
|
||||
* so no signer holds a secret nonce between the two rounds. A label is a
|
||||
* 32-byte value the caller chooses, one per signing attempt; it is public and
|
||||
* it need not be random, but it must never be used twice. That is narrower
|
||||
* than keeping no state at all: a member still has to remember which labels it
|
||||
* has already answered under. Choosing labels is the one thing this API cannot
|
||||
* do for you, so read the note on secp256k1_iceberg_partial_sign before
|
||||
* writing any of this.
|
||||
*
|
||||
* Three things constrain how this can be used:
|
||||
*
|
||||
* 1. Both rounds need a quorum of 2t-1 members online, of whom up to t-1 may
|
||||
* be corrupt. Since 2t-1 members have to exist, the threshold is at most
|
||||
* half the group rounded up: 2-of-3 and 3-of-5 can be expressed where
|
||||
* 2-of-2 and 3-of-4 cannot. Constraint 3 then rules out 2-of-3 and 3-of-5
|
||||
* as well, leaving 2-of-4 as the smallest usable group.
|
||||
*
|
||||
* 2. Messages between participants must travel over authenticated channels, and
|
||||
* a contribution must be accepted only from the member its index names.
|
||||
*
|
||||
* Every check below works by outnumbering the at most t-1 corrupt members
|
||||
* with t honest ones, so that count has to be real, and this library cannot
|
||||
* take it for you. It can tell that an index names a member of the group and
|
||||
* that no two contributions claim the same one. It cannot tell who sent
|
||||
* anything, and no check on the values could: the values of both rounds lie
|
||||
* on a polynomial of degree t-1, so once t of them are public, anyone can
|
||||
* compute the rest. A contribution that verifies proves the value is right,
|
||||
* which is a public property, not that the named member produced it.
|
||||
*
|
||||
* What that costs if it is skipped: an adversary supplying t of the 2t-1
|
||||
* chooses the group's nonce. It picks a polynomial through the t-1 honest
|
||||
* points it has to match, which leaves a coefficient free, and computes its
|
||||
* own points on that. Every check here then passes.
|
||||
*
|
||||
* 3. The group must agree what it is signing before anyone answers, and
|
||||
* agreeing while up to t-1 members misbehave needs n >= 3t-2 members to
|
||||
* exist: 4 for a threshold of 2, 7 for 3, 10 for 4. Nothing here checks
|
||||
* that, because it is a property of the group rather than of the
|
||||
* arithmetic. It is still the bound to size a deployment with.
|
||||
*/
|
||||
|
||||
/** Maximum number of participants in a group.
|
||||
*
|
||||
* A participant holds one 32-byte seed per (t-1)-subset it is not a member of,
|
||||
* so both storage and signing cost grow as C(n-1, t-1): 2 seeds at 2-of-3, 6 at
|
||||
* 3-of-5, 126 at 5-of-10.
|
||||
*
|
||||
* It sizes two of the opaque types below, so it is part of this library's ABI
|
||||
* and not something one member of a group can change on its own.
|
||||
*/
|
||||
#define SECP256K1_ICEBERG_MAX_PARTICIPANTS 10
|
||||
|
||||
/** Largest number of seeds one participant can hold, C(9, 4): the maximum of
|
||||
* C(n-1, t-1) over every expressible configuration, since a quorum of 2t-1 has
|
||||
* to fit in the group and so t is at most (n+1)/2.
|
||||
*
|
||||
* This is the number that makes a group expensive: it sizes two of the opaque
|
||||
* types below, and the module's largest stack frames with them.
|
||||
*/
|
||||
#define SECP256K1_ICEBERG_MAX_SEEDS 126
|
||||
|
||||
/** Largest buffer secp256k1_iceberg_share_serialize can need. A share of a
|
||||
* particular group serializes to 4 + 32*C(n-1, t-1) bytes, which is smaller
|
||||
* for every configuration except the largest; use this to size a fixed buffer
|
||||
* without computing binomials. */
|
||||
#define SECP256K1_ICEBERG_SHARE_MAX_LEN (4 + 32 * SECP256K1_ICEBERG_MAX_SEEDS)
|
||||
|
||||
/** Opaque data structures
|
||||
*
|
||||
* The layout inside these is implementation defined and not portable between
|
||||
* platforms or versions. Use the serialization and parsing functions to move
|
||||
* them between machines or to storage. They contain no pointers, so they can
|
||||
* be copied and moved. But a copy of a share is another copy of the secret,
|
||||
* and erasing one means erasing all of them.
|
||||
*
|
||||
* A stray pointer or an uninitialized struct is caught rather than used.
|
||||
*
|
||||
* Every participant has an index in 1..n, assigned when the group is dealt. It
|
||||
* travels inside the share and inside every object derived from one, so the
|
||||
* aggregation functions can tell contributions apart, and a contribution
|
||||
* separated from its index is not usable.
|
||||
*
|
||||
* The index is a claim the object makes about itself. This library checks only
|
||||
* that it names a member the group has: everything above n is refused, and
|
||||
* within 1..n nothing here tells a genuine contribution from one an adversary
|
||||
* wrote. Binding it to a member is constraint 2 above.
|
||||
*/
|
||||
|
||||
/** Opaque data structure holding a participant's long-term secret share.
|
||||
*
|
||||
* This is the material to guard and to back up, and it is not one number: it
|
||||
* is a collection of 32-byte seeds, one for every group of t-1 participants
|
||||
* that this participant is NOT a member of.
|
||||
*
|
||||
* There is no resharing here, so a compromise or a change of membership means
|
||||
* dealing the group again from scratch.
|
||||
*
|
||||
* Guaranteed to be 4040 bytes in size: sized for the worst case the
|
||||
* compile-time maximum allows, whatever n and t you actually use. Serializing
|
||||
* writes only the seeds in use.
|
||||
*/
|
||||
typedef struct secp256k1_iceberg_share {
|
||||
unsigned char data[8 + 32 * SECP256K1_ICEBERG_MAX_SEEDS];
|
||||
} secp256k1_iceberg_share;
|
||||
|
||||
/** Opaque data structure holding the Lagrange weights derived from a share.
|
||||
*
|
||||
* Despite traveling with the share, this contains NO secret material. The
|
||||
* weights depend only on participant indices and subset structure, both
|
||||
* public, so nothing here needs protecting. It exists purely so that signing
|
||||
* does not repeat the work, and it can be discarded and rebuilt at any time --
|
||||
* which is the only way to move one, since it has no serialized form.
|
||||
*/
|
||||
typedef struct secp256k1_iceberg_share_cache {
|
||||
unsigned char data[8 + 32 * SECP256K1_ICEBERG_MAX_SEEDS];
|
||||
} secp256k1_iceberg_share_cache;
|
||||
|
||||
/** Opaque data structure holding one participant's public key share.
|
||||
*
|
||||
* A commitment to the participant's key share: the point you get by
|
||||
* multiplying that share by the generator, plus the index it belongs to. It
|
||||
* reveals nothing about the share and is meant to be published. This is what
|
||||
* the group aggregates into its public key, and what lets everyone check that
|
||||
* nobody published a wrong one.
|
||||
*
|
||||
* Guaranteed to be 69 bytes in size. Serializes to 34 bytes.
|
||||
*/
|
||||
typedef struct secp256k1_iceberg_pubshare {
|
||||
unsigned char data[69];
|
||||
} secp256k1_iceberg_pubshare;
|
||||
|
||||
/** Derive the Lagrange weights for a share.
|
||||
*
|
||||
* An optimization, not a requirement. Three calls need these weights --
|
||||
* secp256k1_iceberg_pubshare_gen, secp256k1_iceberg_nonce_gen and
|
||||
* secp256k1_iceberg_partial_sign, and each accepts NULL and recomputes them,
|
||||
* at the cost of one scalar inversion and multiplications growing as
|
||||
* C(n-1, t-1). Build one if a participant signs often enough to care.
|
||||
*
|
||||
* The weights depend on n, t and the participant index and on nothing else, so
|
||||
* a cache is checked against those three rather than against the seeds. One
|
||||
* built for a different participant, threshold or group size calls the illegal
|
||||
* callback. One built for a different group of the same shape is accepted: it
|
||||
* holds the same weights the share would have derived.
|
||||
*
|
||||
* Returns: 1 on success. A malformed share calls the illegal callback.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: cache: pointer to a cache object to initialize
|
||||
* In: share: the share to derive weights for
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_share_cache_create(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_share_cache *cache,
|
||||
const secp256k1_iceberg_share *share
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a share. Writes 4 + 32*C(n-1, t-1) bytes.
|
||||
*
|
||||
* The encoding is a version byte, then n, then t, then this participant's
|
||||
* index, then the seeds. Only the seeds are secret. The three before them are
|
||||
* what makes the seeds interpretable (their number is C(n-1, t-1), and which
|
||||
* subset each belongs to follows from n, t and the index), so a caller that
|
||||
* needs any of the three reads it here, off its own buffer.
|
||||
*
|
||||
* On entry outlen is the size of the buffer; on return it is the number of
|
||||
* bytes this share needs, whether or not they fitted. So a short buffer costs
|
||||
* a second call rather than a guess, or size it at
|
||||
* SECP256K1_ICEBERG_SHARE_MAX_LEN and never ask. A malformed share has no
|
||||
* length to report and sets outlen to zero, so a caller looping on it stops
|
||||
* rather than asking for the same buffer forever.
|
||||
*
|
||||
* Returns: 1 on success, 0 if the buffer is too small. A malformed share
|
||||
* calls the illegal callback.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: out: buffer to write to
|
||||
* In/Out: outlen: buffer size in, bytes needed out
|
||||
* In: share: the share to serialize
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_share_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *out,
|
||||
size_t *outlen,
|
||||
const secp256k1_iceberg_share *share
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Parse a share.
|
||||
*
|
||||
* Returns: 1 on success, 0 if the input is malformed.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: share: the share to initialize
|
||||
* In: in: serialized share
|
||||
* inlen: its length
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_share_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_share *share,
|
||||
const unsigned char *in,
|
||||
size_t inlen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Compute this participant's public key share.
|
||||
*
|
||||
* Returns: 1 on success. A malformed share calls the illegal callback.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: pubshare: the public key share
|
||||
* In: share: this participant's share
|
||||
* cache: its Lagrange weights, or NULL to recompute them
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubshare_gen(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_pubshare *pubshare,
|
||||
const secp256k1_iceberg_share *share,
|
||||
const secp256k1_iceberg_share_cache *cache
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a public key share to 34 bytes.
|
||||
*
|
||||
* Returns: 1, or 0 on a malformed public share, which is a caller error and
|
||||
* also calls the illegal callback.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: out34: pointer to a 34-byte array to write to
|
||||
* In: pubshare: the public key share to serialize
|
||||
*/
|
||||
SECP256K1_API int secp256k1_iceberg_pubshare_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *out34,
|
||||
const secp256k1_iceberg_pubshare *pubshare
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Parse a public key share from 34 bytes.
|
||||
*
|
||||
* Returns: 1 on success, 0 if the input does not encode a valid share.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: pubshare: the public key share to initialize
|
||||
* In: in34: pointer to a 34-byte serialized share
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubshare_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_pubshare *pubshare,
|
||||
const unsigned char *in34
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Verify public key shares and combine them into the group public key.
|
||||
*
|
||||
* Checks that the shares lie on a polynomial of degree at most t-1 and then
|
||||
* interpolates them. A participant who published anything else raises the
|
||||
* degree and is caught here, though the check proves only that the set is
|
||||
* inconsistent, not which member is at fault.
|
||||
*
|
||||
* Soundness needs at least t honest points present to pin the true polynomial,
|
||||
* which is where the quorum of 2t-1 comes from: with at most t-1 members
|
||||
* corrupt, 2t-1 contributions leave t honest ones, and t points fix a
|
||||
* polynomial of degree t-1. Those t honest points have to be real: this call
|
||||
* refuses an index above n, and constraint 2 is the caller's half.
|
||||
*
|
||||
* The result is an ordinary public key. Pass it to secp256k1_musig_pubkey_agg
|
||||
* alongside the cosigners' keys exactly as if it belonged to a single signer.
|
||||
*
|
||||
* Returns: 1 on success, 0 if there are fewer than 2t-1 shares or more than
|
||||
* the group size, if the shares are inconsistent, if two carry the
|
||||
* same index, or in the vanishingly unlikely case that they combine
|
||||
* to the point at infinity.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: group_pk: the group's public key
|
||||
* In: pubshares: array of pointers to public key shares
|
||||
* n_pubshares: how many
|
||||
* n: the group size the shares were dealt for. A share
|
||||
* carrying an index above it is rejected: no member sits
|
||||
* there, so nothing could have authenticated it
|
||||
* t: the threshold, at least 1 and at most (n+1)/2. An n or
|
||||
* a t outside its range is a caller bug and calls the
|
||||
* illegal callback
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubkey_agg(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_pubkey *group_pk,
|
||||
const secp256k1_iceberg_pubshare * const *pubshares,
|
||||
size_t n_pubshares,
|
||||
unsigned int n,
|
||||
unsigned int t
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Opaque data structure holding one participant's nonce contribution.
|
||||
*
|
||||
* Guaranteed to be 133 bytes in size. Serialized and parsed with
|
||||
* secp256k1_iceberg_pubnonce_serialize and secp256k1_iceberg_pubnonce_parse.
|
||||
*/
|
||||
typedef struct secp256k1_iceberg_pubnonce {
|
||||
unsigned char data[133];
|
||||
} secp256k1_iceberg_pubnonce;
|
||||
|
||||
/** Opaque data structure holding the group's combined nonce.
|
||||
*
|
||||
* This is the group's internal nonce pair (R1, R2'), before the nesting
|
||||
* coefficient is applied. What goes up to the cosigners is an ordinary
|
||||
* secp256k1_musig_pubnonce instead.
|
||||
*
|
||||
* It is here for an implementation checking its own arithmetic, which needs the
|
||||
* first 66 bytes of the b1 preimage and can get them nowhere else: the published
|
||||
* pair has already had b1 applied and does not yield them back. The coefficient
|
||||
* is
|
||||
* b1 = H_Iceberg/noncecoef(R1 || R2' || P), where P is the group's public key
|
||||
* and each point uses the 33-byte encoding MuSig2 uses for aggregate nonces.
|
||||
* The published pair is (R1, b1*R2'): only the second point is scaled, because
|
||||
* the coefficient enters as b1^(i-1).
|
||||
*
|
||||
* No call here takes one as input, so the serializer below is the only one
|
||||
* that reads it. It is also the only object you can decline to receive:
|
||||
* secp256k1_iceberg_nonce_agg writes it if you want it and skips it if you
|
||||
* pass NULL.
|
||||
*
|
||||
* Guaranteed to be 132 bytes in size. Serialized and parsed with
|
||||
* secp256k1_iceberg_aggnonce_serialize and secp256k1_iceberg_aggnonce_parse.
|
||||
*/
|
||||
typedef struct secp256k1_iceberg_aggnonce {
|
||||
unsigned char data[132];
|
||||
} secp256k1_iceberg_aggnonce;
|
||||
|
||||
/** Opaque data structure holding one participant's signature share.
|
||||
*
|
||||
* Guaranteed to be 37 bytes in size. Serialized and parsed with
|
||||
* secp256k1_iceberg_partial_sig_serialize and
|
||||
* secp256k1_iceberg_partial_sig_parse.
|
||||
*/
|
||||
typedef struct secp256k1_iceberg_partial_sig {
|
||||
unsigned char data[37];
|
||||
} secp256k1_iceberg_partial_sig;
|
||||
|
||||
/** Serialize and parse the round-one and round-two objects.
|
||||
*
|
||||
* pubnonce 67 bytes a member's nonce contribution, index and two points
|
||||
* aggnonce 66 bytes the group's internal aggregate, no index
|
||||
* partial_sig 33 bytes a member's signature share, index and one scalar
|
||||
*
|
||||
* A contribution and a signature share carry their participant index because
|
||||
* everything that consumes them is indexed; an aggregate belongs to the group
|
||||
* rather than to a member and so has none, which is the whole of the 67-versus-
|
||||
* 66 difference. Points use the 33-byte encoding MuSig2 uses for aggregate
|
||||
* nonces, all zero for the point at infinity. The encoding has room for it,
|
||||
* though secp256k1_iceberg_nonce_agg refuses to emit one.
|
||||
*
|
||||
* Parsing rejects an index outside 1..SECP256K1_ICEBERG_MAX_PARTICIPANTS, a
|
||||
* point that is not on the curve, and a signature share that is not a scalar
|
||||
* below the group order. The parsers do not know the group size, so an index
|
||||
* of 9 parses in a group of 5 and is caught when something that does know n
|
||||
* tries to use it. None of these is a security boundary on its own, since a
|
||||
* well-formed lie passes all of them, but each turns a value that would fail
|
||||
* later into one that fails here.
|
||||
*
|
||||
* Returns: for a parser, 1 on success and 0 if the encoding is invalid, which
|
||||
* is an ordinary thing for bytes off a network to be. For a
|
||||
* serializer, 1, or 0 on a malformed object, which is a caller error
|
||||
* and also calls the illegal callback.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out/In: the object and the fixed-size buffer named in each declaration
|
||||
*/
|
||||
SECP256K1_API int secp256k1_iceberg_pubnonce_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *out67,
|
||||
const secp256k1_iceberg_pubnonce *pubnonce
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_pubnonce_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_pubnonce *pubnonce,
|
||||
const unsigned char *in67
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
SECP256K1_API int secp256k1_iceberg_aggnonce_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *out66,
|
||||
const secp256k1_iceberg_aggnonce *aggnonce
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_aggnonce_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_aggnonce *aggnonce,
|
||||
const unsigned char *in66
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
SECP256K1_API int secp256k1_iceberg_partial_sig_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *out33,
|
||||
const secp256k1_iceberg_partial_sig *partial_sig
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sig_parse(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_partial_sig *partial_sig,
|
||||
const unsigned char *in33
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Derive this participant's nonce contribution for a session.
|
||||
*
|
||||
* Depends on the share and the session label and nothing else: no message, no
|
||||
* cosigner nonce, no key aggregation cache. That is what lets this round run
|
||||
* before the message exists, which is the property the whole scheme is built
|
||||
* around, and it is why a member absent from this round is not shut out of the
|
||||
* next one: its contribution is determined, so it can work out what it would
|
||||
* have been. There is no secret nonce object to hold between the rounds --
|
||||
* round two recomputes the value from the share and the same label.
|
||||
*
|
||||
* Returns: 1 on success. A malformed share calls the illegal callback.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* Out: pubnonce: this participant's contribution, to publish
|
||||
* In: share: this participant's share
|
||||
* cache: its Lagrange weights, or NULL to recompute them
|
||||
* sid32: the session label
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_nonce_gen(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_pubnonce *pubnonce,
|
||||
const secp256k1_iceberg_share *share,
|
||||
const secp256k1_iceberg_share_cache *cache,
|
||||
const unsigned char *sid32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||||
SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Verify nonce contributions and combine them.
|
||||
*
|
||||
* Checks both nonce sharings the way key aggregation checks the key sharing,
|
||||
* and for the same reason, then interpolates them into one ordinary MuSig2
|
||||
* public nonce. From that nonce upwards this is plain MuSig2.
|
||||
*
|
||||
* Returns: 1 on success, 0 if there are fewer than 2t-1 contributions or more
|
||||
* than the group size, if they are inconsistent, if two carry the
|
||||
* same index, or if the group's nonce comes out at infinity.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: musig_pubnonce: the group's nonce, to publish to the cosigners
|
||||
* aggnonce: the group's internal nonce, or NULL if you do not
|
||||
* want it. Nothing in this API consumes one
|
||||
* In: pubnonces: the contributions
|
||||
* n_pubnonces: how many
|
||||
* n: the group size. A contribution carrying an index
|
||||
* above it is rejected
|
||||
* t: the threshold, at least 1 and at most (n+1)/2. An n
|
||||
* or a t outside its range is a caller bug and calls
|
||||
* the illegal callback
|
||||
* group_pk: the group's public key. It feeds the nesting
|
||||
* coefficient, so round two must be given the same one
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_nonce_agg(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_musig_pubnonce *musig_pubnonce,
|
||||
secp256k1_iceberg_aggnonce *aggnonce,
|
||||
const secp256k1_iceberg_pubnonce * const *pubnonces,
|
||||
size_t n_pubnonces,
|
||||
unsigned int n,
|
||||
unsigned int t,
|
||||
const secp256k1_pubkey *group_pk
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2)
|
||||
SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
/** Does this key aggregation cache aggregate this list, and is the group in it?
|
||||
*
|
||||
* Run this once, where the cache is built, and not once per session: the key
|
||||
* set belongs to the channel while a session belongs to a single signing
|
||||
* attempt.
|
||||
*
|
||||
* secp256k1_iceberg_partial_sign takes the group's public key and the outer
|
||||
* cache as two separate arguments and nothing ties them together, because
|
||||
* nothing can: a cache records the hash of the key list, not the list, so
|
||||
* membership is not a question it can answer. Passing a cache built over some
|
||||
* other set of cosigners produces a well-formed coefficient for a key that is
|
||||
* not in the aggregation, a signature share that is useless, and a spent session
|
||||
* label, which is the part that matters. Retrying under that label with
|
||||
* the right cache is then the attack, self-inflicted.
|
||||
*
|
||||
* MuSig2's own partial_sign asks for no key list, because there a wrong cache
|
||||
* costs a nonce rather than a label.
|
||||
*
|
||||
* Tweaks are fine. The list hash is fixed when the keys are aggregated and no
|
||||
* tweak touches it, so a cache carrying taproot tweaks still passes.
|
||||
*
|
||||
* Returns: 1 if keyagg_cache aggregates exactly this list of public keys, in
|
||||
* this order, and group_pk is one of them. 0 if it aggregates some
|
||||
* other list, or if group_pk is not in it. A malformed cache or public
|
||||
* key is a caller bug rather than an answer, and calls the illegal
|
||||
* callback as it does everywhere else in this module: neither has a
|
||||
* wire format, so neither can have arrived from a peer
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: keyagg_cache: the outer MuSig2 key aggregation cache to check
|
||||
* pubkeys: the keys it should have been built from, in the order
|
||||
* they were passed to secp256k1_musig_pubkey_agg
|
||||
* n_pubkeys: how many, at least 1
|
||||
* group_pk: the group's public key, which must be one of them
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_keyagg_check(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
const secp256k1_pubkey * const *pubkeys,
|
||||
size_t n_pubkeys,
|
||||
const secp256k1_pubkey *group_pk
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||||
SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Produce this participant's signature share.
|
||||
*
|
||||
* A participant makes its own three secrets alone: both nonce scalars and its
|
||||
* key share follow from its share and the label, with nobody else involved. What
|
||||
* it cannot make alone is the group's aggregate nonce, which is a value at zero
|
||||
* and so takes t points, and that aggregate is hashed into two of the
|
||||
* coefficients those secrets get multiplied by. Hence the round-one
|
||||
* contributions in the argument list below.
|
||||
*
|
||||
* It takes the message and the cosigners' nonce for the same reason, and
|
||||
* recomputes the challenge from them. It also adds the group's own nonce
|
||||
* internally, so "the aggregate nonce includes my contribution" holds by
|
||||
* construction rather than by assumption.
|
||||
*
|
||||
* The rule this function cannot enforce for you: never call it twice with the
|
||||
* same sid32. Not "never with the same message": never twice, whatever else you
|
||||
* change. A participant's three secrets are fixed by the label alone and
|
||||
* everything else in the signing equation is a coefficient over public values,
|
||||
* so two answers under one label are two equations in three unknowns and three
|
||||
* are three, at which point the key share falls out by elimination.
|
||||
*
|
||||
* Half of that is yours: a member can refuse to answer twice if it remembers
|
||||
* what it has answered under, which means durable storage that is never rolled
|
||||
* back, because this library holds nothing between calls. Recording the label
|
||||
* is enough, and it is all that is enough: the label is one signing attempt,
|
||||
* so a retry is a new label rather than a second answer under the old one. The
|
||||
* other half is the group's: two members must not answer one label on different
|
||||
* messages, which no member can detect, since what the others were shown is not
|
||||
* something the protocol tells it. That is constraint 3 at the top of this file.
|
||||
* Where the label is a commitment number and an attempt counter, both halves
|
||||
* come from machinery the surrounding protocol runs anyway, and
|
||||
* examples/iceberg.c shows the member's half.
|
||||
*
|
||||
* What it does check: it builds the group's aggregate from the contributions
|
||||
* rather than accepting one, because the nesting coefficient is a hash of that
|
||||
* aggregate and a coordinator free to invent it would hold a coefficient it
|
||||
* could vary at will. It then interpolates the set and compares the result at
|
||||
* this participant's own index against the contribution it derives locally,
|
||||
* which ties the set to the label being signed under. Otherwise a consistent
|
||||
* sharing from a different session would pass. It also counts the signer among
|
||||
* the honest points of constraint 2: a set assembled by somebody else has to
|
||||
* agree with this participant's own share as well as with the rest. The
|
||||
* participant need not be one of the contributors, since it holds the share
|
||||
* that determines what its own contribution would have been either way.
|
||||
*
|
||||
* Returns: 1 on success, 0 if there are fewer than 2t-1 contributions or more
|
||||
* than the group size, if two carry the same index, or if the set is
|
||||
* inconsistent or belongs to another session. The group size is not an
|
||||
* argument here: it comes off the caller's own share. A malformed
|
||||
* share, or a share cache built for a different participant, calls the
|
||||
* illegal callback
|
||||
* Args: ctx: pointer to a context object (not
|
||||
* secp256k1_context_static)
|
||||
* Out: partial_sig: the signature share to publish
|
||||
* In: share: this participant's share
|
||||
* cache: its Lagrange weights, or NULL to recompute them
|
||||
* sid32: the session label, the same one round one used
|
||||
* pubnonces: the group's own round-one contributions, one per
|
||||
* member. Not the cosigners', which arrive already
|
||||
* aggregated as cosigner_aggnonce below. Pass every valid
|
||||
* one you have; any qualifying set gives the same result,
|
||||
* so members need not agree on which
|
||||
* n_pubnonces: how many, at least 2t-1 and at most the group size
|
||||
* group_pk: the group's public key, the one round one was given
|
||||
* keyagg_cache: the outer MuSig2 key aggregation cache
|
||||
* msg32: the message being signed
|
||||
* cosigner_aggnonce: the cosigners' aggregate nonce, theirs alone. Not the
|
||||
* other group members', and not including the group's
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sign(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_partial_sig *partial_sig,
|
||||
const secp256k1_iceberg_share *share,
|
||||
const secp256k1_iceberg_share_cache *cache,
|
||||
const unsigned char *sid32,
|
||||
const secp256k1_iceberg_pubnonce * const *pubnonces,
|
||||
size_t n_pubnonces,
|
||||
const secp256k1_pubkey *group_pk,
|
||||
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
const unsigned char *msg32,
|
||||
const secp256k1_musig_aggnonce *cosigner_aggnonce
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||||
SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8)
|
||||
SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11);
|
||||
|
||||
/** Check one signature share against what its author published.
|
||||
*
|
||||
* Verifies s_k*G == +-(R1,k + b0*b1*R2,k) + e*a*g*gacc*D_k, the equation
|
||||
* secp256k1_iceberg_partial_sign solves. Without this,
|
||||
* secp256k1_iceberg_partial_sig_agg turns one bad share into a signature that
|
||||
* fails to verify with nothing to say why.
|
||||
*
|
||||
* The participant's nonce is read off the contributions rather than passed in.
|
||||
* They determine one polynomial per nonce, and its value at that index is what
|
||||
* the participant's contribution had to be, so this also works for a member
|
||||
* that sat out round one and published no contribution of its own.
|
||||
*
|
||||
* What a 0 means, and what it does not. It means this share does not satisfy
|
||||
* that equation against this pubnonce and this public share. It does not name a
|
||||
* culprit. A MuSig2 partial signature is forgeable by anyone who knows the
|
||||
* session's public values, so a share that fails here may have been written by
|
||||
* somebody other than the participant it is attributed to. Assigning blame is a
|
||||
* stronger claim and needs an honest-supermajority quorum: 3t-2 members
|
||||
* online at once, rather than the 2t-1 this scheme otherwise requires. Use this
|
||||
* to find out that a signing attempt will fail before spending an aggregation
|
||||
* on it, and to narrow where to look. Do not use it as evidence against a
|
||||
* member.
|
||||
*
|
||||
* A 0 also does not distinguish a bad share from bad inputs. The same answer
|
||||
* comes back if the message or the cosigners' aggregate nonce differs from the
|
||||
* one the signer had. The set of contributions is the exception: any qualifying
|
||||
* set from the session determines the same polynomial, so it need not be the
|
||||
* set the signer used.
|
||||
*
|
||||
* Every other argument must be the one secp256k1_iceberg_partial_sign was
|
||||
* given. The public share is what names the participant, and a signature share
|
||||
* carrying a different index is refused rather than verified against its own,
|
||||
* which catches the two arguments being drawn from different members.
|
||||
*
|
||||
* Returns: 1 if the share satisfies the equation, 0 otherwise, including a
|
||||
* contribution count outside 2t-1..n, and including a malformed
|
||||
* partial_sig, which is the object being examined. A malformed
|
||||
* pubshare, key aggregation cache, group key or cosigner aggregate is
|
||||
* the caller's own and calls the illegal callback
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: partial_sig: the signature share to check
|
||||
* pubshare: the public share of the participant it is
|
||||
* attributed to, which is what says which one that is
|
||||
* pubnonces: a qualifying set from the same session. It need not
|
||||
* be the set the signer used: any consistent 2t-1
|
||||
* determine the same polynomial
|
||||
* n_pubnonces: how many, at least 2t-1 and at most n
|
||||
* n: the group size. A contribution carrying an index
|
||||
* above it is rejected
|
||||
* t: the threshold, at least 1 and at most (n+1)/2. An
|
||||
* n or a t outside its range is a caller bug and
|
||||
* calls the illegal callback
|
||||
* group_pk: the group's aggregate public key
|
||||
* keyagg_cache: the MuSig2 cache for the outer session
|
||||
* msg32: the message being signed
|
||||
* cosigner_aggnonce: the cosigners' aggregate nonce
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sig_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_iceberg_partial_sig *partial_sig,
|
||||
const secp256k1_iceberg_pubshare *pubshare,
|
||||
const secp256k1_iceberg_pubnonce * const *pubnonces,
|
||||
size_t n_pubnonces,
|
||||
unsigned int n,
|
||||
unsigned int t,
|
||||
const secp256k1_pubkey *group_pk,
|
||||
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
const unsigned char *msg32,
|
||||
const secp256k1_musig_aggnonce *cosigner_aggnonce
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3)
|
||||
SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9)
|
||||
SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11);
|
||||
|
||||
/** Combine signature shares into one MuSig2 partial signature.
|
||||
*
|
||||
* Interpolation needs t points and the quorum bound exists only for the degree
|
||||
* check, so unlike the nonce round this takes t rather than 2t-1. The scheme
|
||||
* as specified keeps 2t-1 online throughout regardless; this call simply does
|
||||
* not need them all to have answered.
|
||||
*
|
||||
* The result is an ordinary MuSig2 partial signature. Pass it to
|
||||
* secp256k1_musig_partial_sig_agg with the cosigners' partial signatures.
|
||||
*
|
||||
* Given more than t shares, this refuses a set that contradicts itself. The
|
||||
* shares of one session lie on a polynomial of degree t-1, as the contributions
|
||||
* of the nonce round do, so anything past the t needed to interpolate is a
|
||||
* second opinion and is treated as one. Passing a spare share is therefore
|
||||
* worth something, and can turn a call that would have succeeded into one that
|
||||
* fails: at exactly t there is nothing to disagree with, and a bad share is
|
||||
* aggregated into a signature that fails later without naming a share.
|
||||
*
|
||||
* This is not verification. It says the shares agree with each other, not that
|
||||
* they are the ones the members would have produced, and it cannot tell you
|
||||
* which of them is the odd one out. secp256k1_iceberg_partial_sig_verify
|
||||
* answers both, one share at a time, against the public share it names.
|
||||
*
|
||||
* Returns: 1 on success, 0 if there are fewer than t shares or more than the
|
||||
* group size, if a share carries an out-of-range index, if two carry
|
||||
* the same one, or if more than t shares are given and they do not
|
||||
* lie on one polynomial of degree t-1.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: musig_partial_sig: the group's partial signature
|
||||
* In: partial_sigs: the signature shares. An uninitialized one calls
|
||||
* the illegal callback;
|
||||
* a share off the wire is fine, since
|
||||
* secp256k1_iceberg_partial_sig_parse writes the
|
||||
* same tag secp256k1_iceberg_partial_sign does
|
||||
* n_partial_sigs: how many
|
||||
* n: the group size. A share carrying an index above
|
||||
* it is rejected
|
||||
* t: the threshold, at least 1 and at most (n+1)/2.
|
||||
* An n or a t outside its range is a caller bug
|
||||
* and calls the illegal callback
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_partial_sig_agg(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_musig_partial_sig *musig_partial_sig,
|
||||
const secp256k1_iceberg_partial_sig * const *partial_sigs,
|
||||
size_t n_partial_sigs,
|
||||
unsigned int n,
|
||||
unsigned int t
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_ICEBERG_H */
|
||||
@@ -1,57 +0,0 @@
|
||||
#ifndef SECP256K1_ICEBERG_DEALER_H
|
||||
#define SECP256K1_ICEBERG_DEALER_H
|
||||
|
||||
#include "secp256k1_iceberg.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** A trusted dealer for Iceberg shares.
|
||||
*
|
||||
* This header is deliberately separate from secp256k1_iceberg.h and is not
|
||||
* installed. Including the module's own header does not offer you a dealer,
|
||||
* because for the duration of the call below one machine holds everything
|
||||
* needed to reconstruct the group's private key, which is the situation a
|
||||
* threshold scheme exists to avoid.
|
||||
*
|
||||
* It is here so that the tests, the benchmarks and the example have shares to
|
||||
* work with. It is also usable where one party is already trusted with the
|
||||
* whole key, which is a real if narrow case. Anything else wants a distributed
|
||||
* key generation: the same shares, assembled without the key ever existing in
|
||||
* one place. This module does not provide one.
|
||||
*
|
||||
* Nothing in the signing API depends on how a share was produced. A share
|
||||
* arrives through secp256k1_iceberg_share_parse, so an externally generated
|
||||
* one, from a DKG or from another implementation, is used exactly the same
|
||||
* way.
|
||||
*/
|
||||
|
||||
/** Deal a group's shares from a single seed.
|
||||
*
|
||||
* The seed is the group's private key in all but name until this returns.
|
||||
* Erase it afterwards, and do not derive it from anything reproducible.
|
||||
*
|
||||
* Returns: 1 on success.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: shares: array of n pointers to share objects, participant k at k-1
|
||||
* In: n: number of participants, 1 to
|
||||
* SECP256K1_ICEBERG_MAX_PARTICIPANTS
|
||||
* t: threshold, at least 1 and at most (n+1)/2, since a quorum of
|
||||
* 2t-1 has to fit in the group. So 2-of-2 and 3-of-4 are
|
||||
* inexpressible
|
||||
* seed32: 32 bytes of uniformly random data
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_iceberg_shares_gen(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_iceberg_share * const *shares,
|
||||
unsigned int n,
|
||||
unsigned int t,
|
||||
const unsigned char *seed32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_ICEBERG_DEALER_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,12 +14,12 @@ extern "C" {
|
||||
*
|
||||
* Context objects created by functions in this module can be used like contexts
|
||||
* objects created by functions in secp256k1.h, i.e., they can be passed to any
|
||||
* API function that expects a context object (see secp256k1.h for details). The
|
||||
* API function that excepts a context object (see secp256k1.h for details). The
|
||||
* only exception is that context objects created by functions in this module
|
||||
* must be destroyed using secp256k1_context_preallocated_destroy (in this
|
||||
* module) instead of secp256k1_context_destroy (in secp256k1.h).
|
||||
*
|
||||
* It is guaranteed that functions in this module will not call malloc or its
|
||||
* It is guaranteed that functions in by this module will not call malloc or its
|
||||
* friends realloc, calloc, and free.
|
||||
*/
|
||||
|
||||
@@ -52,19 +52,17 @@ SECP256K1_API size_t secp256k1_context_preallocated_size(
|
||||
* in the memory. In simpler words, the prealloc pointer (or any pointer derived
|
||||
* from it) should not be used during the lifetime of the context object.
|
||||
*
|
||||
* Returns: pointer to newly created context object.
|
||||
* In: prealloc: pointer to a rewritable contiguous block of memory of
|
||||
* Returns: a newly created context object.
|
||||
* In: prealloc: a pointer to a rewritable contiguous block of memory of
|
||||
* size at least secp256k1_context_preallocated_size(flags)
|
||||
* bytes, as detailed above.
|
||||
* bytes, as detailed above (cannot be NULL)
|
||||
* flags: which parts of the context to initialize.
|
||||
*
|
||||
* See secp256k1_context_create (in secp256k1.h) for further details.
|
||||
*
|
||||
* See also secp256k1_context_randomize (in secp256k1.h)
|
||||
* and secp256k1_context_preallocated_destroy.
|
||||
*/
|
||||
SECP256K1_API secp256k1_context *secp256k1_context_preallocated_create(
|
||||
void *prealloc,
|
||||
SECP256K1_API secp256k1_context* secp256k1_context_preallocated_create(
|
||||
void* prealloc,
|
||||
unsigned int flags
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT;
|
||||
|
||||
@@ -72,10 +70,10 @@ SECP256K1_API secp256k1_context *secp256k1_context_preallocated_create(
|
||||
* caller-provided memory.
|
||||
*
|
||||
* Returns: the required size of the caller-provided memory block.
|
||||
* In: ctx: pointer to a context to copy.
|
||||
* In: ctx: an existing context to copy (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API size_t secp256k1_context_preallocated_clone_size(
|
||||
const secp256k1_context *ctx
|
||||
const secp256k1_context* ctx
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_WARN_UNUSED_RESULT;
|
||||
|
||||
/** Copy a secp256k1 context object into caller-provided memory.
|
||||
@@ -88,18 +86,15 @@ SECP256K1_API size_t secp256k1_context_preallocated_clone_size(
|
||||
* the lifetime of this context object, see the description of
|
||||
* secp256k1_context_preallocated_create for details.
|
||||
*
|
||||
* Cloning secp256k1_context_static is not possible, and should not be emulated by
|
||||
* the caller (e.g., using memcpy). Create a new context instead.
|
||||
*
|
||||
* Returns: pointer to a newly created context object.
|
||||
* Args: ctx: pointer to a context to copy (not secp256k1_context_static).
|
||||
* In: prealloc: pointer to a rewritable contiguous block of memory of
|
||||
* Returns: a newly created context object.
|
||||
* Args: ctx: an existing context to copy (cannot be NULL)
|
||||
* In: prealloc: a pointer to a rewritable contiguous block of memory of
|
||||
* size at least secp256k1_context_preallocated_size(flags)
|
||||
* bytes, as detailed above.
|
||||
* bytes, as detailed above (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API secp256k1_context *secp256k1_context_preallocated_clone(
|
||||
const secp256k1_context *ctx,
|
||||
void *prealloc
|
||||
SECP256K1_API secp256k1_context* secp256k1_context_preallocated_clone(
|
||||
const secp256k1_context* ctx,
|
||||
void* prealloc
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_WARN_UNUSED_RESULT;
|
||||
|
||||
/** Destroy a secp256k1 context object that has been created in
|
||||
@@ -118,14 +113,13 @@ SECP256K1_API secp256k1_context *secp256k1_context_preallocated_clone(
|
||||
* preallocated pointer given to secp256k1_context_preallocated_create or
|
||||
* secp256k1_context_preallocated_clone.
|
||||
*
|
||||
* Args: ctx: pointer to a context to destroy, constructed using
|
||||
* Args: ctx: an existing context to destroy, constructed using
|
||||
* secp256k1_context_preallocated_create or
|
||||
* secp256k1_context_preallocated_clone
|
||||
* (i.e., not secp256k1_context_static).
|
||||
* secp256k1_context_preallocated_clone (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API void secp256k1_context_preallocated_destroy(
|
||||
secp256k1_context *ctx
|
||||
) SECP256K1_ARG_NONNULL(1);
|
||||
secp256k1_context* ctx
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
#ifndef SECP256K1_PREFRACTAL_H
|
||||
#define SECP256K1_PREFRACTAL_H
|
||||
|
||||
#include "secp256k1_frost.h"
|
||||
#include "secp256k1_musig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** This module implements a nested FROST+MuSig2 signing scheme ("prefractal"),
|
||||
* which lets a FROST t-of-n group occupy ONE participant slot of an ordinary
|
||||
* MuSig2 (BIP 327) session.
|
||||
*
|
||||
* WARNING: EXPERIMENTAL. Neither the scheme nor this implementation has been
|
||||
* reviewed by anyone outside the project, and should not be used to protect
|
||||
* anything of value.
|
||||
*
|
||||
* The construction is the nested signing scheme of frosty-musig
|
||||
* (https://github.com/jesseposner/frosty-musig). Each group member computes
|
||||
*
|
||||
* s_i = k1_i + b_frost*b_musig*k2_i + e*a*lambda_i*g*gacc*d_i
|
||||
*
|
||||
* where b_frost is this module's nonce-binding coefficient and b_musig, e, a,
|
||||
* g and gacc all belong to the OUTER MuSig2 session. The group publishes one
|
||||
* ordinary MuSig2 public nonce and one ordinary MuSig2 partial signature, so
|
||||
* cosigners cannot tell a group is involved and need no support for it.
|
||||
*
|
||||
* DELIBERATE DEVIATIONS FROM BIP 445 (see doc/prefractal.md):
|
||||
*
|
||||
* 1. b_frost does NOT commit to the message. BIP 445's nonce coefficient
|
||||
* hashes the message, but this module is built for protocols that publish
|
||||
* nonces before the message exists. The OUTER coefficient b_musig does
|
||||
* commit to the message and binds b_frost through it, exactly as the
|
||||
* iceberg module does with its own Iceberg/noncecoef tag.
|
||||
*
|
||||
* 2. There is NO g_frost factor. Stock FROST normalizes its threshold public
|
||||
* key to even Y (see g_times_gacc_parity in the frost module), because it
|
||||
* produces a BIP 340 x-only signature. Here the threshold public key is an
|
||||
* inner participant of the outer key aggregation and is used as a FULL
|
||||
* point, so all key-side parity normalization happens once, at the
|
||||
* aggregate level, using the OUTER keyagg cache. Applying the frost-level
|
||||
* factor would break the relation for every group whose key has odd Y.
|
||||
*
|
||||
* 3. The FROST tweak cache must be the identity (tacc == 0, gacc_parity == 0).
|
||||
* The channel protocols this module targets tweak only the outer aggregate
|
||||
* key. Every entry point below checks this rather than silently ignoring a
|
||||
* tweaked cache.
|
||||
*
|
||||
* NONCE HANDLING: the caller supplies secnonces produced by
|
||||
* secp256k1_frost_nonce_gen. As always, a secnonce MUST be used for exactly
|
||||
* one signature; reuse across two different messages leaks the secret share
|
||||
* and nothing here can detect it.
|
||||
*
|
||||
* The round-two signer set must be EXACTLY the round-one set: the Lagrange
|
||||
* coefficients and the aggregate nonce are both defined over the
|
||||
* participating set, so a proper subset produces an invalid signature with no
|
||||
* error raised. Pass the same ids array to every function below.
|
||||
*/
|
||||
|
||||
/** Aggregate the group members' public nonces and export the group's
|
||||
* OUTER-wire nonce.
|
||||
*
|
||||
* pubnonce_out is an ordinary MuSig2 public nonce, (R1, b_frost*R2), which is
|
||||
* what the group sends to its cosigners. aggnonce_out is the UNSCALED FROST
|
||||
* aggregate nonce, which the members need later for partial signing; it is an
|
||||
* internal value and must be given back to secp256k1_prefractal_sign and
|
||||
* secp256k1_prefractal_partial_sig_verify unchanged.
|
||||
*
|
||||
* b_frost = tagged_hash("Prefractal/noncecoef",
|
||||
* ser32(u) || sorted ser32 ids || aggnonce66 ||
|
||||
* cbytes_ext(thresh_pk))
|
||||
*
|
||||
* Returns: 1 on success, 0 if a pubnonce could not be loaded, if the ids are
|
||||
* invalid, or if either output nonce component is the point at
|
||||
* infinity. A FROST aggregate nonce component may legitimately be
|
||||
* infinity (BIP 445 NonceAgg), but a MuSig2 public nonce has no
|
||||
* encoding for it, so such a session must be restarted with fresh
|
||||
* nonces.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: pubnonce_out: the group's MuSig2 public nonce
|
||||
* aggnonce_out: the group's unscaled FROST aggregate nonce
|
||||
* In: pubnonces: array of pointers to the members' public nonces
|
||||
* ids: array of the members' identifiers
|
||||
* n_signers: number of members (must match the array lengths, at
|
||||
* least 1 and at most SECP256K1_FROST_MAX_PARTICIPANTS)
|
||||
* thresh_pk: the group's (untweaked) threshold public key
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_nonce_agg(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_musig_pubnonce *pubnonce_out,
|
||||
secp256k1_frost_aggnonce *aggnonce_out,
|
||||
const secp256k1_frost_pubnonce *const *pubnonces,
|
||||
const uint32_t *ids,
|
||||
size_t n_signers,
|
||||
const secp256k1_pubkey *thresh_pk
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7);
|
||||
|
||||
/** Produce one group member's nested partial signature.
|
||||
*
|
||||
* Computes s_i = k1 + b_frost*b_musig*k2 + e*a*lambda_i*g*gacc*d_i, with both
|
||||
* nonce scalars negated iff the OUTER final nonce has odd Y, and the key-side
|
||||
* factor g*gacc taken from the OUTER keyagg cache. There is deliberately no
|
||||
* g_frost factor (see the module notes above).
|
||||
*
|
||||
* The secnonce is wiped, so a second call with the same secnonce fails. The
|
||||
* partial signature is self-verified before it is returned, as BIP 445
|
||||
* recommends.
|
||||
*
|
||||
* Returns: 1 on success, 0 on failure. Failure cases include: an invalidated
|
||||
* or malformed secnonce, an invalid secret share, my_id not in ids,
|
||||
* a secret share that does not match its pubshare, a non-identity
|
||||
* tweak_cache, a nonce component at infinity, and a failed
|
||||
* self-verification.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: partial_sig: the member's partial signature
|
||||
* In/Out: secnonce: the member's secret nonce, wiped by this call
|
||||
* In: secshare32: the member's 32-byte secret share
|
||||
* my_id: the member's identifier
|
||||
* ids: array of the participating members' identifiers,
|
||||
* the SAME array given to _nonce_agg
|
||||
* pubshares: array of the members' public shares, in the order
|
||||
* of ids, or NULL to skip the share/pubshare check
|
||||
* (providing them is recommended)
|
||||
* n_signers: number of members
|
||||
* aggnonce: the unscaled FROST aggregate nonce from _nonce_agg
|
||||
* thresh_pk: the group's (untweaked) threshold public key
|
||||
* tweak_cache: the group's FROST tweak cache, which MUST be the
|
||||
* identity
|
||||
* keyagg_cache: the OUTER MuSig2 keyagg cache, already carrying any
|
||||
* BIP 341 tweak
|
||||
* cosigner_aggnonce: the aggregate of the NON-group participants'
|
||||
* MuSig2 public nonces
|
||||
* msg32: the 32-byte message being signed
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_sign(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_frost_partial_sig *partial_sig,
|
||||
secp256k1_frost_secnonce *secnonce,
|
||||
const unsigned char *secshare32,
|
||||
uint32_t my_id,
|
||||
const uint32_t *ids,
|
||||
const secp256k1_pubkey *pubshares,
|
||||
size_t n_signers,
|
||||
const secp256k1_frost_aggnonce *aggnonce,
|
||||
const secp256k1_pubkey *thresh_pk,
|
||||
const secp256k1_frost_tweak_cache *tweak_cache,
|
||||
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
const secp256k1_musig_aggnonce *cosigner_aggnonce,
|
||||
const unsigned char *msg32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11) SECP256K1_ARG_NONNULL(12) SECP256K1_ARG_NONNULL(13) SECP256K1_ARG_NONNULL(14);
|
||||
|
||||
/** Verify one group member's nested partial signature.
|
||||
*
|
||||
* Checks s_i*G == R1_i + b_frost*b_musig*R2_i + e*a*lambda_i*g*gacc*P_i, with
|
||||
* the nonce points negated iff the OUTER final nonce has odd Y. The session is
|
||||
* recomputed from the same parameters secp256k1_prefractal_sign takes, so the
|
||||
* caller must pass exactly the same ids, aggnonce, keys and message.
|
||||
*
|
||||
* This is the identifiable-abort tool: when the aggregate signature fails,
|
||||
* running this over each member's share names the one at fault.
|
||||
*
|
||||
* Returns: 1 if the partial signature is valid, 0 otherwise.
|
||||
* Args: ctx: pointer to a context object
|
||||
* In: partial_sig: the partial signature to verify
|
||||
* pubnonce: the member's public nonce, as given to _nonce_agg
|
||||
* pubshare: the member's public share
|
||||
* my_id: the member's identifier
|
||||
* ids: array of the participating members' identifiers
|
||||
* n_signers: number of members
|
||||
* aggnonce: the unscaled FROST aggregate nonce from _nonce_agg
|
||||
* thresh_pk: the group's (untweaked) threshold public key
|
||||
* tweak_cache: the group's FROST tweak cache, which MUST be the
|
||||
* identity
|
||||
* keyagg_cache: the OUTER MuSig2 keyagg cache
|
||||
* cosigner_aggnonce: the aggregate of the NON-group participants' nonces
|
||||
* msg32: the 32-byte message being signed
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_partial_sig_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_frost_partial_sig *partial_sig,
|
||||
const secp256k1_frost_pubnonce *pubnonce,
|
||||
const secp256k1_pubkey *pubshare,
|
||||
uint32_t my_id,
|
||||
const uint32_t *ids,
|
||||
size_t n_signers,
|
||||
const secp256k1_frost_aggnonce *aggnonce,
|
||||
const secp256k1_pubkey *thresh_pk,
|
||||
const secp256k1_frost_tweak_cache *tweak_cache,
|
||||
const secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
const secp256k1_musig_aggnonce *cosigner_aggnonce,
|
||||
const unsigned char *msg32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10) SECP256K1_ARG_NONNULL(11) SECP256K1_ARG_NONNULL(12) SECP256K1_ARG_NONNULL(13);
|
||||
|
||||
/** Sum the members' partial signatures into one ordinary MuSig2 partial
|
||||
* signature.
|
||||
*
|
||||
* The result is ready for secp256k1_musig_partial_sig_agg alongside the
|
||||
* cosigners' partial signatures. The sum is plain because the FROST tweak
|
||||
* cache is required to be the identity: with a tweak there would be an
|
||||
* additional e*g*tacc term to fold in, and this module does not support that.
|
||||
*
|
||||
* This does not check that the shares are the ones the members would have
|
||||
* produced; secp256k1_prefractal_partial_sig_verify answers that, one share at
|
||||
* a time.
|
||||
*
|
||||
* Returns: 1 on success, 0 if a partial signature could not be loaded or if
|
||||
* tweak_cache is not the identity.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: sig_out: the resulting MuSig2 partial signature
|
||||
* error_index: if non-NULL and a partial signature fails to load,
|
||||
* receives its index in partial_sigs
|
||||
* In: partial_sigs: array of pointers to the members' partial signatures
|
||||
* n_sigs: number of partial signatures (at least 1 and at most
|
||||
* SECP256K1_FROST_MAX_PARTICIPANTS)
|
||||
* tweak_cache: the group's FROST tweak cache, which MUST be the
|
||||
* identity
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_prefractal_partial_sig_agg(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_musig_partial_sig *sig_out,
|
||||
size_t *error_index,
|
||||
const secp256k1_frost_partial_sig *const *partial_sigs,
|
||||
size_t n_sigs,
|
||||
const secp256k1_frost_tweak_cache *tweak_cache
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_PREFRACTAL_H */
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SECP256K1_RANGEPROOF_H
|
||||
# define SECP256K1_RANGEPROOF_H
|
||||
#ifndef _SECP256K1_RANGEPROOF_
|
||||
# define _SECP256K1_RANGEPROOF_
|
||||
|
||||
# include "secp256k1.h"
|
||||
# include "secp256k1_generator.h"
|
||||
@@ -10,54 +10,164 @@ extern "C" {
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** This module implements a variant of Back-Maxwell range proofs as described
|
||||
* in the Confidential Assets paper (https://blockstream.com/bitcoin17-final41.pdf).
|
||||
* The construction is based on Borromean ring signatures.
|
||||
* (https://nt4tn.net/papers/borromean_draft_0.01_34241bb.pdf)
|
||||
/** Opaque data structure that stores a Pedersen commitment
|
||||
*
|
||||
* This implementation differs from the variant in the paper mainly in that it
|
||||
* omits an optimization that saves one scalar per ring. This optimization complicates
|
||||
* the protocol and security analysis, as it requires differentiating cases where
|
||||
* the i-th bit v_i = 0 versus otherwise, and makes calculating response points R_i less
|
||||
* straightforward. The implemented version uses Borromean ring signatures in
|
||||
* an unmodified way.
|
||||
*
|
||||
* Another difference is that the implementation omits the last ring's commitment
|
||||
* from the proof, which is recovered by the verifier by subtracting all other digit
|
||||
* commitments from the total, reducing proof size by one group element.
|
||||
*
|
||||
* Furthermore, in the implementation every hash calculation includes a message
|
||||
* m=SHA256(C||H||header||C_0||...||C_(n-2)||extra_commit), binding the commitment C,
|
||||
* generator H, proof header, the n-1 explicit digit commitments, and any extra data.
|
||||
* This prevents an attack that would compromise non-malleability. In the paper's
|
||||
* version of the protocol, a prover could pick distinct indices i, j and a scalar y,
|
||||
* and modify digit commitments in the original proof by setting C'_i = C_i + yG and
|
||||
* C'_j = C_j - yG, obtaining a different valid proof for the same commitment and
|
||||
* witness.
|
||||
*
|
||||
* In the current implementation, up to 3968 bytes of message data can be
|
||||
* embedded and recovered within maximally-sized proofs. The implemented embedding
|
||||
* method using the forged parts of ring signatures could also be applied to the
|
||||
* construction in the paper, but is not mentioned there. Message embedding is used
|
||||
* in Confidential Assets to transmit values and blinding factors of the corresponding
|
||||
* commitments. This is possible because randomness is generated by seeding HMAC-DRBG
|
||||
* with the shared ECDH key, allowing the receiver to rewind the proof using the same
|
||||
* random values the sender used.
|
||||
* The exact representation of data inside is implementation defined and not
|
||||
* guaranteed to be portable between different platforms or versions. It is
|
||||
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
|
||||
* If you need to convert to a format suitable for storage, transmission, or
|
||||
* comparison, use secp256k1_pedersen_commitment_serialize and
|
||||
* secp256k1_pedersen_commitment_parse.
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned char data[64];
|
||||
} secp256k1_pedersen_commitment;
|
||||
|
||||
/** Length of a message that can be embedded into a maximally-sized rangeproof
|
||||
*
|
||||
* It is not be possible to fit a message of this size into a non-maximally-sized
|
||||
* rangeproof, but it is guaranteed that any embeddable message can fit into an
|
||||
* array of this size. This constant is intended to be used for memory allocations
|
||||
* and sanity checks.
|
||||
/**
|
||||
* Static constant generator 'h' maintained for historical reasons.
|
||||
*/
|
||||
#define SECP256K1_RANGEPROOF_MAX_MESSAGE_LEN 3968
|
||||
SECP256K1_API extern const secp256k1_generator *secp256k1_generator_h;
|
||||
|
||||
/** Parse a 33-byte commitment into a commitment object.
|
||||
*
|
||||
* Returns: 1 if input contains a valid commitment.
|
||||
* Args: ctx: a secp256k1 context object.
|
||||
* Out: commit: pointer to the output commitment object
|
||||
* In: input: pointer to a 33-byte serialized commitment key
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_commitment_parse(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_pedersen_commitment* commit,
|
||||
const unsigned char *input
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize a commitment object into a serialized byte sequence.
|
||||
*
|
||||
* Returns: 1 always.
|
||||
* Args: ctx: a secp256k1 context object.
|
||||
* Out: output: a pointer to a 33-byte byte array
|
||||
* In: commit: a pointer to a secp256k1_pedersen_commitment containing an
|
||||
* initialized commitment
|
||||
*/
|
||||
SECP256K1_API int secp256k1_pedersen_commitment_serialize(
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *output,
|
||||
const secp256k1_pedersen_commitment* commit
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Initialize a context for usage with Pedersen commitments. */
|
||||
void secp256k1_pedersen_context_initialize(secp256k1_context* ctx);
|
||||
|
||||
/** Generate a pedersen commitment.
|
||||
* Returns 1: Commitment successfully created.
|
||||
* 0: Error. The blinding factor is larger than the group order
|
||||
* (probability for random 32 byte number < 2^-127) or results in the
|
||||
* point at infinity. Retry with a different factor.
|
||||
* In: ctx: pointer to a context object, initialized for signing and Pedersen commitment (cannot be NULL)
|
||||
* blind: pointer to a 32-byte blinding factor (cannot be NULL)
|
||||
* value: unsigned 64-bit integer value to commit to.
|
||||
* gen: additional generator 'h'
|
||||
* Out: commit: pointer to the commitment (cannot be NULL)
|
||||
*
|
||||
* Blinding factors can be generated and verified in the same way as secp256k1 private keys for ECDSA.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_commit(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_pedersen_commitment *commit,
|
||||
const unsigned char *blind,
|
||||
uint64_t value,
|
||||
const secp256k1_generator *gen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Computes the sum of multiple positive and negative blinding factors.
|
||||
* Returns 1: Sum successfully computed.
|
||||
* 0: Error. A blinding factor is larger than the group order
|
||||
* (probability for random 32 byte number < 2^-127). Retry with
|
||||
* different factors.
|
||||
* In: ctx: pointer to a context object (cannot be NULL)
|
||||
* blinds: pointer to pointers to 32-byte character arrays for blinding factors. (cannot be NULL)
|
||||
* n: number of factors pointed to by blinds.
|
||||
* npositive: how many of the initial factors should be treated with a positive sign.
|
||||
* Out: blind_out: pointer to a 32-byte array for the sum (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_blind_sum(
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *blind_out,
|
||||
const unsigned char * const *blinds,
|
||||
size_t n,
|
||||
size_t npositive
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Verify a tally of pedersen commitments
|
||||
* Returns 1: commitments successfully sum to zero.
|
||||
* 0: Commitments do not sum to zero or other error.
|
||||
* In: ctx: pointer to a context object (cannot be NULL)
|
||||
* commits: pointer to array of pointers to the commitments. (cannot be NULL if pcnt is non-zero)
|
||||
* pcnt: number of commitments pointed to by commits.
|
||||
* ncommits: pointer to array of pointers to the negative commitments. (cannot be NULL if ncnt is non-zero)
|
||||
* ncnt: number of commitments pointed to by ncommits.
|
||||
*
|
||||
* This computes sum(commit[0..pcnt)) - sum(ncommit[0..ncnt)) == 0.
|
||||
*
|
||||
* A pedersen commitment is xG + vA where G and A are generators for the secp256k1 group and x is a blinding factor,
|
||||
* while v is the committed value. For a collection of commitments to sum to zero, for each distinct generator
|
||||
* A all blinding factors and all values must sum to zero.
|
||||
*
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_verify_tally(
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_pedersen_commitment * const* commits,
|
||||
size_t pcnt,
|
||||
const secp256k1_pedersen_commitment * const* ncommits,
|
||||
size_t ncnt
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Sets the final Pedersen blinding factor correctly when the generators themselves
|
||||
* have blinding factors.
|
||||
*
|
||||
* Consider a generator of the form A' = A + rG, where A is the "real" generator
|
||||
* but A' is the generator provided to verifiers. Then a Pedersen commitment
|
||||
* P = vA' + r'G really has the form vA + (vr + r')G. To get all these (vr + r')
|
||||
* to sum to zero for multiple commitments, we take three arrays consisting of
|
||||
* the `v`s, `r`s, and `r'`s, respectively called `value`s, `generator_blind`s
|
||||
* and `blinding_factor`s, and sum them.
|
||||
*
|
||||
* The function then subtracts the sum of all (vr + r') from the last element
|
||||
* of the `blinding_factor` array, setting the total sum to zero.
|
||||
*
|
||||
* Returns 1: Blinding factor successfully computed.
|
||||
* 0: Error. A blinding_factor or generator_blind are larger than the group
|
||||
* order (probability for random 32 byte number < 2^-127). Retry with
|
||||
* different values.
|
||||
*
|
||||
* In: ctx: pointer to a context object
|
||||
* value: array of asset values, `v` in the above paragraph.
|
||||
* May not be NULL unless `n_total` is 0.
|
||||
* generator_blind: array of asset blinding factors, `r` in the above paragraph
|
||||
* May not be NULL unless `n_total` is 0.
|
||||
* n_total: Total size of the above arrays
|
||||
* n_inputs: How many of the initial array elements represent commitments that
|
||||
* will be negated in the final sum
|
||||
* In/Out: blinding_factor: array of commitment blinding factors, `r'` in the above paragraph
|
||||
* May not be NULL unless `n_total` is 0.
|
||||
* the last value will be modified to get the total sum to zero.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_blind_generator_blind_sum(
|
||||
const secp256k1_context* ctx,
|
||||
const uint64_t *value,
|
||||
const unsigned char* const* generator_blind,
|
||||
unsigned char* const* blinding_factor,
|
||||
size_t n_total,
|
||||
size_t n_inputs
|
||||
);
|
||||
|
||||
/** Initialize a context for usage with Pedersen commitments. */
|
||||
void secp256k1_rangeproof_context_initialize(secp256k1_context* ctx);
|
||||
|
||||
/** Verify a proof that a committed value is within a range.
|
||||
* Returns 1: Value is within the range [0..2^64), the specifically proven range is in the min/max value outputs.
|
||||
* 0: Proof failed or other error.
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* In: ctx: pointer to a context object, initialized for range-proof and commitment (cannot be NULL)
|
||||
* commit: the commitment being proved. (cannot be NULL)
|
||||
* proof: pointer to character array with the proof. (cannot be NULL)
|
||||
* plen: length of proof in bytes.
|
||||
@@ -68,7 +178,7 @@ extern "C" {
|
||||
* max_value: pointer to a unsigned int64 which will be updated with the maximum value that commit could have. (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
uint64_t *min_value,
|
||||
uint64_t *max_value,
|
||||
const secp256k1_pedersen_commitment *commit,
|
||||
@@ -76,13 +186,13 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_verify(
|
||||
size_t plen,
|
||||
const unsigned char *extra_commit,
|
||||
size_t extra_commit_len,
|
||||
const secp256k1_generator *gen
|
||||
const secp256k1_generator* gen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(9);
|
||||
|
||||
/** Verify a range proof proof and rewind the proof to recover information sent by its author.
|
||||
* Returns 1: Value is within the range [0..2^64), the specifically proven range is in the min/max value outputs, and the value and blinding were recovered.
|
||||
* 0: Proof failed, rewind failed, or other error.
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* In: ctx: pointer to a context object, initialized for range-proof and Pedersen commitment (cannot be NULL)
|
||||
* commit: the commitment being proved. (cannot be NULL)
|
||||
* proof: pointer to character array with the proof. (cannot be NULL)
|
||||
* plen: length of proof in bytes.
|
||||
@@ -93,14 +203,12 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_verify(
|
||||
* In/Out: blind_out: storage for the 32-byte blinding factor used for the commitment
|
||||
* value_out: pointer to an unsigned int64 which has the exact value of the commitment.
|
||||
* message_out: pointer to a 4096 byte character array to receive message data from the proof author.
|
||||
* outlen: length of message data written to message_out. This is generally not equal to the
|
||||
* msg_len used by the signer. However, for all i with msg_len <= i < outlen, it is
|
||||
* guaranteed that message_out[i] == 0.
|
||||
* outlen: length of message data written to message_out.
|
||||
* min_value: pointer to an unsigned int64 which will be updated with the minimum value that commit could have. (cannot be NULL)
|
||||
* max_value: pointer to an unsigned int64 which will be updated with the maximum value that commit could have. (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_rewind(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *blind_out,
|
||||
uint64_t *value_out,
|
||||
unsigned char *message_out,
|
||||
@@ -119,20 +227,12 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_rewind(
|
||||
/** Author a proof that a committed value is within a range.
|
||||
* Returns 1: Proof successfully created.
|
||||
* 0: Error
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* In: ctx: pointer to a context object, initialized for range-proof, signing, and Pedersen commitment (cannot be NULL)
|
||||
* proof: pointer to array to receive the proof, can be up to 5134 bytes. (cannot be NULL)
|
||||
* min_value: constructs a proof where the verifer can tell the minimum value is at least the specified amount.
|
||||
* commit: the commitment being proved.
|
||||
* blind: 32-byte blinding factor used by commit. The blinding factor may be all-zeros as long as min_bits is set to 3 or greater.
|
||||
* This is a side-effect of the underlying crypto, not a deliberate API choice, but it may be useful when balancing CT transactions.
|
||||
* nonce: 32-byte secret nonce used to initialize the proof.
|
||||
*
|
||||
* Each call to this function must have a UNIQUE nonce that
|
||||
* MUST NOT BE REUSED in subsequent calls. The nonce must be
|
||||
* KEPT SECRET except from parties authorized to rewind the
|
||||
* proof. Anyone who knows the nonce can recover `value` and
|
||||
* `blind` from the proof. Reusing the nonce may expose `blind`
|
||||
* even to parties that do not know the nonce.
|
||||
* blind: 32-byte blinding factor used by commit.
|
||||
* nonce: 32-byte secret nonce used to initialize the proof (value can be reverse-engineered out of the proof if this secret is known.)
|
||||
* exp: Base-10 exponent. Digits below above will be made public, but the proof will be made smaller. Allowed range is -1 to 18.
|
||||
* (-1 is a special case that makes the value public. 0 is the most private.)
|
||||
* min_bits: Number of bits of the value to keep private. (0 = auto/minimal, - 64).
|
||||
@@ -152,7 +252,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_rewind(
|
||||
*
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_sign(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *proof,
|
||||
size_t *plen,
|
||||
uint64_t min_value,
|
||||
@@ -181,7 +281,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_sign(
|
||||
* max_value: pointer to an unsigned int64 which will be updated with the maximum value that commit could have. (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
int *exp,
|
||||
int *mantissa,
|
||||
uint64_t *min_value,
|
||||
@@ -190,33 +290,6 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info(
|
||||
size_t plen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Returns an upper bound on the size of a rangeproof with the given parameters
|
||||
*
|
||||
* An actual rangeproof may be smaller, for example if the actual value
|
||||
* is less than both the provided `max_value` and 2^`min_bits`, or if
|
||||
* the `exp` parameter to `secp256k1_rangeproof_sign` is set such that
|
||||
* the proven range is compressed. In particular this function will always
|
||||
* overestimate the size of single-value proofs. Also, if `min_value`
|
||||
* is set to 0 in the proof, the result will usually, but not always,
|
||||
* be 8 bytes smaller than if a nonzero value had been passed.
|
||||
*
|
||||
* The goal of this function is to provide a useful upper bound for
|
||||
* memory allocation or fee estimation purposes, without requiring
|
||||
* too many parameters be fixed in advance.
|
||||
*
|
||||
* To obtain the size of largest possible proof, set `max_value` to
|
||||
* `UINT64_MAX` (and `min_bits` to any valid value such as 0).
|
||||
*
|
||||
* In: ctx: pointer to a context object
|
||||
* max_value: the maximum value that might be passed for `value` for the proof.
|
||||
* min_bits: the value that will be passed as `min_bits` for the proof.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT size_t secp256k1_rangeproof_max_size(
|
||||
const secp256k1_context *ctx,
|
||||
uint64_t max_value,
|
||||
int min_bits
|
||||
) SECP256K1_ARG_NONNULL(1);
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Opaque data structure that holds a parsed ECDSA signature,
|
||||
/** Opaque data structured that holds a parsed ECDSA signature,
|
||||
* supporting pubkey recovery.
|
||||
*
|
||||
* The exact representation of data inside is implementation defined and not
|
||||
@@ -21,21 +21,21 @@ extern "C" {
|
||||
* recoverability) will have identical representation, so they can be
|
||||
* memcmp'ed.
|
||||
*/
|
||||
typedef struct secp256k1_ecdsa_recoverable_signature {
|
||||
typedef struct {
|
||||
unsigned char data[65];
|
||||
} secp256k1_ecdsa_recoverable_signature;
|
||||
|
||||
/** Parse a compact ECDSA signature (64 bytes + recovery id).
|
||||
*
|
||||
* 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: input64: pointer to a 64-byte compact signature
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: sig: a pointer to a signature object
|
||||
* In: input64: a pointer to a 64-byte compact signature
|
||||
* recid: the recovery id (0, 1, 2 or 3)
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_recoverable_signature_parse_compact(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_ecdsa_recoverable_signature *sig,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_ecdsa_recoverable_signature* sig,
|
||||
const unsigned char *input64,
|
||||
int recid
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
@@ -43,48 +43,45 @@ SECP256K1_API int secp256k1_ecdsa_recoverable_signature_parse_compact(
|
||||
/** Convert a recoverable signature into a normal signature.
|
||||
*
|
||||
* Returns: 1
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: sig: pointer to a normal signature.
|
||||
* In: sigin: pointer to a recoverable signature.
|
||||
* Out: sig: a pointer to a normal signature (cannot be NULL).
|
||||
* In: sigin: a pointer to a recoverable signature (cannot be NULL).
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_recoverable_signature_convert(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_ecdsa_signature *sig,
|
||||
const secp256k1_ecdsa_recoverable_signature *sigin
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_ecdsa_signature* sig,
|
||||
const secp256k1_ecdsa_recoverable_signature* sigin
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Serialize an ECDSA signature in compact format (64 bytes + recovery id).
|
||||
*
|
||||
* Returns: 1
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: output64: pointer to a 64-byte array of the compact signature.
|
||||
* recid: pointer to an integer to hold the recovery id.
|
||||
* In: sig: pointer to an initialized signature object.
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: output64: a pointer to a 64-byte array of the compact signature (cannot be NULL)
|
||||
* recid: a pointer to an integer to hold the recovery id (can be NULL).
|
||||
* In: sig: a pointer to an initialized signature object (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_recoverable_signature_serialize_compact(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *output64,
|
||||
int *recid,
|
||||
const secp256k1_ecdsa_recoverable_signature *sig
|
||||
const secp256k1_ecdsa_recoverable_signature* sig
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Create a recoverable ECDSA signature.
|
||||
*
|
||||
* Returns: 1: signature created
|
||||
* 0: the nonce generation function failed, or the secret key was invalid.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static).
|
||||
* Out: sig: pointer to a signature object.
|
||||
* In: msghash32: the 32-byte message hash being signed.
|
||||
* seckey: pointer to a 32-byte secret key.
|
||||
* noncefp: pointer to a nonce generation function. If NULL,
|
||||
* secp256k1_nonce_function_default is used.
|
||||
* ndata: pointer to arbitrary data used by the nonce generation function
|
||||
* (can be NULL for secp256k1_nonce_function_default).
|
||||
* 0: the nonce generation function failed, or the private key was invalid.
|
||||
* Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
|
||||
* Out: sig: pointer to an array where the signature will be placed (cannot be NULL)
|
||||
* In: msg32: the 32-byte message hash being signed (cannot be NULL)
|
||||
* seckey: pointer to a 32-byte secret key (cannot be NULL)
|
||||
* noncefp:pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used
|
||||
* ndata: pointer to arbitrary data used by the nonce generation function (can be NULL)
|
||||
*/
|
||||
SECP256K1_API int secp256k1_ecdsa_sign_recoverable(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_ecdsa_recoverable_signature *sig,
|
||||
const unsigned char *msghash32,
|
||||
const unsigned char *msg32,
|
||||
const unsigned char *seckey,
|
||||
secp256k1_nonce_function noncefp,
|
||||
const void *ndata
|
||||
@@ -92,28 +89,18 @@ SECP256K1_API int secp256k1_ecdsa_sign_recoverable(
|
||||
|
||||
/** Recover an ECDSA public key from a signature.
|
||||
*
|
||||
* Successful public key recovery guarantees that the signature, after normalization,
|
||||
* passes `secp256k1_ecdsa_verify`. Thus, explicit verification is not necessary.
|
||||
*
|
||||
* However, a recoverable signature that successfully passes `secp256k1_ecdsa_recover`,
|
||||
* when converted to a non-recoverable signature (using
|
||||
* `secp256k1_ecdsa_recoverable_signature_convert`), is not guaranteed to be
|
||||
* normalized and thus not guaranteed to pass `secp256k1_ecdsa_verify`. If a
|
||||
* normalized signature is required, call `secp256k1_ecdsa_signature_normalize`
|
||||
* after `secp256k1_ecdsa_recoverable_signature_convert`.
|
||||
*
|
||||
* Returns: 1: public key successfully recovered
|
||||
* Returns: 1: public key successfully recovered (which guarantees a correct signature).
|
||||
* 0: otherwise.
|
||||
* Args: ctx: pointer to a context object.
|
||||
* Out: pubkey: pointer to the recovered public key.
|
||||
* In: sig: pointer to initialized signature that supports pubkey recovery.
|
||||
* msghash32: the 32-byte message hash assumed to be signed.
|
||||
* Args: ctx: pointer to a context object, initialized for verification (cannot be NULL)
|
||||
* Out: pubkey: pointer to the recovered public key (cannot be NULL)
|
||||
* In: sig: pointer to initialized signature that supports pubkey recovery (cannot be NULL)
|
||||
* msg32: the 32-byte message hash assumed to be signed (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_recover(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_pubkey *pubkey,
|
||||
const secp256k1_ecdsa_recoverable_signature *sig,
|
||||
const unsigned char *msghash32
|
||||
const unsigned char *msg32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,190 +1,118 @@
|
||||
#ifndef SECP256K1_SCHNORRSIG_H
|
||||
#define SECP256K1_SCHNORRSIG_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
#include "secp256k1_extrakeys.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** This module implements a variant of Schnorr signatures compliant with
|
||||
* Bitcoin Improvement Proposal 340 "Schnorr Signatures for secp256k1"
|
||||
* (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
|
||||
* BIP-schnorr
|
||||
* (https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki).
|
||||
*/
|
||||
|
||||
/** A pointer to a function to deterministically generate a nonce.
|
||||
/** Opaque data structure that holds a parsed Schnorr signature.
|
||||
*
|
||||
* Same as secp256k1_nonce function with the exception of accepting an
|
||||
* additional pubkey argument and not requiring an attempt argument. The pubkey
|
||||
* argument can protect signature schemes with key-prefixed challenge hash
|
||||
* inputs against reusing the nonce when signing with the wrong precomputed
|
||||
* pubkey.
|
||||
*
|
||||
* Returns: 1 if a nonce was successfully generated. 0 will cause signing to
|
||||
* return an error.
|
||||
* Out: nonce32: pointer to a 32-byte array to be filled by the function
|
||||
* In: msg: the message being verified. Is NULL if and only if msglen
|
||||
* is 0.
|
||||
* msglen: the length of the message
|
||||
* key32: pointer to a 32-byte secret key (will not be NULL)
|
||||
* xonly_pk32: the 32-byte serialized xonly pubkey corresponding to key32
|
||||
* (will not be NULL)
|
||||
* algo: pointer to an array describing the signature
|
||||
* algorithm (will not be NULL)
|
||||
* algolen: the length of the algo array
|
||||
* data: arbitrary data pointer that is passed through
|
||||
*
|
||||
* Except for test cases, this function should compute some cryptographic hash of
|
||||
* the message, the key, the pubkey, the algorithm description, and data.
|
||||
* The exact representation of data inside is implementation defined and not
|
||||
* guaranteed to be portable between different platforms or versions. It is
|
||||
* however guaranteed to be 64 bytes in size, and can be safely copied/moved.
|
||||
* If you need to convert to a format suitable for storage, transmission, or
|
||||
* comparison, use the `secp256k1_schnorrsig_serialize` and
|
||||
* `secp256k1_schnorrsig_parse` functions.
|
||||
*/
|
||||
typedef int (*secp256k1_nonce_function_hardened)(
|
||||
unsigned char *nonce32,
|
||||
const unsigned char *msg,
|
||||
size_t msglen,
|
||||
const unsigned char *key32,
|
||||
const unsigned char *xonly_pk32,
|
||||
const unsigned char *algo,
|
||||
size_t algolen,
|
||||
void *data
|
||||
);
|
||||
typedef struct {
|
||||
unsigned char data[64];
|
||||
} secp256k1_schnorrsig;
|
||||
|
||||
/** An implementation of the nonce generation function as defined in Bitcoin
|
||||
* Improvement Proposal 340 "Schnorr Signatures for secp256k1"
|
||||
* (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
|
||||
/** Serialize a Schnorr signature.
|
||||
*
|
||||
* If a data pointer is passed, it is assumed to be a pointer to 32 bytes of
|
||||
* auxiliary random data as defined in BIP-340. If the data pointer is NULL,
|
||||
* the nonce derivation procedure follows BIP-340 by setting the auxiliary
|
||||
* random data to zero. The algo argument must be non-NULL, otherwise the
|
||||
* function will fail and return 0. The hash will be tagged with algo.
|
||||
* Therefore, to create BIP-340 compliant signatures, algo must be set to
|
||||
* "BIP0340/nonce" and algolen to 13.
|
||||
* Returns: 1
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: out64: pointer to a 64-byte array to store the serialized signature
|
||||
* In: sig: pointer to the signature
|
||||
*
|
||||
* See secp256k1_schnorrsig_parse for details about the encoding.
|
||||
*/
|
||||
SECP256K1_API const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340;
|
||||
SECP256K1_API int secp256k1_schnorrsig_serialize(
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *out64,
|
||||
const secp256k1_schnorrsig* sig
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Data structure that contains additional arguments for schnorrsig_sign_custom.
|
||||
/** Parse a Schnorr signature.
|
||||
*
|
||||
* A schnorrsig_extraparams structure object can be initialized correctly by
|
||||
* setting it to SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT.
|
||||
* Returns: 1 when the signature could be parsed, 0 otherwise.
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: sig: pointer to a signature object
|
||||
* In: in64: pointer to the 64-byte signature to be parsed
|
||||
*
|
||||
* Members:
|
||||
* magic: set to SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC at initialization
|
||||
* and has no other function than making sure the object is
|
||||
* initialized.
|
||||
* noncefp: pointer to a nonce generation function. If NULL,
|
||||
* secp256k1_nonce_function_bip340 is used
|
||||
* ndata: pointer to arbitrary data used by the nonce generation function
|
||||
* (can be NULL). If it is non-NULL and
|
||||
* secp256k1_nonce_function_bip340 is used, then ndata must be a
|
||||
* pointer to 32-byte auxiliary randomness as per BIP-340.
|
||||
* The signature is serialized in the form R||s, where R is a 32-byte public
|
||||
* key (x-coordinate only; the y-coordinate is considered to be the unique
|
||||
* y-coordinate satisfying the curve equation that is a quadratic residue)
|
||||
* and s is a 32-byte big-endian scalar.
|
||||
*
|
||||
* After the call, sig will always be initialized. If parsing failed or the
|
||||
* encoded numbers are out of range, signature validation with it is
|
||||
* guaranteed to fail for every message and public key.
|
||||
*/
|
||||
typedef struct secp256k1_schnorrsig_extraparams {
|
||||
unsigned char magic[4];
|
||||
secp256k1_nonce_function_hardened noncefp;
|
||||
void *ndata;
|
||||
} secp256k1_schnorrsig_extraparams;
|
||||
|
||||
#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC { 0xda, 0x6f, 0xb3, 0x8c }
|
||||
#define SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT {\
|
||||
SECP256K1_SCHNORRSIG_EXTRAPARAMS_MAGIC,\
|
||||
NULL,\
|
||||
NULL\
|
||||
}
|
||||
SECP256K1_API int secp256k1_schnorrsig_parse(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_schnorrsig* sig,
|
||||
const unsigned char *in64
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Create a Schnorr signature.
|
||||
*
|
||||
* Does _not_ strictly follow BIP-340 because it does not verify the resulting
|
||||
* signature. Instead, you can manually use secp256k1_schnorrsig_verify and
|
||||
* abort if it fails.
|
||||
*
|
||||
* This function only signs 32-byte messages. If you have messages of a
|
||||
* different size (or the same size but without a context-specific tag
|
||||
* prefix), it is recommended to create a 32-byte message hash with
|
||||
* secp256k1_tagged_sha256 and then sign the hash. Tagged hashing allows
|
||||
* providing an context-specific tag for domain separation. This prevents
|
||||
* signatures from being valid in multiple contexts by accident.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static).
|
||||
* Out: sig64: pointer to a 64-byte array to store the serialized signature.
|
||||
* In: msg32: the 32-byte message being signed.
|
||||
* keypair: pointer to an initialized keypair.
|
||||
* aux_rand32: 32 bytes of fresh randomness. While recommended to provide
|
||||
* this, it is only supplemental to security and can be NULL. A
|
||||
* NULL argument is treated the same as an all-zero one. See
|
||||
* BIP-340 "Default Signing" for a full explanation of this
|
||||
* argument and for guidance if randomness is expensive.
|
||||
* Returns 1 on success, 0 on failure.
|
||||
* Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
|
||||
* Out: sig: pointer to the returned signature (cannot be NULL)
|
||||
* nonce_is_negated: a pointer to an integer indicates if signing algorithm negated the
|
||||
* nonce (can be NULL)
|
||||
* In: msg32: the 32-byte message hash being signed (cannot be NULL)
|
||||
* seckey: pointer to a 32-byte secret key (cannot be NULL)
|
||||
* noncefp: pointer to a nonce generation function. If NULL, secp256k1_nonce_function_bipschnorr is used
|
||||
* ndata: pointer to arbitrary data used by the nonce generation function (can be NULL)
|
||||
*/
|
||||
SECP256K1_API int secp256k1_schnorrsig_sign32(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *sig64,
|
||||
const unsigned char *msg32,
|
||||
const secp256k1_keypair *keypair,
|
||||
const unsigned char *aux_rand32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Same as secp256k1_schnorrsig_sign32, but DEPRECATED. Will be removed in
|
||||
* future versions. */
|
||||
SECP256K1_API int secp256k1_schnorrsig_sign(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *sig64,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_schnorrsig *sig,
|
||||
int *nonce_is_negated,
|
||||
const unsigned char *msg32,
|
||||
const secp256k1_keypair *keypair,
|
||||
const unsigned char *aux_rand32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)
|
||||
SECP256K1_DEPRECATED("Use secp256k1_schnorrsig_sign32 instead");
|
||||
|
||||
/** Create a Schnorr signature with a more flexible API.
|
||||
*
|
||||
* Same arguments as secp256k1_schnorrsig_sign except that it allows signing
|
||||
* variable length messages and accepts a pointer to an extraparams object that
|
||||
* allows customizing signing by passing additional arguments.
|
||||
*
|
||||
* Equivalent to secp256k1_schnorrsig_sign32(..., aux_rand32) if msglen is 32
|
||||
* and extraparams is initialized as follows:
|
||||
* ```
|
||||
* secp256k1_schnorrsig_extraparams extraparams = SECP256K1_SCHNORRSIG_EXTRAPARAMS_INIT;
|
||||
* extraparams.ndata = (unsigned char*)aux_rand32;
|
||||
* ```
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
* Args: ctx: pointer to a context object (not secp256k1_context_static).
|
||||
* Out: sig64: pointer to a 64-byte array to store the serialized signature.
|
||||
* In: msg: the message being signed. Can only be NULL if msglen is 0.
|
||||
* msglen: length of the message.
|
||||
* keypair: pointer to an initialized keypair.
|
||||
* extraparams: pointer to an extraparams object (can be NULL).
|
||||
*/
|
||||
SECP256K1_API int secp256k1_schnorrsig_sign_custom(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *sig64,
|
||||
const unsigned char *msg,
|
||||
size_t msglen,
|
||||
const secp256k1_keypair *keypair,
|
||||
secp256k1_schnorrsig_extraparams *extraparams
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
|
||||
const unsigned char *seckey,
|
||||
secp256k1_nonce_function noncefp,
|
||||
void *ndata
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Verify a Schnorr signature.
|
||||
*
|
||||
* Returns: 1: correct signature
|
||||
* 0: incorrect signature
|
||||
* Args: ctx: pointer to a context object.
|
||||
* In: sig64: pointer to the 64-byte signature to verify.
|
||||
* msg: the message being verified. Can only be NULL if msglen is 0.
|
||||
* msglen: length of the message
|
||||
* pubkey: pointer to an x-only public key to verify with
|
||||
* 0: incorrect or unparseable signature
|
||||
* Args: ctx: a secp256k1 context object, initialized for verification.
|
||||
* In: sig: the signature being verified (cannot be NULL)
|
||||
* msg32: the 32-byte message hash being verified (cannot be NULL)
|
||||
* pubkey: pointer to a public key to verify with (cannot be NULL)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const unsigned char *sig64,
|
||||
const unsigned char *msg,
|
||||
size_t msglen,
|
||||
const secp256k1_xonly_pubkey *pubkey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5);
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_schnorrsig *sig,
|
||||
const unsigned char *msg32,
|
||||
const secp256k1_pubkey *pubkey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
/** Verifies a set of Schnorr signatures.
|
||||
*
|
||||
* Returns 1 if all succeeded, 0 otherwise. In particular, returns 1 if n_sigs is 0.
|
||||
*
|
||||
* Args: ctx: a secp256k1 context object, initialized for verification.
|
||||
* scratch: scratch space used for the multiexponentiation
|
||||
* In: sig: array of signatures, or NULL if there are no signatures
|
||||
* msg32: array of messages, or NULL if there are no signatures
|
||||
* pk: array of public keys, or NULL if there are no signatures
|
||||
* n_sigs: number of signatures in above arrays. Must be smaller than
|
||||
* 2^31 and smaller than half the maximum size_t value. Must be 0
|
||||
* if above arrays are NULL.
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify_batch(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_scratch_space *scratch,
|
||||
const secp256k1_schnorrsig *const *sig,
|
||||
const unsigned char *const *msg32,
|
||||
const secp256k1_pubkey *const *pk,
|
||||
size_t n_sigs
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_SCHNORRSIG_H */
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
#ifndef SECP256K1_SCHNORRSIG_HALFAGG_H
|
||||
#define SECP256K1_SCHNORRSIG_HALFAGG_H
|
||||
|
||||
#include "secp256k1.h"
|
||||
#include "secp256k1_extrakeys.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/** Incrementally (Half-)Aggregate a sequence of Schnorr
|
||||
* signatures to an existing half-aggregate signature.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
* Args: ctx: a secp256k1 context object.
|
||||
* In/Out: aggsig: pointer to the serialized aggregate signature
|
||||
* that is input. The first 32*(n_before+1) of this
|
||||
* array should hold the input aggsig. It will be
|
||||
* overwritten by the new serialized aggregate signature.
|
||||
* It should be large enough for that, see aggsig_len.
|
||||
* aggsig_len: size of aggsig array in bytes.
|
||||
* Should be large enough to hold the new
|
||||
* serialized aggregate signature, i.e.,
|
||||
* should satisfy aggsig_size >= 32*(n_before+n_new+1).
|
||||
* It will be overwritten to be the exact size of the
|
||||
* resulting aggsig.
|
||||
* In: all_pubkeys: Array of (n_before + n_new) many x-only public keys,
|
||||
* including both the ones for the already aggregated signature
|
||||
* and the ones for the signatures that should be added.
|
||||
* Can only be NULL if n_before + n_new is 0.
|
||||
* all_msgs32: Array of (n_before + n_new) many 32-byte messages,
|
||||
* including both the ones for the already aggregated signature
|
||||
* and the ones for the signatures that should be added.
|
||||
* Can only be NULL if n_before + n_new is 0.
|
||||
* new_sigs64: Array of n_new many 64-byte signatures, containing the new
|
||||
* signatures that should be added. Can only be NULL if n_new is 0.
|
||||
* n_before: Number of signatures that have already been aggregated
|
||||
* in the input aggregate signature.
|
||||
* n_new: Number of signatures that should now be added
|
||||
* to the aggregate signature.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_schnorrsig_inc_aggregate(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *aggsig,
|
||||
size_t *aggsig_len,
|
||||
const secp256k1_xonly_pubkey* all_pubkeys,
|
||||
const unsigned char *all_msgs32,
|
||||
const unsigned char *new_sigs64,
|
||||
size_t n_before,
|
||||
size_t n_new
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** (Half-)Aggregate a sequence of Schnorr signatures.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure.
|
||||
* Args: ctx: a secp256k1 context object.
|
||||
* Out: aggsig: pointer to an array of aggsig_len many bytes to
|
||||
* store the serialized aggregate signature.
|
||||
* In/Out: aggsig_len: size of the aggsig array that is passed in bytes;
|
||||
* will be overwritten to be the exact size of aggsig.
|
||||
* In: pubkeys: Array of n many x-only public keys.
|
||||
* Can only be NULL if n is 0.
|
||||
* msgs32: Array of n many 32-byte messages.
|
||||
* Can only be NULL if n is 0.
|
||||
* sigs64: Array of n many 64-byte signatures.
|
||||
* Can only be NULL if n is 0.
|
||||
* n: number of signatures to be aggregated.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_schnorrsig_aggregate(
|
||||
const secp256k1_context *ctx,
|
||||
unsigned char *aggsig,
|
||||
size_t *aggsig_len,
|
||||
const secp256k1_xonly_pubkey *pubkeys,
|
||||
const unsigned char *msgs32,
|
||||
const unsigned char *sigs64,
|
||||
size_t n
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Verify a (Half-)aggregate Schnorr signature.
|
||||
*
|
||||
* Returns: 1: correct signature.
|
||||
* 0: incorrect signature.
|
||||
* Args: ctx: a secp256k1 context object.
|
||||
* In: pubkeys: Array of n many x-only public keys. Can only be NULL if n is 0.
|
||||
* msgs32: Array of n many 32-byte messages. Can only be NULL if n is 0.
|
||||
* n: number of signatures to that have been aggregated.
|
||||
* aggsig: Pointer to an array of aggsig_size many bytes
|
||||
* containing the serialized aggregate
|
||||
* signature to be verified.
|
||||
* aggsig_len: Size of the aggregate signature in bytes.
|
||||
* Should be aggsig_len = 32*(n+1)
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_aggverify(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_xonly_pubkey *pubkeys,
|
||||
const unsigned char *msgs32,
|
||||
size_t n,
|
||||
const unsigned char *aggsig,
|
||||
size_t aggsig_len
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SECP256K1_SCHNORRSIG_HALFAGG_H */
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SECP256K1_SURJECTIONPROOF_H
|
||||
#define SECP256K1_SURJECTIONPROOF_H
|
||||
#ifndef _SECP256K1_SURJECTIONPROOF_
|
||||
#define _SECP256K1_SURJECTIONPROOF_
|
||||
|
||||
#include "secp256k1.h"
|
||||
#include "secp256k1_rangeproof.h"
|
||||
@@ -11,9 +11,6 @@ extern "C" {
|
||||
/** Maximum number of inputs that may be given in a surjection proof */
|
||||
#define SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS 256
|
||||
|
||||
/** Maximum number of inputs that may be used in a surjection proof */
|
||||
#define SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS 256
|
||||
|
||||
/** Number of bytes a serialized surjection proof requires given the
|
||||
* number of inputs and the number of used inputs.
|
||||
*/
|
||||
@@ -22,7 +19,7 @@ extern "C" {
|
||||
|
||||
/** Maximum number of bytes a serialized surjection proof requires. */
|
||||
#define SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES_MAX \
|
||||
SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS)
|
||||
SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS)
|
||||
|
||||
/** Opaque data structure that holds a parsed surjection proof
|
||||
*
|
||||
@@ -39,7 +36,7 @@ extern "C" {
|
||||
* The representation is exposed to allow creation of these objects on the
|
||||
* stack; please *do not* use these internals directly.
|
||||
*/
|
||||
typedef struct secp256k1_surjectionproof {
|
||||
typedef struct {
|
||||
#ifdef VERIFY
|
||||
/** Mark whether this proof has gone through `secp256k1_surjectionproof_initialize` */
|
||||
int initialized;
|
||||
@@ -49,16 +46,15 @@ typedef struct secp256k1_surjectionproof {
|
||||
/** Bitmap of which input tags are used in the surjection proof */
|
||||
unsigned char used_inputs[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS / 8];
|
||||
/** Borromean signature: e0, scalars */
|
||||
unsigned char data[32 * (1 + SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS)];
|
||||
unsigned char data[32 * (1 + SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS)];
|
||||
} secp256k1_surjectionproof;
|
||||
|
||||
#ifndef USE_REDUCED_SURJECTION_PROOF_SIZE
|
||||
/** Parse a surjection proof
|
||||
*
|
||||
* Returns: 1 when the proof could be parsed, 0 otherwise.
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: proof: pointer to a proof object
|
||||
* In: input: pointer to the array to parse
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: proof: a pointer to a proof object
|
||||
* In: input: a pointer to the array to parse
|
||||
* inputlen: length of the array pointed to by input
|
||||
*
|
||||
* The proof must consist of:
|
||||
@@ -69,26 +65,26 @@ typedef struct secp256k1_surjectionproof {
|
||||
* is the number of set bits in the bitmap
|
||||
*/
|
||||
SECP256K1_API int secp256k1_surjectionproof_parse(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_surjectionproof *proof,
|
||||
const unsigned char *input,
|
||||
size_t inputlen
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
#endif
|
||||
|
||||
/** Serialize a surjection proof
|
||||
*
|
||||
* Returns: 1 if enough space was available to serialize, 0 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: output: pointer to an array to store the serialization
|
||||
* In/Out: outputlen: pointer to an integer which is initially set to the size
|
||||
* of output, and is overwritten with the written size.
|
||||
* In: proof: pointer to an initialized proof object
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: output: a pointer to an array to store the serialization
|
||||
* In/Out: outputlen: a pointer to an integer which is initially set to the
|
||||
* size of output, and is overwritten with the written
|
||||
* size.
|
||||
* In: proof: a pointer to an initialized proof object
|
||||
*
|
||||
* See secp256k1_surjectionproof_parse for details about the encoding.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_surjectionproof_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *output,
|
||||
size_t *outputlen,
|
||||
const secp256k1_surjectionproof *proof
|
||||
@@ -100,7 +96,7 @@ SECP256K1_API int secp256k1_surjectionproof_serialize(
|
||||
* data the API user wants to use as an asset tag. Its contents have no
|
||||
* semantic meaning to libsecp whatsoever.
|
||||
*/
|
||||
typedef struct secp256k1_fixed_asset_tag {
|
||||
typedef struct {
|
||||
unsigned char data[32];
|
||||
} secp256k1_fixed_asset_tag;
|
||||
|
||||
@@ -108,33 +104,33 @@ typedef struct secp256k1_fixed_asset_tag {
|
||||
*
|
||||
* Returns: the number of inputs for the given proof
|
||||
* In: ctx: pointer to a context object
|
||||
* proof: pointer to a proof object
|
||||
* proof: a pointer to a proof object
|
||||
*/
|
||||
SECP256K1_API size_t secp256k1_surjectionproof_n_total_inputs(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_surjectionproof *proof
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_surjectionproof* proof
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
|
||||
|
||||
/** Returns the actual number of inputs that a proof uses
|
||||
*
|
||||
* Returns: the number of inputs for the given proof
|
||||
* In: ctx: pointer to a context object
|
||||
* proof: pointer to a proof object
|
||||
* proof: a pointer to a proof object
|
||||
*/
|
||||
SECP256K1_API size_t secp256k1_surjectionproof_n_used_inputs(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_surjectionproof *proof
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_surjectionproof* proof
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
|
||||
|
||||
/** Returns the total size this proof would take, in bytes, when serialized
|
||||
*
|
||||
* Returns: the total size
|
||||
* In: ctx: pointer to a context object
|
||||
* proof: pointer to a proof object
|
||||
* proof: a pointer to a proof object
|
||||
*/
|
||||
SECP256K1_API size_t secp256k1_surjectionproof_serialized_size(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_surjectionproof *proof
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_surjectionproof* proof
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
|
||||
|
||||
/** Surjection proof initialization function; decides on inputs to use
|
||||
@@ -147,27 +143,26 @@ SECP256K1_API size_t secp256k1_surjectionproof_serialized_size(
|
||||
* e.g. in a coinjoin with others' inputs, an ephemeral tag can be given;
|
||||
* this won't match the output tag but might be used in the anonymity set.)
|
||||
* n_input_tags: the number of entries in the fixed_input_tags array
|
||||
* n_input_tags_to_use: the number of inputs to select randomly to put in the anonymity set
|
||||
* Must be <= SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS
|
||||
* n_input_tags_to_use: the number of inputs to select randomly to put in the anonymity set
|
||||
* fixed_output_tag: fixed output tag
|
||||
* max_n_iterations: the maximum number of iterations to do before giving up. Because the
|
||||
* maximum number of inputs (SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS) is
|
||||
* limited to 256 the probability of giving up is smaller than
|
||||
* (255/256)^(n_input_tags_to_use*max_n_iterations).
|
||||
*
|
||||
* random_seed32: random seed to be used for input selection
|
||||
* random_seed32: a random seed to be used for input selection
|
||||
* Out: proof: The proof whose bitvector will be initialized. In case of failure,
|
||||
* the state of the proof is undefined.
|
||||
* input_index: The index of the actual input that is secretly mapped to the output
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_initialize(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_surjectionproof *proof,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_surjectionproof* proof,
|
||||
size_t *input_index,
|
||||
const secp256k1_fixed_asset_tag *fixed_input_tags,
|
||||
const secp256k1_fixed_asset_tag* fixed_input_tags,
|
||||
const size_t n_input_tags,
|
||||
const size_t n_input_tags_to_use,
|
||||
const secp256k1_fixed_asset_tag *fixed_output_tag,
|
||||
const secp256k1_fixed_asset_tag* fixed_output_tag,
|
||||
const size_t n_max_iterations,
|
||||
const unsigned char *random_seed32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7);
|
||||
@@ -178,8 +173,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_initial
|
||||
* n: inputs were selected after n iterations of random selection
|
||||
*
|
||||
* In: ctx: pointer to a context object
|
||||
* proof_out_p: pointer to a pointer to `secp256k1_surjectionproof*`.
|
||||
* The newly-allocated struct pointer will be saved here.
|
||||
* proof_out_p: a pointer to a pointer to `secp256k1_surjectionproof*`.
|
||||
* the newly-allocated struct pointer will be saved here.
|
||||
* fixed_input_tags: fixed input tags `A_i` for all inputs. (If the fixed tag is not known,
|
||||
* e.g. in a coinjoin with others' inputs, an ephemeral tag can be given;
|
||||
* this won't match the output tag but might be used in the anonymity set.)
|
||||
@@ -191,19 +186,19 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_initial
|
||||
* limited to 256 the probability of giving up is smaller than
|
||||
* (255/256)^(n_input_tags_to_use*max_n_iterations).
|
||||
*
|
||||
* random_seed32: random seed to be used for input selection
|
||||
* Out: proof_out_p: pointer to newly-allocated proof whose bitvector will be initialized.
|
||||
* random_seed32: a random seed to be used for input selection
|
||||
* Out: proof_out_p: The pointer to newly-allocated proof whose bitvector will be initialized.
|
||||
* In case of failure, the pointer will be NULL.
|
||||
* input_index: The index of the actual input that is secretly mapped to the output
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_allocate_initialized(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_surjectionproof **proof_out_p,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_surjectionproof** proof_out_p,
|
||||
size_t *input_index,
|
||||
const secp256k1_fixed_asset_tag *fixed_input_tags,
|
||||
const secp256k1_fixed_asset_tag* fixed_input_tags,
|
||||
const size_t n_input_tags,
|
||||
const size_t n_input_tags_to_use,
|
||||
const secp256k1_fixed_asset_tag *fixed_output_tag,
|
||||
const secp256k1_fixed_asset_tag* fixed_output_tag,
|
||||
const size_t n_max_iterations,
|
||||
const unsigned char *random_seed32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7);
|
||||
@@ -214,14 +209,14 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_allocat
|
||||
* In: proof: pointer to secp256k1_surjectionproof struct
|
||||
*/
|
||||
SECP256K1_API void secp256k1_surjectionproof_destroy(
|
||||
secp256k1_surjectionproof *proof
|
||||
secp256k1_surjectionproof* proof
|
||||
) SECP256K1_ARG_NONNULL(1);
|
||||
|
||||
/** Surjection proof generation function
|
||||
* Returns 0: proof could not be created
|
||||
* 1: proof was successfully created
|
||||
*
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* In: ctx: pointer to a context object, initialized for signing and verification
|
||||
* ephemeral_input_tags: the ephemeral asset tag of all inputs
|
||||
* n_ephemeral_input_tags: the number of entries in the ephemeral_input_tags array
|
||||
* ephemeral_output_tag: the ephemeral asset tag of the output
|
||||
@@ -231,36 +226,34 @@ SECP256K1_API void secp256k1_surjectionproof_destroy(
|
||||
* In/Out: proof: The produced surjection proof. Must have already gone through `secp256k1_surjectionproof_initialize`
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_generate(
|
||||
const secp256k1_context *ctx,
|
||||
secp256k1_surjectionproof *proof,
|
||||
const secp256k1_generator *ephemeral_input_tags,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_surjectionproof* proof,
|
||||
const secp256k1_generator* ephemeral_input_tags,
|
||||
size_t n_ephemeral_input_tags,
|
||||
const secp256k1_generator *ephemeral_output_tag,
|
||||
const secp256k1_generator* ephemeral_output_tag,
|
||||
size_t input_index,
|
||||
const unsigned char *input_blinding_key,
|
||||
const unsigned char *output_blinding_key
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
|
||||
#ifndef USE_REDUCED_SURJECTION_PROOF_SIZE
|
||||
/** Surjection proof verification function
|
||||
* Returns 0: proof was invalid
|
||||
* 1: proof was valid
|
||||
*
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* In: ctx: pointer to a context object, initialized for signing and verification
|
||||
* proof: proof to be verified
|
||||
* ephemeral_input_tags: the ephemeral asset tag of all inputs
|
||||
* n_ephemeral_input_tags: the number of entries in the ephemeral_input_tags array
|
||||
* ephemeral_output_tag: the ephemeral asset tag of the output
|
||||
*/
|
||||
SECP256K1_API int secp256k1_surjectionproof_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_surjectionproof *proof,
|
||||
const secp256k1_generator *ephemeral_input_tags,
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_surjectionproof* proof,
|
||||
const secp256k1_generator* ephemeral_input_tags,
|
||||
size_t n_ephemeral_input_tags,
|
||||
const secp256k1_generator *ephemeral_output_tag
|
||||
const secp256k1_generator* ephemeral_output_tag
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_WHITELIST_H
|
||||
#define SECP256K1_WHITELIST_H
|
||||
#ifndef _SECP256K1_WHITELIST_
|
||||
#define _SECP256K1_WHITELIST_
|
||||
|
||||
#include "secp256k1.h"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SECP256K1_WHITELIST_MAX_N_KEYS 255
|
||||
#define SECP256K1_WHITELIST_MAX_N_KEYS 256
|
||||
|
||||
/** Opaque data structure that holds a parsed whitelist proof
|
||||
*
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
* stack; please *do not* use these internals directly. To learn the number
|
||||
* of keys for a signature, use `secp256k1_whitelist_signature_n_keys`.
|
||||
*/
|
||||
typedef struct secp256k1_whitelist_signature {
|
||||
typedef struct {
|
||||
size_t n_keys;
|
||||
/* e0, scalars */
|
||||
unsigned char data[32 * (1 + SECP256K1_WHITELIST_MAX_N_KEYS)];
|
||||
@@ -40,9 +40,9 @@ typedef struct secp256k1_whitelist_signature {
|
||||
/** Parse a whitelist 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: input: pointer to the array to parse
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: sig: a pointer to a signature object
|
||||
* In: input: a pointer to the array to parse
|
||||
* input_len: the length of the above array
|
||||
*
|
||||
* The signature must consist of a 1-byte n_keys value, followed by a 32-byte
|
||||
@@ -58,7 +58,7 @@ typedef struct secp256k1_whitelist_signature {
|
||||
* to fail validation for any set of keys.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_signature_parse(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_whitelist_signature *sig,
|
||||
const unsigned char *input,
|
||||
size_t input_len
|
||||
@@ -67,11 +67,7 @@ SECP256K1_API int secp256k1_whitelist_signature_parse(
|
||||
/** Returns the number of keys a signature expects to have.
|
||||
*
|
||||
* Returns: the number of keys for the given signature
|
||||
* In: sig: pointer to a signature object
|
||||
*
|
||||
* This count is a property of the signature only. It might not match the
|
||||
* number of public keys the caller has, and the caller should not truncate
|
||||
* their key set to match it.
|
||||
* In: sig: a pointer to a signature object
|
||||
*/
|
||||
SECP256K1_API size_t secp256k1_whitelist_signature_n_keys(
|
||||
const secp256k1_whitelist_signature *sig
|
||||
@@ -80,15 +76,15 @@ SECP256K1_API size_t secp256k1_whitelist_signature_n_keys(
|
||||
/** Serialize a whitelist signature
|
||||
*
|
||||
* Returns: 1
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: output64: pointer to an array to store the serialization
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: output64: a pointer to an array to store the serialization
|
||||
* In/Out: output_len: length of the above array, updated with the actual serialized length
|
||||
* In: sig: pointer to an initialized signature object
|
||||
* In: sig: a pointer to an initialized signature object
|
||||
*
|
||||
* See secp256k1_whitelist_signature_parse for details about the encoding.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_signature_serialize(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *output,
|
||||
size_t *output_len,
|
||||
const secp256k1_whitelist_signature *sig
|
||||
@@ -97,7 +93,7 @@ SECP256K1_API int secp256k1_whitelist_signature_serialize(
|
||||
/** Compute a whitelist signature
|
||||
* Returns 1: signature was successfully created
|
||||
* 0: signature was not successfully created
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* In: ctx: pointer to a context object, initialized for signing and verification
|
||||
* online_pubkeys: list of all online pubkeys
|
||||
* offline_pubkeys: list of all offline pubkeys
|
||||
* n_keys: the number of entries in each of the above two arrays
|
||||
@@ -105,6 +101,8 @@ SECP256K1_API int secp256k1_whitelist_signature_serialize(
|
||||
* online_seckey: the secret key to the signer's online pubkey
|
||||
* summed_seckey: the secret key to the sum of (whitelisted key, signer's offline pubkey)
|
||||
* index: the signer's index in the lists of keys
|
||||
* noncefp:pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used
|
||||
* ndata: pointer to arbitrary data used by the nonce generation function (can be NULL)
|
||||
* Out: sig: The produced signature.
|
||||
*
|
||||
* The signatures are of the list of all passed pubkeys in the order
|
||||
@@ -113,40 +111,33 @@ SECP256K1_API int secp256k1_whitelist_signature_serialize(
|
||||
* online_i + H(offline_i + whitelist)(offline_i + whitelist)
|
||||
* for each public key pair (offline_i, offline_i). Here H means sha256 of the
|
||||
* compressed serialization of the key.
|
||||
*
|
||||
* See secp256k1_whitelist_verify for the rationale on the degenerate destination W = -P_i.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_sign(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_whitelist_signature *sig,
|
||||
const secp256k1_pubkey *online_pubkeys,
|
||||
const secp256k1_pubkey *offline_pubkeys,
|
||||
const size_t n_keys,
|
||||
const secp256k1_pubkey *sub_pubkey,
|
||||
const unsigned char *online_seckey,
|
||||
const unsigned char *summed_seckeyx,
|
||||
const size_t index
|
||||
const unsigned char *summed_seckey,
|
||||
const size_t index,
|
||||
secp256k1_nonce_function noncefp,
|
||||
const void *noncedata
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
/** Verify a whitelist signature
|
||||
* Returns 1: signature is valid
|
||||
* 0: signature is not valid
|
||||
* In: ctx: pointer to a context object (not secp256k1_context_static)
|
||||
* In: ctx: pointer to a context object, initialized for signing and verification
|
||||
* sig: the signature to be verified
|
||||
* online_pubkeys: list of all online pubkeys
|
||||
* offline_pubkeys: list of all offline pubkeys
|
||||
* n_keys: the number of entries in each of the above two arrays
|
||||
* sub_pubkey: the key to be whitelisted
|
||||
*
|
||||
* When the destination W equals -P_i for a whitelisted offline key, the tweak
|
||||
* degenerates and the ring key collapses to K_i = Q_i, so the online key alone
|
||||
* produces a valid proof for that destination. This is accepted deliberately:
|
||||
* the output is spendable only by the holder of p_i (the discrete log of -P_i
|
||||
* is -p_i), i.e. the offline half of the same whitelist entry, so no funds can
|
||||
* be diverted.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_verify(
|
||||
const secp256k1_context *ctx,
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_whitelist_signature *sig,
|
||||
const secp256k1_pubkey *online_pubkeys,
|
||||
const secp256k1_pubkey *offline_pubkeys,
|
||||
|
||||
@@ -9,4 +9,5 @@ URL: https://github.com/bitcoin-core/secp256k1
|
||||
Version: @PACKAGE_VERSION@
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lsecp256k1
|
||||
Libs.private: @SECP_LIBS@
|
||||
|
||||
|
||||
0
obj/.gitignore
vendored
Normal file
0
obj/.gitignore
vendored
Normal file
@@ -1,156 +0,0 @@
|
||||
load("secp256k1_params.sage")
|
||||
|
||||
MAX_ORDER = 1000
|
||||
|
||||
# Set of (curve) orders we have encountered so far.
|
||||
orders_done = set()
|
||||
|
||||
# Map from (subgroup) orders to [b, int(gen.x), int(gen.y), gen, lambda] for those subgroups.
|
||||
solutions = {}
|
||||
|
||||
# Iterate over curves of the form y^2 = x^3 + B.
|
||||
for b in range(1, P):
|
||||
# There are only 6 curves (up to isomorphism) of the form y^2 = x^3 + B. Stop once we have tried all.
|
||||
if len(orders_done) == 6:
|
||||
break
|
||||
|
||||
E = EllipticCurve(F, [0, b])
|
||||
print("Analyzing curve y^2 = x^3 + %i" % b)
|
||||
n = E.order()
|
||||
|
||||
# Skip curves with an order we've already tried
|
||||
if n in orders_done:
|
||||
print("- Isomorphic to earlier curve")
|
||||
print()
|
||||
continue
|
||||
orders_done.add(n)
|
||||
|
||||
# Skip curves isomorphic to the real secp256k1
|
||||
if n.is_pseudoprime():
|
||||
assert E.is_isomorphic(C)
|
||||
print("- Isomorphic to secp256k1")
|
||||
print()
|
||||
continue
|
||||
|
||||
print("- Finding prime subgroups")
|
||||
|
||||
# Map from group_order to a set of independent generators for that order.
|
||||
curve_gens = {}
|
||||
|
||||
for g in E.gens():
|
||||
# Find what prime subgroups of group generated by g exist.
|
||||
g_order = g.order()
|
||||
for f, _ in g.order().factor():
|
||||
# Skip subgroups that have bad size.
|
||||
if f < 4:
|
||||
print(f" - Subgroup of size {f}: too small")
|
||||
continue
|
||||
if f > MAX_ORDER:
|
||||
print(f" - Subgroup of size {f}: too large")
|
||||
continue
|
||||
|
||||
# Construct a generator for that subgroup.
|
||||
gen = g * (g_order // f)
|
||||
assert(gen.order() == f)
|
||||
|
||||
# Add to set the minimal multiple of gen.
|
||||
curve_gens.setdefault(f, set()).add(min([j*gen for j in range(1, f)]))
|
||||
print(f" - Subgroup of size {f}: ok")
|
||||
|
||||
for f in sorted(curve_gens.keys()):
|
||||
print(f"- Constructing group of order {f}")
|
||||
cbrts = sorted([int(c) for c in Integers(f)(1).nth_root(3, all=true) if c != 1])
|
||||
gens = list(curve_gens[f])
|
||||
sol_count = 0
|
||||
no_endo_count = 0
|
||||
|
||||
# Consider all non-zero linear combinations of the independent generators.
|
||||
for j in range(1, f**len(gens)):
|
||||
gen = sum(gens[k] * ((j // f**k) % f) for k in range(len(gens)))
|
||||
assert not gen.is_zero()
|
||||
assert (f*gen).is_zero()
|
||||
|
||||
# Find lambda for endomorphism. Skip if none can be found.
|
||||
lam = None
|
||||
for l in cbrts:
|
||||
if l*gen == E(BETA*gen[0], gen[1]):
|
||||
lam = l
|
||||
break
|
||||
|
||||
if lam is None:
|
||||
no_endo_count += 1
|
||||
else:
|
||||
sol_count += 1
|
||||
solutions.setdefault(f, []).append((b, int(gen[0]), int(gen[1]), gen, lam))
|
||||
|
||||
print(f" - Found {sol_count} generators (plus {no_endo_count} without endomorphism)")
|
||||
|
||||
print()
|
||||
|
||||
def output_generator(g, name):
|
||||
print(f"#define {name} SECP256K1_GE_CONST(\\")
|
||||
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x,\\" % tuple((int(g[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
|
||||
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x,\\" % tuple((int(g[0]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
|
||||
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x,\\" % tuple((int(g[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4)))
|
||||
print(" 0x%08x, 0x%08x, 0x%08x, 0x%08x\\" % tuple((int(g[1]) >> (32 * (7 - i))) & 0xffffffff for i in range(4, 8)))
|
||||
print(")")
|
||||
|
||||
def output_b(b):
|
||||
print(f"#define SECP256K1_B {int(b)}")
|
||||
|
||||
print()
|
||||
print("To be put in src/group_impl.h:")
|
||||
print()
|
||||
print("/* Begin of section generated by sage/gen_exhaustive_groups.sage. */")
|
||||
for f in sorted(solutions.keys()):
|
||||
# Use as generator/2 the one with lowest b, and lowest (x, y) generator (interpreted as non-negative integers).
|
||||
b, _, _, HALF_G, lam = min(solutions[f])
|
||||
output_generator(2 * HALF_G, f"SECP256K1_G_ORDER_{f}")
|
||||
print("/** Generator for secp256k1, value 'g' defined in")
|
||||
print(" * \"Standards for Efficient Cryptography\" (SEC2) 2.7.1.")
|
||||
print(" */")
|
||||
output_generator(G, "SECP256K1_G")
|
||||
print("/* These exhaustive group test orders and generators are chosen such that:")
|
||||
print(" * - The field size is equal to that of secp256k1, so field code is the same.")
|
||||
print(" * - The curve equation is of the form y^2=x^3+B for some small constant B.")
|
||||
print(" * - The subgroup has a generator 2*P, where P.x is as small as possible.")
|
||||
print(f" * - The subgroup has size less than {MAX_ORDER} to permit exhaustive testing.")
|
||||
print(" * - The subgroup admits an endomorphism of the form lambda*(x,y) == (beta*x,y).")
|
||||
print(" */")
|
||||
print("#if defined(EXHAUSTIVE_TEST_ORDER)")
|
||||
first = True
|
||||
for f in sorted(solutions.keys()):
|
||||
b, _, _, _, lam = min(solutions[f])
|
||||
print(f"# {'if' if first else 'elif'} EXHAUSTIVE_TEST_ORDER == {f}")
|
||||
first = False
|
||||
print()
|
||||
print(f"static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G_ORDER_{f};")
|
||||
output_b(b)
|
||||
print()
|
||||
print("# else")
|
||||
print("# error No known generator for the specified exhaustive test group order.")
|
||||
print("# endif")
|
||||
print("#else")
|
||||
print()
|
||||
print("static const secp256k1_ge secp256k1_ge_const_g = SECP256K1_G;")
|
||||
output_b(7)
|
||||
print()
|
||||
print("#endif")
|
||||
print("/* End of section generated by sage/gen_exhaustive_groups.sage. */")
|
||||
|
||||
|
||||
print()
|
||||
print()
|
||||
print("To be put in src/scalar_impl.h:")
|
||||
print()
|
||||
print("/* Begin of section generated by sage/gen_exhaustive_groups.sage. */")
|
||||
first = True
|
||||
for f in sorted(solutions.keys()):
|
||||
_, _, _, _, lam = min(solutions[f])
|
||||
print("# %s EXHAUSTIVE_TEST_ORDER == %i" % ("if" if first else "elif", f))
|
||||
first = False
|
||||
print("# define EXHAUSTIVE_TEST_LAMBDA %i" % lam)
|
||||
print("# else")
|
||||
print("# error No known lambda for the specified exhaustive test group order.")
|
||||
print("# endif")
|
||||
print("/* End of section generated by sage/gen_exhaustive_groups.sage. */")
|
||||
@@ -1,123 +0,0 @@
|
||||
""" Generates the constants used in secp256k1_scalar_split_lambda.
|
||||
|
||||
See the comments for secp256k1_scalar_split_lambda in src/scalar_impl.h for detailed explanations.
|
||||
"""
|
||||
|
||||
load("secp256k1_params.sage")
|
||||
|
||||
def inf_norm(v):
|
||||
"""Returns the infinity norm of a vector."""
|
||||
return max(map(abs, v))
|
||||
|
||||
def gauss_reduction(i1, i2):
|
||||
v1, v2 = i1.copy(), i2.copy()
|
||||
while True:
|
||||
if inf_norm(v2) < inf_norm(v1):
|
||||
v1, v2 = v2, v1
|
||||
# This is essentially
|
||||
# m = round((v1[0]*v2[0] + v1[1]*v2[1]) / (inf_norm(v1)**2))
|
||||
# (rounding to the nearest integer) without relying on floating point arithmetic.
|
||||
m = ((v1[0]*v2[0] + v1[1]*v2[1]) + (inf_norm(v1)**2) // 2) // (inf_norm(v1)**2)
|
||||
if m == 0:
|
||||
return v1, v2
|
||||
v2[0] -= m*v1[0]
|
||||
v2[1] -= m*v1[1]
|
||||
|
||||
def find_split_constants_gauss():
|
||||
"""Find constants for secp256k1_scalar_split_lamdba using gauss reduction."""
|
||||
(v11, v12), (v21, v22) = gauss_reduction([0, N], [1, int(LAMBDA)])
|
||||
|
||||
# We use related vectors in secp256k1_scalar_split_lambda.
|
||||
A1, B1 = -v21, -v11
|
||||
A2, B2 = v22, -v21
|
||||
|
||||
return A1, B1, A2, B2
|
||||
|
||||
def find_split_constants_explicit_tof():
|
||||
"""Find constants for secp256k1_scalar_split_lamdba using the trace of Frobenius.
|
||||
|
||||
See Benjamin Smith: "Easy scalar decompositions for efficient scalar multiplication on
|
||||
elliptic curves and genus 2 Jacobians" (https://eprint.iacr.org/2013/672), Example 2
|
||||
"""
|
||||
assert P % 3 == 1 # The paper says P % 3 == 2 but that appears to be a mistake, see [10].
|
||||
assert C.j_invariant() == 0
|
||||
|
||||
t = C.trace_of_frobenius()
|
||||
|
||||
c = Integer(sqrt((4*P - t**2)/3))
|
||||
A1 = Integer((t - c)/2 - 1)
|
||||
B1 = c
|
||||
|
||||
A2 = Integer((t + c)/2 - 1)
|
||||
B2 = Integer(1 - (t - c)/2)
|
||||
|
||||
# We use a negated b values in secp256k1_scalar_split_lambda.
|
||||
B1, B2 = -B1, -B2
|
||||
|
||||
return A1, B1, A2, B2
|
||||
|
||||
A1, B1, A2, B2 = find_split_constants_explicit_tof()
|
||||
|
||||
# For extra fun, use an independent method to recompute the constants.
|
||||
assert (A1, B1, A2, B2) == find_split_constants_gauss()
|
||||
|
||||
# PHI : Z[l] -> Z_n where phi(a + b*l) == a + b*lambda mod n.
|
||||
def PHI(a,b):
|
||||
return Z(a + LAMBDA*b)
|
||||
|
||||
# Check that (A1, B1) and (A2, B2) are in the kernel of PHI.
|
||||
assert PHI(A1, B1) == Z(0)
|
||||
assert PHI(A2, B2) == Z(0)
|
||||
|
||||
# Check that the parallelogram generated by (A1, A2) and (B1, B2)
|
||||
# is a fundamental domain by containing exactly N points.
|
||||
# Since the LHS is the determinant and N != 0, this also checks that
|
||||
# (A1, A2) and (B1, B2) are linearly independent. By the previous
|
||||
# assertions, (A1, A2) and (B1, B2) are a basis of the kernel.
|
||||
assert A1*B2 - B1*A2 == N
|
||||
|
||||
# Check that their components are short enough.
|
||||
assert (A1 + A2)/2 < sqrt(N)
|
||||
assert B1 < sqrt(N)
|
||||
assert B2 < sqrt(N)
|
||||
|
||||
# Verify connection to Eisenstein integers Z[w] where w = (-1 + sqrt(-3))/2.
|
||||
# The group order N factors as N = pi * conj(pi) in Z[w], where pi = A - B*w
|
||||
# is an Eisenstein prime with norm A^2 + A*B + B^2. The GLV endomorphism
|
||||
# eigenvalue LAMBDA equals B/A mod N, which is the image of w^2 under the
|
||||
# isomorphism Z[w]/(pi) -> Z/NZ (since w -> A/B and (A/B)^2 = B/A in Z/NZ).
|
||||
A_EIS, B_EIS = -B1, A1
|
||||
assert A_EIS**2 + A_EIS*B_EIS + B_EIS**2 == N
|
||||
assert Z(B_EIS / A_EIS) == LAMBDA
|
||||
|
||||
G1 = round((2**384)*B2/N)
|
||||
G2 = round((2**384)*(-B1)/N)
|
||||
|
||||
def rnddiv2(v):
|
||||
if v & 1:
|
||||
v += 1
|
||||
return v >> 1
|
||||
|
||||
def scalar_lambda_split(k):
|
||||
"""Equivalent to secp256k1_scalar_lambda_split()."""
|
||||
c1 = rnddiv2((k * G1) >> 383)
|
||||
c2 = rnddiv2((k * G2) >> 383)
|
||||
c1 = (c1 * -B1) % N
|
||||
c2 = (c2 * -B2) % N
|
||||
r2 = (c1 + c2) % N
|
||||
r1 = (k + r2 * -LAMBDA) % N
|
||||
return (r1, r2)
|
||||
|
||||
# The result of scalar_lambda_split can depend on the representation of k (mod n).
|
||||
SPECIAL = (2**383) // G2 + 1
|
||||
assert scalar_lambda_split(SPECIAL) != scalar_lambda_split(SPECIAL + N)
|
||||
|
||||
print(' A1 =', hex(A1))
|
||||
print(' -B1 =', hex(-B1))
|
||||
print(' A2 =', hex(A2))
|
||||
print(' -B2 =', hex(-B2))
|
||||
print(' =', hex(Z(-B2)))
|
||||
print(' -LAMBDA =', hex(-LAMBDA))
|
||||
|
||||
print(' G1 =', hex(G1))
|
||||
print(' G2 =', hex(G2))
|
||||
@@ -42,7 +42,7 @@
|
||||
# as we assume that all constraints in it are complementary with each other.
|
||||
#
|
||||
# Based on the sage verification scripts used in the Explicit-Formulas Database
|
||||
# by Tanja Lange and others, see https://hyperelliptic.org/EFD
|
||||
# by Tanja Lange and others, see http://hyperelliptic.org/EFD
|
||||
|
||||
class fastfrac:
|
||||
"""Fractions over rings."""
|
||||
@@ -65,7 +65,7 @@ class fastfrac:
|
||||
return self.top in I and self.bot not in I
|
||||
|
||||
def reduce(self,assumeZero):
|
||||
zero = self.R.ideal(list(map(numerator, assumeZero)))
|
||||
zero = self.R.ideal(map(numerator, assumeZero))
|
||||
return fastfrac(self.R, zero.reduce(self.top)) / fastfrac(self.R, zero.reduce(self.bot))
|
||||
|
||||
def __add__(self,other):
|
||||
@@ -100,7 +100,7 @@ class fastfrac:
|
||||
"""Multiply something else with a fraction."""
|
||||
return self.__mul__(other)
|
||||
|
||||
def __truediv__(self,other):
|
||||
def __div__(self,other):
|
||||
"""Divide two fractions."""
|
||||
if parent(other) == ZZ:
|
||||
return fastfrac(self.R,self.top,self.bot * other)
|
||||
@@ -108,11 +108,6 @@ class fastfrac:
|
||||
return fastfrac(self.R,self.top * other.bot,self.bot * other.top)
|
||||
return NotImplemented
|
||||
|
||||
# Compatibility wrapper for Sage versions based on Python 2
|
||||
def __div__(self,other):
|
||||
"""Divide two fractions."""
|
||||
return self.__truediv__(other)
|
||||
|
||||
def __pow__(self,other):
|
||||
"""Compute a power of a fraction."""
|
||||
if parent(other) == ZZ:
|
||||
@@ -164,9 +159,6 @@ class constraints:
|
||||
def negate(self):
|
||||
return constraints(zero=self.nonzero, nonzero=self.zero)
|
||||
|
||||
def map(self, fun):
|
||||
return constraints(zero={fun(k): v for k, v in self.zero.items()}, nonzero={fun(k): v for k, v in self.nonzero.items()})
|
||||
|
||||
def __add__(self, other):
|
||||
zero = self.zero.copy()
|
||||
zero.update(other.zero)
|
||||
@@ -180,34 +172,10 @@ class constraints:
|
||||
def __repr__(self):
|
||||
return "%s" % self
|
||||
|
||||
def normalize_factor(p):
|
||||
"""Normalizes the sign of primitive polynomials (as returned by factor())
|
||||
|
||||
This function ensures that the polynomial has a positive leading coefficient.
|
||||
|
||||
This is necessary because recent sage versions (starting with v9.3 or v9.4,
|
||||
we don't know) are inconsistent about the placement of the minus sign in
|
||||
polynomial factorizations:
|
||||
```
|
||||
sage: R.<ax,bx,ay,by,Az,Bz,Ai,Bi> = PolynomialRing(QQ,8,order='invlex')
|
||||
sage: R((-2 * (bx - ax)) ^ 1).factor()
|
||||
(-2) * (bx - ax)
|
||||
sage: R((-2 * (bx - ax)) ^ 2).factor()
|
||||
(4) * (-bx + ax)^2
|
||||
sage: R((-2 * (bx - ax)) ^ 3).factor()
|
||||
(8) * (-bx + ax)^3
|
||||
```
|
||||
"""
|
||||
# Assert p is not 0 and that its non-zero coefficients are coprime.
|
||||
# (We could just work with the primitive part p/p.content() but we want to be
|
||||
# aware if factor() does not return a primitive part in future sage versions.)
|
||||
assert p.content() == 1
|
||||
# Ensure that the first non-zero coefficient is positive.
|
||||
return p if p.lc() > 0 else -p
|
||||
|
||||
def conflicts(R, con):
|
||||
"""Check whether any of the passed non-zero assumptions is implied by the zero assumptions"""
|
||||
zero = R.ideal(list(map(numerator, con.zero)))
|
||||
zero = R.ideal(map(numerator, con.zero))
|
||||
if 1 in zero:
|
||||
return True
|
||||
# First a cheap check whether any of the individual nonzero terms conflict on
|
||||
@@ -227,20 +195,20 @@ def conflicts(R, con):
|
||||
|
||||
def get_nonzero_set(R, assume):
|
||||
"""Calculate a simple set of nonzero expressions"""
|
||||
zero = R.ideal(list(map(numerator, assume.zero)))
|
||||
zero = R.ideal(map(numerator, assume.zero))
|
||||
nonzero = set()
|
||||
for nz in map(numerator, assume.nonzero):
|
||||
for (f,n) in nz.factor():
|
||||
nonzero.add(normalize_factor(f))
|
||||
nonzero.add(f)
|
||||
rnz = zero.reduce(nz)
|
||||
for (f,n) in rnz.factor():
|
||||
nonzero.add(normalize_factor(f))
|
||||
nonzero.add(f)
|
||||
return nonzero
|
||||
|
||||
|
||||
def prove_nonzero(R, exprs, assume):
|
||||
"""Check whether an expression is provably nonzero, given assumptions"""
|
||||
zero = R.ideal(list(map(numerator, assume.zero)))
|
||||
zero = R.ideal(map(numerator, assume.zero))
|
||||
nonzero = get_nonzero_set(R, assume)
|
||||
expl = set()
|
||||
ok = True
|
||||
@@ -249,27 +217,27 @@ def prove_nonzero(R, exprs, assume):
|
||||
return (False, [exprs[expr]])
|
||||
allexprs = reduce(lambda a,b: numerator(a)*numerator(b), exprs, 1)
|
||||
for (f, n) in allexprs.factor():
|
||||
if normalize_factor(f) not in nonzero:
|
||||
if f not in nonzero:
|
||||
ok = False
|
||||
if ok:
|
||||
return (True, None)
|
||||
ok = True
|
||||
for (f, n) in zero.reduce(allexprs).factor():
|
||||
if normalize_factor(f) not in nonzero:
|
||||
for (f, n) in zero.reduce(numerator(allexprs)).factor():
|
||||
if f not in nonzero:
|
||||
ok = False
|
||||
if ok:
|
||||
return (True, None)
|
||||
ok = True
|
||||
for expr in exprs:
|
||||
for (f,n) in numerator(expr).factor():
|
||||
if normalize_factor(f) not in nonzero:
|
||||
if f not in nonzero:
|
||||
ok = False
|
||||
if ok:
|
||||
return (True, None)
|
||||
ok = True
|
||||
for expr in exprs:
|
||||
for (f,n) in zero.reduce(numerator(expr)).factor():
|
||||
if normalize_factor(f) not in nonzero:
|
||||
if f not in nonzero:
|
||||
expl.add(exprs[expr])
|
||||
if expl:
|
||||
return (False, list(expl))
|
||||
@@ -281,8 +249,8 @@ def prove_zero(R, exprs, assume):
|
||||
"""Check whether all of the passed expressions are provably zero, given assumptions"""
|
||||
r, e = prove_nonzero(R, dict(map(lambda x: (fastfrac(R, x.bot, 1), exprs[x]), exprs)), assume)
|
||||
if not r:
|
||||
return (False, list(map(lambda x: "Possibly zero denominator: %s" % x, e)))
|
||||
zero = R.ideal(list(map(numerator, assume.zero)))
|
||||
return (False, map(lambda x: "Possibly zero denominator: %s" % x, e))
|
||||
zero = R.ideal(map(numerator, assume.zero))
|
||||
nonzero = prod(x for x in assume.nonzero)
|
||||
expl = []
|
||||
for expr in exprs:
|
||||
@@ -297,8 +265,8 @@ def describe_extra(R, assume, assumeExtra):
|
||||
"""Describe what assumptions are added, given existing assumptions"""
|
||||
zerox = assume.zero.copy()
|
||||
zerox.update(assumeExtra.zero)
|
||||
zero = R.ideal(list(map(numerator, assume.zero)))
|
||||
zeroextra = R.ideal(list(map(numerator, zerox)))
|
||||
zero = R.ideal(map(numerator, assume.zero))
|
||||
zeroextra = R.ideal(map(numerator, zerox))
|
||||
nonzero = get_nonzero_set(R, assume)
|
||||
ret = set()
|
||||
# Iterate over the extra zero expressions
|
||||
@@ -306,8 +274,8 @@ def describe_extra(R, assume, assumeExtra):
|
||||
if base not in zero:
|
||||
add = []
|
||||
for (f, n) in numerator(base).factor():
|
||||
if normalize_factor(f) not in nonzero:
|
||||
add += ["%s" % normalize_factor(f)]
|
||||
if f not in nonzero:
|
||||
add += ["%s" % f]
|
||||
if add:
|
||||
ret.add((" * ".join(add)) + " = 0 [%s]" % assumeExtra.zero[base])
|
||||
# Iterate over the extra nonzero expressions
|
||||
@@ -315,8 +283,8 @@ def describe_extra(R, assume, assumeExtra):
|
||||
nzr = zeroextra.reduce(numerator(nz))
|
||||
if nzr not in zeroextra:
|
||||
for (f,n) in nzr.factor():
|
||||
if normalize_factor(zeroextra.reduce(f)) not in nonzero:
|
||||
ret.add("%s != 0" % normalize_factor(zeroextra.reduce(f)))
|
||||
if zeroextra.reduce(f) not in nonzero:
|
||||
ret.add("%s != 0" % zeroextra.reduce(f))
|
||||
return ", ".join(x for x in ret)
|
||||
|
||||
|
||||
@@ -326,21 +294,22 @@ def check_symbolic(R, assumeLaw, assumeAssert, assumeBranch, require):
|
||||
|
||||
if conflicts(R, assume):
|
||||
# This formula does not apply
|
||||
return (True, None)
|
||||
return None
|
||||
|
||||
describe = describe_extra(R, assumeLaw + assumeBranch, assumeAssert)
|
||||
if describe != "":
|
||||
describe = " (assuming " + describe + ")"
|
||||
|
||||
ok, msg = prove_zero(R, require.zero, assume)
|
||||
if not ok:
|
||||
return (False, "FAIL, %s fails%s" % (str(msg), describe))
|
||||
return "FAIL, %s fails (assuming %s)" % (str(msg), describe)
|
||||
|
||||
res, expl = prove_nonzero(R, require.nonzero, assume)
|
||||
if not res:
|
||||
return (False, "FAIL, %s fails%s" % (str(expl), describe))
|
||||
return "FAIL, %s fails (assuming %s)" % (str(expl), describe)
|
||||
|
||||
return (True, "OK%s" % describe)
|
||||
if describe != "":
|
||||
return "OK (assuming %s)" % describe
|
||||
else:
|
||||
return "OK"
|
||||
|
||||
|
||||
def concrete_verify(c):
|
||||
|
||||
@@ -8,20 +8,25 @@ load("weierstrass_prover.sage")
|
||||
def formula_secp256k1_gej_double_var(a):
|
||||
"""libsecp256k1's secp256k1_gej_double_var, used by various addition functions"""
|
||||
rz = a.Z * a.Y
|
||||
s = a.Y^2
|
||||
l = a.X^2
|
||||
l = l * 3
|
||||
l = l / 2
|
||||
t = -s
|
||||
t = t * a.X
|
||||
rx = l^2
|
||||
rx = rx + t
|
||||
rx = rx + t
|
||||
s = s^2
|
||||
t = t + rx
|
||||
ry = t * l
|
||||
ry = ry + s
|
||||
ry = -ry
|
||||
rz = rz * 2
|
||||
t1 = a.X^2
|
||||
t1 = t1 * 3
|
||||
t2 = t1^2
|
||||
t3 = a.Y^2
|
||||
t3 = t3 * 2
|
||||
t4 = t3^2
|
||||
t4 = t4 * 2
|
||||
t3 = t3 * a.X
|
||||
rx = t3
|
||||
rx = rx * 4
|
||||
rx = -rx
|
||||
rx = rx + t2
|
||||
t2 = -t2
|
||||
t3 = t3 * 6
|
||||
t3 = t3 + t2
|
||||
ry = t1 * t3
|
||||
t2 = -t4
|
||||
ry = ry + t2
|
||||
return jacobianpoint(rx, ry, rz)
|
||||
|
||||
def formula_secp256k1_gej_add_var(branch, a, b):
|
||||
@@ -40,26 +45,29 @@ def formula_secp256k1_gej_add_var(branch, a, b):
|
||||
s2 = s2 * a.Z
|
||||
h = -u1
|
||||
h = h + u2
|
||||
i = -s2
|
||||
i = i + s1
|
||||
i = -s1
|
||||
i = i + s2
|
||||
if branch == 2:
|
||||
r = formula_secp256k1_gej_double_var(a)
|
||||
return (constraints(), constraints(zero={h : 'h=0', i : 'i=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}), r)
|
||||
if branch == 3:
|
||||
return (constraints(), constraints(zero={h : 'h=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={i : 'i!=0'}), point_at_infinity())
|
||||
t = h * b.Z
|
||||
rz = a.Z * t
|
||||
i2 = i^2
|
||||
h2 = h^2
|
||||
h2 = -h2
|
||||
h3 = h2 * h
|
||||
h = h * b.Z
|
||||
rz = a.Z * h
|
||||
t = u1 * h2
|
||||
rx = i^2
|
||||
rx = t
|
||||
rx = rx * 2
|
||||
rx = rx + h3
|
||||
rx = rx + t
|
||||
rx = rx + t
|
||||
t = t + rx
|
||||
ry = t * i
|
||||
rx = -rx
|
||||
rx = rx + i2
|
||||
ry = -rx
|
||||
ry = ry + t
|
||||
ry = ry * i
|
||||
h3 = h3 * s1
|
||||
h3 = -h3
|
||||
ry = ry + h3
|
||||
return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz))
|
||||
|
||||
@@ -77,25 +85,28 @@ def formula_secp256k1_gej_add_ge_var(branch, a, b):
|
||||
s2 = s2 * a.Z
|
||||
h = -u1
|
||||
h = h + u2
|
||||
i = -s2
|
||||
i = i + s1
|
||||
i = -s1
|
||||
i = i + s2
|
||||
if (branch == 2):
|
||||
r = formula_secp256k1_gej_double_var(a)
|
||||
return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r)
|
||||
if (branch == 3):
|
||||
return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity())
|
||||
rz = a.Z * h
|
||||
i2 = i^2
|
||||
h2 = h^2
|
||||
h2 = -h2
|
||||
h3 = h2 * h
|
||||
h3 = h * h2
|
||||
rz = a.Z * h
|
||||
t = u1 * h2
|
||||
rx = i^2
|
||||
rx = t
|
||||
rx = rx * 2
|
||||
rx = rx + h3
|
||||
rx = rx + t
|
||||
rx = rx + t
|
||||
t = t + rx
|
||||
ry = t * i
|
||||
rx = -rx
|
||||
rx = rx + i2
|
||||
ry = -rx
|
||||
ry = ry + t
|
||||
ry = ry * i
|
||||
h3 = h3 * s1
|
||||
h3 = -h3
|
||||
ry = ry + h3
|
||||
return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz))
|
||||
|
||||
@@ -103,15 +114,14 @@ def formula_secp256k1_gej_add_zinv_var(branch, a, b):
|
||||
"""libsecp256k1's secp256k1_gej_add_zinv_var"""
|
||||
bzinv = b.Z^(-1)
|
||||
if branch == 0:
|
||||
rinf = b.Infinity
|
||||
return (constraints(), constraints(nonzero={b.Infinity : 'b_infinite'}), a)
|
||||
if branch == 1:
|
||||
bzinv2 = bzinv^2
|
||||
bzinv3 = bzinv2 * bzinv
|
||||
rx = b.X * bzinv2
|
||||
ry = b.Y * bzinv3
|
||||
rz = 1
|
||||
return (constraints(), constraints(nonzero={a.Infinity : 'a_infinite'}), jacobianpoint(rx, ry, rz, rinf))
|
||||
if branch == 1:
|
||||
return (constraints(), constraints(zero={a.Infinity : 'a_finite'}, nonzero={b.Infinity : 'b_infinite'}), a)
|
||||
return (constraints(), constraints(zero={b.Infinity : 'b_finite'}, nonzero={a.Infinity : 'a_infinite'}), jacobianpoint(rx, ry, rz))
|
||||
azz = a.Z * bzinv
|
||||
z12 = azz^2
|
||||
u1 = a.X
|
||||
@@ -121,25 +131,29 @@ def formula_secp256k1_gej_add_zinv_var(branch, a, b):
|
||||
s2 = s2 * azz
|
||||
h = -u1
|
||||
h = h + u2
|
||||
i = -s2
|
||||
i = i + s1
|
||||
i = -s1
|
||||
i = i + s2
|
||||
if branch == 2:
|
||||
r = formula_secp256k1_gej_double_var(a)
|
||||
return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r)
|
||||
if branch == 3:
|
||||
return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity())
|
||||
rz = a.Z * h
|
||||
i2 = i^2
|
||||
h2 = h^2
|
||||
h2 = -h2
|
||||
h3 = h2 * h
|
||||
h3 = h * h2
|
||||
rz = a.Z
|
||||
rz = rz * h
|
||||
t = u1 * h2
|
||||
rx = i^2
|
||||
rx = t
|
||||
rx = rx * 2
|
||||
rx = rx + h3
|
||||
rx = rx + t
|
||||
rx = rx + t
|
||||
t = t + rx
|
||||
ry = t * i
|
||||
rx = -rx
|
||||
rx = rx + i2
|
||||
ry = -rx
|
||||
ry = ry + t
|
||||
ry = ry * i
|
||||
h3 = h3 * s1
|
||||
h3 = -h3
|
||||
ry = ry + h3
|
||||
return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz))
|
||||
|
||||
@@ -148,7 +162,7 @@ def formula_secp256k1_gej_add_ge(branch, a, b):
|
||||
zeroes = {}
|
||||
nonzeroes = {}
|
||||
a_infinity = False
|
||||
if (branch & 2) != 0:
|
||||
if (branch & 4) != 0:
|
||||
nonzeroes.update({a.Infinity : 'a_infinite'})
|
||||
a_infinity = True
|
||||
else:
|
||||
@@ -167,11 +181,15 @@ def formula_secp256k1_gej_add_ge(branch, a, b):
|
||||
m_alt = -u2
|
||||
tt = u1 * m_alt
|
||||
rr = rr + tt
|
||||
degenerate = (branch & 1) != 0
|
||||
if degenerate:
|
||||
degenerate = (branch & 3) == 3
|
||||
if (branch & 1) != 0:
|
||||
zeroes.update({m : 'm_zero'})
|
||||
else:
|
||||
nonzeroes.update({m : 'm_nonzero'})
|
||||
if (branch & 2) != 0:
|
||||
zeroes.update({rr : 'rr_zero'})
|
||||
else:
|
||||
nonzeroes.update({rr : 'rr_nonzero'})
|
||||
rr_alt = s1
|
||||
rr_alt = rr_alt * 2
|
||||
m_alt = m_alt + u1
|
||||
@@ -179,13 +197,21 @@ def formula_secp256k1_gej_add_ge(branch, a, b):
|
||||
rr_alt = rr
|
||||
m_alt = m
|
||||
n = m_alt^2
|
||||
q = -t
|
||||
q = q * n
|
||||
q = n * t
|
||||
n = n^2
|
||||
if degenerate:
|
||||
n = m
|
||||
t = rr_alt^2
|
||||
rz = a.Z * m_alt
|
||||
infinity = False
|
||||
if (branch & 8) != 0:
|
||||
if not a_infinity:
|
||||
infinity = True
|
||||
zeroes.update({rz : 'r.z=0'})
|
||||
else:
|
||||
nonzeroes.update({rz : 'r.z!=0'})
|
||||
rz = rz * 2
|
||||
q = -q
|
||||
t = t + q
|
||||
rx = t
|
||||
t = t * 2
|
||||
@@ -193,16 +219,14 @@ def formula_secp256k1_gej_add_ge(branch, a, b):
|
||||
t = t * rr_alt
|
||||
t = t + n
|
||||
ry = -t
|
||||
ry = ry / 2
|
||||
rx = rx * 4
|
||||
ry = ry * 4
|
||||
if a_infinity:
|
||||
rx = b.X
|
||||
ry = b.Y
|
||||
rz = 1
|
||||
if (branch & 4) != 0:
|
||||
zeroes.update({rz : 'r.z = 0'})
|
||||
if infinity:
|
||||
return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), point_at_infinity())
|
||||
else:
|
||||
nonzeroes.update({rz : 'r.z != 0'})
|
||||
return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zeroes, nonzero=nonzeroes), jacobianpoint(rx, ry, rz))
|
||||
|
||||
def formula_secp256k1_gej_add_ge_old(branch, a, b):
|
||||
@@ -268,18 +292,15 @@ def formula_secp256k1_gej_add_ge_old(branch, a, b):
|
||||
return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zero, nonzero=nonzero), jacobianpoint(rx, ry, rz))
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = True
|
||||
success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var)
|
||||
success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var)
|
||||
success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var)
|
||||
success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 8, formula_secp256k1_gej_add_ge)
|
||||
success = success & (not check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old))
|
||||
check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var)
|
||||
check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var)
|
||||
check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var)
|
||||
check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge)
|
||||
check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old)
|
||||
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == "--exhaustive":
|
||||
success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43)
|
||||
success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43)
|
||||
success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43)
|
||||
success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 8, formula_secp256k1_gej_add_ge, 43)
|
||||
success = success & (not check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43))
|
||||
|
||||
sys.exit(int(not success))
|
||||
check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43)
|
||||
check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43)
|
||||
check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43)
|
||||
check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43)
|
||||
check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43)
|
||||
@@ -1,39 +0,0 @@
|
||||
"""Prime order of finite field underlying secp256k1 (2^256 - 2^32 - 977)"""
|
||||
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
||||
|
||||
"""Finite field underlying secp256k1"""
|
||||
F = FiniteField(P)
|
||||
|
||||
"""Elliptic curve secp256k1: y^2 = x^3 + 7"""
|
||||
C = EllipticCurve([F(0), F(7)])
|
||||
|
||||
"""Base point of secp256k1"""
|
||||
G = C.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
|
||||
if int(G[1]) & 1:
|
||||
# G.y is even
|
||||
G = -G
|
||||
|
||||
"""Prime order of secp256k1"""
|
||||
N = C.order()
|
||||
|
||||
"""Finite field of scalars of secp256k1"""
|
||||
Z = FiniteField(N)
|
||||
|
||||
""" Beta value of secp256k1 non-trivial endomorphism: lambda * (x, y) = (beta * x, y)"""
|
||||
BETA = F(2)^((P-1)/3)
|
||||
|
||||
""" Lambda value of secp256k1 non-trivial endomorphism: lambda * (x, y) = (beta * x, y)"""
|
||||
LAMBDA = Z(3)^((N-1)/3)
|
||||
|
||||
assert is_prime(P)
|
||||
assert is_prime(N)
|
||||
|
||||
assert BETA != F(1)
|
||||
assert BETA^3 == F(1)
|
||||
assert BETA^2 + BETA + 1 == 0
|
||||
|
||||
assert LAMBDA != Z(1)
|
||||
assert LAMBDA^3 == Z(1)
|
||||
assert LAMBDA^2 + LAMBDA + 1 == 0
|
||||
|
||||
assert Integer(LAMBDA)*G == C(BETA*G[0], G[1])
|
||||
@@ -175,25 +175,24 @@ laws_jacobian_weierstrass = {
|
||||
def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p):
|
||||
"""Verify an implementation of addition of Jacobian points on a Weierstrass curve, by executing and validating the result for every possible addition in a prime field"""
|
||||
F = Integers(p)
|
||||
print("Formula %s on Z%i:" % (name, p))
|
||||
print "Formula %s on Z%i:" % (name, p)
|
||||
points = []
|
||||
for x in range(0, p):
|
||||
for y in range(0, p):
|
||||
for x in xrange(0, p):
|
||||
for y in xrange(0, p):
|
||||
point = affinepoint(F(x), F(y))
|
||||
r, e = concrete_verify(on_weierstrass_curve(A, B, point))
|
||||
if r:
|
||||
points.append(point)
|
||||
|
||||
ret = True
|
||||
for za in range(1, p):
|
||||
for zb in range(1, p):
|
||||
for za in xrange(1, p):
|
||||
for zb in xrange(1, p):
|
||||
for pa in points:
|
||||
for pb in points:
|
||||
for ia in range(2):
|
||||
for ib in range(2):
|
||||
for ia in xrange(2):
|
||||
for ib in xrange(2):
|
||||
pA = jacobianpoint(pa.x * F(za)^2, pa.y * F(za)^3, F(za), ia)
|
||||
pB = jacobianpoint(pb.x * F(zb)^2, pb.y * F(zb)^3, F(zb), ib)
|
||||
for branch in range(0, branches):
|
||||
for branch in xrange(0, branches):
|
||||
assumeAssert, assumeBranch, pC = formula(branch, pA, pB)
|
||||
pC.X = F(pC.X)
|
||||
pC.Y = F(pC.Y)
|
||||
@@ -207,16 +206,13 @@ def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p):
|
||||
r, e = concrete_verify(assumeLaw)
|
||||
if r:
|
||||
if match:
|
||||
print(" multiple branches for (%s,%s,%s,%s) + (%s,%s,%s,%s)" % (pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity))
|
||||
print " multiple branches for (%s,%s,%s,%s) + (%s,%s,%s,%s)" % (pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity)
|
||||
else:
|
||||
match = True
|
||||
r, e = concrete_verify(require)
|
||||
if not r:
|
||||
ret = False
|
||||
print(" failure in branch %i for (%s,%s,%s,%s) + (%s,%s,%s,%s) = (%s,%s,%s,%s): %s" % (branch, pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity, pC.X, pC.Y, pC.Z, pC.Infinity, e))
|
||||
|
||||
print()
|
||||
return ret
|
||||
print " failure in branch %i for (%s,%s,%s,%s) + (%s,%s,%s,%s) = (%s,%s,%s,%s): %s" % (branch, pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity, pC.X, pC.Y, pC.Z, pC.Infinity, e)
|
||||
print
|
||||
|
||||
|
||||
def check_symbolic_function(R, assumeAssert, assumeBranch, f, A, B, pa, pb, pA, pB, pC):
|
||||
@@ -246,30 +242,23 @@ def check_symbolic_jacobian_weierstrass(name, A, B, branches, formula):
|
||||
for key in laws_jacobian_weierstrass:
|
||||
res[key] = []
|
||||
|
||||
print("Formula " + name + ":")
|
||||
print ("Formula " + name + ":")
|
||||
count = 0
|
||||
ret = True
|
||||
for branch in range(branches):
|
||||
for branch in xrange(branches):
|
||||
assumeFormula, assumeBranch, pC = formula(branch, pA, pB)
|
||||
assumeBranch = assumeBranch.map(lift)
|
||||
assumeFormula = assumeFormula.map(lift)
|
||||
pC.X = lift(pC.X)
|
||||
pC.Y = lift(pC.Y)
|
||||
pC.Z = lift(pC.Z)
|
||||
pC.Infinity = lift(pC.Infinity)
|
||||
|
||||
for key in laws_jacobian_weierstrass:
|
||||
success, msg = check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC)
|
||||
if not success:
|
||||
ret = False
|
||||
res[key].append((msg, branch))
|
||||
res[key].append((check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC), branch))
|
||||
|
||||
for key in res:
|
||||
print(" %s:" % key)
|
||||
print " %s:" % key
|
||||
val = res[key]
|
||||
for x in val:
|
||||
if x[0] is not None:
|
||||
print(" branch %i: %s" % (x[1], x[0]))
|
||||
print " branch %i: %s" % (x[1], x[0])
|
||||
|
||||
print()
|
||||
return ret
|
||||
print
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
add_library(secp256k1)
|
||||
|
||||
set_property(TARGET secp256k1 PROPERTY PUBLIC_HEADER
|
||||
${PROJECT_SOURCE_DIR}/include/secp256k1.h
|
||||
${PROJECT_SOURCE_DIR}/include/secp256k1_preallocated.h
|
||||
)
|
||||
|
||||
# Processing must be done in a topological sorting of the dependency graph
|
||||
# (dependent module first).
|
||||
if(SECP256K1_ENABLE_MODULE_SCHNORRSIG_HALFAGG)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the schnorrsig_halfagg module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON)
|
||||
add_compile_definitions(ENABLE_MODULE_SCHNORRSIG_HALFAGG=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig_halfagg.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_BPPP)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_GENERATOR AND NOT SECP256K1_ENABLE_MODULE_GENERATOR)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the generator module explicitly, but it is required by the bppp module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_GENERATOR ON)
|
||||
add_compile_definitions(ENABLE_MODULE_BPPP=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_bppp.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ECDSA_S2C)
|
||||
add_compile_definitions(ENABLE_MODULE_ECDSA_S2C=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ecdsa_s2c.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ECDSA_ADAPTOR)
|
||||
add_compile_definitions(ENABLE_MODULE_ECDSA_ADAPTOR=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ecdsa_adaptor.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_WHITELIST)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_RANGEPROOF AND NOT SECP256K1_ENABLE_MODULE_RANGEPROOF)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the rangeproof module explicitly, but it is required by the whitelist module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_RANGEPROOF ON)
|
||||
add_compile_definitions(ENABLE_MODULE_WHITELIST=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_whitelist.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_SURJECTIONPROOF)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_RANGEPROOF AND NOT SECP256K1_ENABLE_MODULE_RANGEPROOF)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the rangeproof module explicitly, but it is required by the surjectionproof module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_RANGEPROOF ON)
|
||||
add_compile_definitions(ENABLE_MODULE_SURJECTIONPROOF=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_surjectionproof.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_RANGEPROOF)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_GENERATOR AND NOT SECP256K1_ENABLE_MODULE_GENERATOR)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the generator module explicitly, but it is required by the rangeproof module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_GENERATOR ON)
|
||||
add_compile_definitions(ENABLE_MODULE_RANGEPROOF=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_rangeproof.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_GENERATOR)
|
||||
add_compile_definitions(ENABLE_MODULE_GENERATOR=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_generator.h)
|
||||
endif()
|
||||
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ELLSWIFT)
|
||||
add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ellswift.h)
|
||||
endif()
|
||||
|
||||
# Like the prefractal block below, this must precede the FROST block: the
|
||||
# set() call here is what forces frost on, and it is only observed by blocks
|
||||
# that run after it.
|
||||
if(SECP256K1_ENABLE_MODULE_FROST_ENROLLMENT)
|
||||
# frost defaults to OFF, so the "DEFINED AND NOT" guard the other blocks use
|
||||
# would reject every frost-enrollment build; enabling frost-enrollment
|
||||
# simply implies frost. See the prefractal block below for the full
|
||||
# reasoning, including why the flag is also lifted into the parent scope.
|
||||
set(SECP256K1_ENABLE_MODULE_FROST ON)
|
||||
set(SECP256K1_ENABLE_MODULE_FROST ON PARENT_SCOPE)
|
||||
add_compile_definitions(ENABLE_MODULE_FROST_ENROLLMENT=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_frost_enrollment.h)
|
||||
endif()
|
||||
|
||||
# Must precede the musig and frost blocks below: the set() calls here are what
|
||||
# force those modules on, and they are only observed by blocks that run after.
|
||||
if(SECP256K1_ENABLE_MODULE_PREFRACTAL)
|
||||
# musig defaults to ON, so the guard every other block in this file uses
|
||||
# reads as "the user turned it off explicitly" and is meaningful here.
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_MUSIG AND NOT SECP256K1_ENABLE_MODULE_MUSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the musig module explicitly, but it is required by the prefractal module.")
|
||||
endif()
|
||||
# frost is the first default-OFF module anything depends on. The same guard
|
||||
# cannot be used: option() always leaves the variable DEFINED, so for a
|
||||
# default-OFF module "DEFINED AND NOT" is true by default and would reject
|
||||
# every prefractal build. There is no way to tell an explicit -D...=OFF from
|
||||
# the default once both are in the cache, so enabling prefractal simply
|
||||
# implies frost.
|
||||
set(SECP256K1_ENABLE_MODULE_FROST ON)
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG ON)
|
||||
# Also lift them into the parent scope so the top-level configuration
|
||||
# summary, which runs after add_subdirectory(src), reports what was actually
|
||||
# built. Without this a prefractal-only configure prints "frost OFF" while
|
||||
# compiling frost in.
|
||||
set(SECP256K1_ENABLE_MODULE_FROST ON PARENT_SCOPE)
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG ON PARENT_SCOPE)
|
||||
add_compile_definitions(ENABLE_MODULE_PREFRACTAL=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_prefractal.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ICEBERG)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_MUSIG AND NOT SECP256K1_ENABLE_MODULE_MUSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the musig module explicitly, but it is required by the iceberg module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_MUSIG ON)
|
||||
add_compile_definitions(ENABLE_MODULE_ICEBERG=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_iceberg.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_MUSIG)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the musig module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON)
|
||||
add_compile_definitions(ENABLE_MODULE_MUSIG=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_musig.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_FROST)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the frost module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON)
|
||||
add_compile_definitions(ENABLE_MODULE_FROST=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_frost.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_CHILLDKG)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_SCHNORRSIG AND NOT SECP256K1_ENABLE_MODULE_SCHNORRSIG)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the schnorrsig module explicitly, but it is required by the chilldkg module.")
|
||||
endif()
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_ECDH AND NOT SECP256K1_ENABLE_MODULE_ECDH)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the ecdh module explicitly, but it is required by the chilldkg module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_SCHNORRSIG ON)
|
||||
set(SECP256K1_ENABLE_MODULE_ECDH ON)
|
||||
add_compile_definitions(ENABLE_MODULE_CHILLDKG=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_chilldkg.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_SCHNORRSIG)
|
||||
if(DEFINED SECP256K1_ENABLE_MODULE_EXTRAKEYS AND NOT SECP256K1_ENABLE_MODULE_EXTRAKEYS)
|
||||
message(FATAL_ERROR "Module dependency error: You have disabled the extrakeys module explicitly, but it is required by the schnorrsig module.")
|
||||
endif()
|
||||
set(SECP256K1_ENABLE_MODULE_EXTRAKEYS ON)
|
||||
add_compile_definitions(ENABLE_MODULE_SCHNORRSIG=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_schnorrsig.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_EXTRAKEYS)
|
||||
add_compile_definitions(ENABLE_MODULE_EXTRAKEYS=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_extrakeys.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_RECOVERY)
|
||||
add_compile_definitions(ENABLE_MODULE_RECOVERY=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_recovery.h)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_ENABLE_MODULE_ECDH)
|
||||
add_compile_definitions(ENABLE_MODULE_ECDH=1)
|
||||
set_property(TARGET secp256k1 APPEND PROPERTY PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/include/secp256k1_ecdh.h)
|
||||
endif()
|
||||
|
||||
add_library(secp256k1_precomputed OBJECT EXCLUDE_FROM_ALL
|
||||
precomputed_ecmult.c
|
||||
precomputed_ecmult_gen.c
|
||||
)
|
||||
|
||||
# Add objects explicitly rather than linking to the object libs to keep them
|
||||
# from being exported.
|
||||
target_sources(secp256k1 PRIVATE secp256k1.c $<TARGET_OBJECTS:secp256k1_precomputed>)
|
||||
|
||||
if(NOT SECP256K1_ENABLE_API_VISIBILITY_ATTRIBUTES)
|
||||
target_compile_definitions(secp256k1 PRIVATE SECP256K1_NO_API_VISIBILITY_ATTRIBUTES)
|
||||
endif()
|
||||
|
||||
# Create a helper lib that parent projects can use to link secp256k1 into a
|
||||
# static lib.
|
||||
add_library(secp256k1_objs INTERFACE)
|
||||
target_sources(secp256k1_objs INTERFACE $<TARGET_OBJECTS:secp256k1> $<TARGET_OBJECTS:secp256k1_precomputed>)
|
||||
|
||||
add_library(secp256k1_asm INTERFACE)
|
||||
if(SECP256K1_ASM STREQUAL "arm32")
|
||||
add_library(secp256k1_asm_arm OBJECT EXCLUDE_FROM_ALL)
|
||||
target_sources(secp256k1_asm_arm PUBLIC
|
||||
asm/field_10x26_arm.s
|
||||
)
|
||||
target_sources(secp256k1 PRIVATE $<TARGET_OBJECTS:secp256k1_asm_arm>)
|
||||
target_sources(secp256k1_objs INTERFACE $<TARGET_OBJECTS:secp256k1_asm_arm>)
|
||||
target_link_libraries(secp256k1_asm INTERFACE secp256k1_asm_arm)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Define our export symbol only for shared libs.
|
||||
set_target_properties(secp256k1 PROPERTIES DEFINE_SYMBOL SECP256K1_DLL_EXPORT)
|
||||
target_compile_definitions(secp256k1 INTERFACE $<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:SECP256K1_STATIC>)
|
||||
endif()
|
||||
|
||||
# Object libs don't know if they're being built for a shared or static lib.
|
||||
# Grab the PIC property from secp256k1 which knows.
|
||||
get_target_property(use_pic secp256k1 POSITION_INDEPENDENT_CODE)
|
||||
set_target_properties(secp256k1_precomputed PROPERTIES POSITION_INDEPENDENT_CODE ${use_pic})
|
||||
|
||||
# Add the include path for parent projects so that they don't have to manually add it.
|
||||
target_include_directories(secp256k1 INTERFACE
|
||||
$<BUILD_INTERFACE:$<$<NOT:$<BOOL:${PROJECT_IS_TOP_LEVEL}>>:${PROJECT_SOURCE_DIR}/include>>
|
||||
)
|
||||
set_target_properties(secp256k1_objs PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "$<TARGET_PROPERTY:secp256k1,INTERFACE_COMPILE_DEFINITIONS>"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:secp256k1,INTERFACE_INCLUDE_DIRECTORIES>"
|
||||
)
|
||||
|
||||
include(SetLibtoolAbiVersion)
|
||||
set_libtool_abi_version(secp256k1
|
||||
${${PROJECT_NAME}_LIB_VERSION_CURRENT}
|
||||
${${PROJECT_NAME}_LIB_VERSION_REVISION}
|
||||
${${PROJECT_NAME}_LIB_VERSION_AGE}
|
||||
)
|
||||
|
||||
if(SECP256K1_BUILD_BENCHMARK)
|
||||
add_executable(bench bench.c)
|
||||
target_link_libraries(bench secp256k1)
|
||||
add_executable(bench_internal bench_internal.c)
|
||||
target_link_libraries(bench_internal secp256k1_precomputed secp256k1_asm)
|
||||
add_executable(bench_ecmult bench_ecmult.c)
|
||||
target_link_libraries(bench_ecmult secp256k1_precomputed secp256k1_asm)
|
||||
if(SECP256K1_ENABLE_MODULE_ICEBERG)
|
||||
add_executable(bench_iceberg bench_iceberg.c)
|
||||
target_link_libraries(bench_iceberg secp256k1_precomputed secp256k1_asm)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(SECP256K1_BUILD_TESTS)
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(sys/types.h HAVE_SYS_TYPES_H)
|
||||
check_include_file(sys/wait.h HAVE_SYS_WAIT_H)
|
||||
check_include_file(unistd.h HAVE_UNISTD_H)
|
||||
|
||||
set(TEST_DEFINITIONS "")
|
||||
if(HAVE_SYS_TYPES_H AND HAVE_SYS_WAIT_H AND HAVE_UNISTD_H)
|
||||
list(APPEND TEST_DEFINITIONS SUPPORTS_CONCURRENCY=1)
|
||||
endif()
|
||||
|
||||
function(add_executable_and_tests exe_name verify_definition)
|
||||
add_executable(${exe_name} tests.c)
|
||||
target_link_libraries(${exe_name} secp256k1_precomputed secp256k1_asm)
|
||||
target_compile_definitions(${exe_name} PRIVATE ${verify_definition} ${TEST_DEFINITIONS})
|
||||
include(DiscoverTests)
|
||||
discover_tests(${exe_name}
|
||||
DISCOVERY_ARGS "--list_tests"
|
||||
DISCOVERY_MATCH "^\\t\\\\[ *[0-9]+\\\\] ([^ ].*)$"
|
||||
TEST_NAME_REPLACEMENT "secp256k1.${exe_name}.\\\\1"
|
||||
TEST_ARGS_REPLACEMENT "--target=\\\\1 --log=1"
|
||||
PROPERTIES
|
||||
LABELS "secp256k1_${exe_name}"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
add_executable_and_tests(noverify_tests "")
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Coverage")
|
||||
add_executable_and_tests(tests VERIFY)
|
||||
endif()
|
||||
unset(TEST_DEFINITIONS)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_BUILD_EXHAUSTIVE_TESTS)
|
||||
# Note: do not include secp256k1_precomputed in exhaustive_tests (it uses runtime-generated tables).
|
||||
add_executable(exhaustive_tests tests_exhaustive.c)
|
||||
target_link_libraries(exhaustive_tests secp256k1_asm)
|
||||
target_compile_definitions(exhaustive_tests PRIVATE $<$<NOT:$<CONFIG:Coverage>>:VERIFY>)
|
||||
add_test(NAME secp256k1.exhaustive_tests COMMAND exhaustive_tests)
|
||||
set_tests_properties(secp256k1.exhaustive_tests PROPERTIES
|
||||
LABELS secp256k1_exhaustive
|
||||
)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_BUILD_CTIME_TESTS)
|
||||
add_executable(ctime_tests ctime_tests.c)
|
||||
target_link_libraries(ctime_tests secp256k1)
|
||||
endif()
|
||||
|
||||
if(SECP256K1_INSTALL)
|
||||
include(GNUInstallDirs)
|
||||
target_include_directories(secp256k1 INTERFACE
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
install(TARGETS secp256k1
|
||||
EXPORT ${PROJECT_NAME}-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
|
||||
install(EXPORT ${PROJECT_NAME}-targets
|
||||
FILE ${PROJECT_NAME}-targets.cmake
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
configure_package_config_file(
|
||||
${PROJECT_SOURCE_DIR}/cmake/config.cmake.in
|
||||
${PROJECT_NAME}-config.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
||||
NO_SET_AND_CHECK_MACRO
|
||||
)
|
||||
write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake
|
||||
COMPATIBILITY SameMinorVersion
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
include(GeneratePkgConfigFile)
|
||||
generate_pkg_config_file(${PROJECT_SOURCE_DIR}/libsecp256k1.pc.in)
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
|
||||
)
|
||||
endif()
|
||||
@@ -1,9 +1,9 @@
|
||||
@ vim: set tabstop=8 softtabstop=8 shiftwidth=8 noexpandtab syntax=armasm:
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2014 Wladimir J. van der Laan *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2014 Wladimir J. van der Laan *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
/*
|
||||
ARM implementation of field_10x26 inner loops.
|
||||
|
||||
@@ -29,7 +29,6 @@ Note:
|
||||
.align 2
|
||||
.global secp256k1_fe_mul_inner
|
||||
.type secp256k1_fe_mul_inner, %function
|
||||
.hidden secp256k1_fe_mul_inner
|
||||
@ Arguments:
|
||||
@ r0 r Restrict: can overlap with a, not with b
|
||||
@ r1 a
|
||||
@@ -517,7 +516,6 @@ secp256k1_fe_mul_inner:
|
||||
.align 2
|
||||
.global secp256k1_fe_sqr_inner
|
||||
.type secp256k1_fe_sqr_inner, %function
|
||||
.hidden secp256k1_fe_sqr_inner
|
||||
@ Arguments:
|
||||
@ r0 r Can overlap with a
|
||||
@ r1 a
|
||||
@@ -913,4 +911,3 @@ secp256k1_fe_sqr_inner:
|
||||
ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}
|
||||
.size secp256k1_fe_sqr_inner, .-secp256k1_fe_sqr_inner
|
||||
|
||||
.section .note.GNU-stack,"",%progbits
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2020 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ASSUMPTIONS_H
|
||||
#define SECP256K1_ASSUMPTIONS_H
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "util.h"
|
||||
#if defined(SECP256K1_INT128_NATIVE)
|
||||
#include "int128_native.h"
|
||||
#endif
|
||||
|
||||
/* This library, like most software, relies on a number of compiler implementation defined (but not undefined)
|
||||
behaviours. Although the behaviours we require are essentially universal we test them specifically here to
|
||||
reduce the odds of experiencing an unwelcome surprise.
|
||||
*/
|
||||
|
||||
#if defined(__has_attribute)
|
||||
# if __has_attribute(__unavailable__)
|
||||
__attribute__((__unavailable__("Don't call this function. It only exists because STATIC_ASSERT cannot be used outside a function.")))
|
||||
# endif
|
||||
#endif
|
||||
static void secp256k1_assumption_checker(void) {
|
||||
/* Bytes are 8 bits. */
|
||||
STATIC_ASSERT(CHAR_BIT == 8);
|
||||
|
||||
/* No integer promotion for uint32_t. This ensures that we can multiply uintXX_t values where XX >= 32
|
||||
without signed overflow, which would be undefined behaviour. */
|
||||
STATIC_ASSERT(UINT_MAX <= UINT32_MAX);
|
||||
|
||||
/* Conversions from unsigned to signed outside of the bounds of the signed type are
|
||||
implementation-defined. Verify that they function as reinterpreting the lower
|
||||
bits of the input in two's complement notation. Do this for conversions:
|
||||
- from uint(N)_t to int(N)_t with negative result
|
||||
- from uint(2N)_t to int(N)_t with negative result
|
||||
- from int(2N)_t to int(N)_t with negative result
|
||||
- from int(2N)_t to int(N)_t with positive result */
|
||||
|
||||
/* To int8_t. */
|
||||
STATIC_ASSERT(((int8_t)(uint8_t)0xAB == (int8_t)-(int8_t)0x55));
|
||||
STATIC_ASSERT((int8_t)(uint16_t)0xABCD == (int8_t)-(int8_t)0x33);
|
||||
STATIC_ASSERT((int8_t)(int16_t)(uint16_t)0xCDEF == (int8_t)(uint8_t)0xEF);
|
||||
STATIC_ASSERT((int8_t)(int16_t)(uint16_t)0x9234 == (int8_t)(uint8_t)0x34);
|
||||
|
||||
/* To int16_t. */
|
||||
STATIC_ASSERT((int16_t)(uint16_t)0xBCDE == (int16_t)-(int16_t)0x4322);
|
||||
STATIC_ASSERT((int16_t)(uint32_t)0xA1B2C3D4 == (int16_t)-(int16_t)0x3C2C);
|
||||
STATIC_ASSERT((int16_t)(int32_t)(uint32_t)0xC1D2E3F4 == (int16_t)(uint16_t)0xE3F4);
|
||||
STATIC_ASSERT((int16_t)(int32_t)(uint32_t)0x92345678 == (int16_t)(uint16_t)0x5678);
|
||||
|
||||
/* To int32_t. */
|
||||
STATIC_ASSERT((int32_t)(uint32_t)0xB2C3D4E5 == (int32_t)-(int32_t)0x4D3C2B1B);
|
||||
STATIC_ASSERT((int32_t)(uint64_t)0xA123B456C789D012ULL == (int32_t)-(int32_t)0x38762FEE);
|
||||
STATIC_ASSERT((int32_t)(int64_t)(uint64_t)0xC1D2E3F4A5B6C7D8ULL == (int32_t)(uint32_t)0xA5B6C7D8);
|
||||
STATIC_ASSERT((int32_t)(int64_t)(uint64_t)0xABCDEF0123456789ULL == (int32_t)(uint32_t)0x23456789);
|
||||
|
||||
/* To int64_t. */
|
||||
STATIC_ASSERT((int64_t)(uint64_t)0xB123C456D789E012ULL == (int64_t)-(int64_t)0x4EDC3BA928761FEEULL);
|
||||
#if defined(SECP256K1_INT128_NATIVE)
|
||||
STATIC_ASSERT((int64_t)(((uint128_t)0xA1234567B8901234ULL << 64) + 0xC5678901D2345678ULL) == (int64_t)-(int64_t)0x3A9876FE2DCBA988ULL);
|
||||
STATIC_ASSERT(((int64_t)(int128_t)(((uint128_t)0xB1C2D3E4F5A6B7C8ULL << 64) + 0xD9E0F1A2B3C4D5E6ULL)) == (int64_t)(uint64_t)0xD9E0F1A2B3C4D5E6ULL);
|
||||
STATIC_ASSERT(((int64_t)(int128_t)(((uint128_t)0xABCDEF0123456789ULL << 64) + 0x0123456789ABCDEFULL)) == (int64_t)(uint64_t)0x0123456789ABCDEFULL);
|
||||
|
||||
/* To int128_t. */
|
||||
STATIC_ASSERT((int128_t)(((uint128_t)0xB1234567C8901234ULL << 64) + 0xD5678901E2345678ULL) == (int128_t)(-(int128_t)0x8E1648B3F50E80DCULL * 0x8E1648B3F50E80DDULL + 0x5EA688D5482F9464ULL));
|
||||
#endif
|
||||
|
||||
/* Right shift on negative signed values is implementation defined. Verify that it
|
||||
acts as a right shift in two's complement with sign extension (i.e duplicating
|
||||
the top bit into newly added bits). */
|
||||
STATIC_ASSERT((((int8_t)0xE8) >> 2) == (int8_t)(uint8_t)0xFA);
|
||||
STATIC_ASSERT((((int16_t)0xE9AC) >> 4) == (int16_t)(uint16_t)0xFE9A);
|
||||
STATIC_ASSERT((((int32_t)0x937C918A) >> 9) == (int32_t)(uint32_t)0xFFC9BE48);
|
||||
STATIC_ASSERT((((int64_t)0xA8B72231DF9CF4B9ULL) >> 19) == (int64_t)(uint64_t)0xFFFFF516E4463BF3ULL);
|
||||
#if defined(SECP256K1_INT128_NATIVE)
|
||||
STATIC_ASSERT((((int128_t)(((uint128_t)0xCD833A65684A0DBCULL << 64) + 0xB349312F71EA7637ULL)) >> 39) == (int128_t)(((uint128_t)0xFFFFFFFFFF9B0674ULL << 64) + 0xCAD0941B79669262ULL));
|
||||
#endif
|
||||
|
||||
/* This function is not supposed to be called. */
|
||||
VERIFY_CHECK(0);
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_ASSUMPTIONS_H */
|
||||
37
src/basic-config.h
Normal file
37
src/basic-config.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2013, 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_BASIC_CONFIG_H
|
||||
#define SECP256K1_BASIC_CONFIG_H
|
||||
|
||||
#ifdef USE_BASIC_CONFIG
|
||||
|
||||
#undef USE_ASM_X86_64
|
||||
#undef USE_ECMULT_STATIC_PRECOMPUTATION
|
||||
#undef USE_ENDOMORPHISM
|
||||
#undef USE_EXTERNAL_ASM
|
||||
#undef USE_EXTERNAL_DEFAULT_CALLBACKS
|
||||
#undef USE_FIELD_10X26
|
||||
#undef USE_FIELD_5X52
|
||||
#undef USE_FIELD_INV_BUILTIN
|
||||
#undef USE_FIELD_INV_NUM
|
||||
#undef USE_NUM_GMP
|
||||
#undef USE_NUM_NONE
|
||||
#undef USE_SCALAR_4X64
|
||||
#undef USE_SCALAR_8X32
|
||||
#undef USE_SCALAR_INV_BUILTIN
|
||||
#undef USE_SCALAR_INV_NUM
|
||||
|
||||
#define USE_NUM_NONE 1
|
||||
#define USE_FIELD_INV_BUILTIN 1
|
||||
#define USE_SCALAR_INV_BUILTIN 1
|
||||
#define USE_FIELD_10X26 1
|
||||
#define USE_SCALAR_8X32 1
|
||||
#define ECMULT_WINDOW_SIZE 15
|
||||
|
||||
#endif /* USE_BASIC_CONFIG */
|
||||
|
||||
#endif /* SECP256K1_BASIC_CONFIG_H */
|
||||
288
src/bench.c
288
src/bench.c
@@ -1,288 +0,0 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../include/secp256k1.h"
|
||||
#include "util.h"
|
||||
#include "bench.h"
|
||||
|
||||
static void help(const char *executable_path, int default_iters) {
|
||||
printf("Benchmarks the following algorithms:\n");
|
||||
printf(" - ECDSA signing/verification\n");
|
||||
|
||||
#ifdef ENABLE_MODULE_RECOVERY
|
||||
printf(" - Public key recovery (optional module)\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ECDH
|
||||
printf(" - ECDH key exchange (optional module)\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_SCHNORRSIG
|
||||
printf(" - Schnorr signatures (optional module)\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ELLSWIFT
|
||||
printf(" - ElligatorSwift (optional module)\n");
|
||||
#endif
|
||||
|
||||
printf("\n");
|
||||
printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters);
|
||||
printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n");
|
||||
printf("\n");
|
||||
printf("Usage: %s [args]\n", executable_path);
|
||||
printf("By default, all benchmarks will be run.\n");
|
||||
printf("args:\n");
|
||||
printf(" help : display this help and exit\n");
|
||||
printf(" ecdsa : all ECDSA algorithms--sign, verify, recovery (if enabled)\n");
|
||||
printf(" ecdsa_sign : ECDSA siging algorithm\n");
|
||||
printf(" ecdsa_verify : ECDSA verification algorithm\n");
|
||||
printf(" ec : all EC public key algorithms (keygen)\n");
|
||||
printf(" ec_keygen : EC public key generation\n");
|
||||
|
||||
#ifdef ENABLE_MODULE_RECOVERY
|
||||
printf(" ecdsa_recover : ECDSA public key recovery algorithm\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ECDH
|
||||
printf(" ecdh : ECDH key exchange algorithm\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_SCHNORRSIG
|
||||
printf(" schnorrsig : all Schnorr signature algorithms (sign, verify)\n");
|
||||
printf(" schnorrsig_sign : Schnorr sigining algorithm\n");
|
||||
printf(" schnorrsig_verify : Schnorr verification algorithm\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ELLSWIFT
|
||||
printf(" ellswift : all ElligatorSwift benchmarks (encode, decode, keygen, ecdh)\n");
|
||||
printf(" ellswift_encode : ElligatorSwift encoding\n");
|
||||
printf(" ellswift_decode : ElligatorSwift decoding\n");
|
||||
printf(" ellswift_keygen : ElligatorSwift key generation\n");
|
||||
printf(" ellswift_ecdh : ECDH on ElligatorSwift keys\n");
|
||||
#endif
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
secp256k1_context *ctx;
|
||||
unsigned char msg[32];
|
||||
unsigned char key[32];
|
||||
unsigned char sig[72];
|
||||
size_t siglen;
|
||||
unsigned char pubkey[33];
|
||||
size_t pubkeylen;
|
||||
} bench_data;
|
||||
|
||||
static void bench_verify(void* arg, int iters) {
|
||||
int i;
|
||||
bench_data* data = (bench_data*)arg;
|
||||
|
||||
for (i = 0; i < iters; i++) {
|
||||
secp256k1_pubkey pubkey;
|
||||
secp256k1_ecdsa_signature sig;
|
||||
data->sig[data->siglen - 1] ^= (i & 0xFF);
|
||||
data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF);
|
||||
data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF);
|
||||
CHECK(secp256k1_ec_pubkey_parse(data->ctx, &pubkey, data->pubkey, data->pubkeylen) == 1);
|
||||
CHECK(secp256k1_ecdsa_signature_parse_der(data->ctx, &sig, data->sig, data->siglen) == 1);
|
||||
CHECK(secp256k1_ecdsa_verify(data->ctx, &sig, data->msg, &pubkey) == (i == 0));
|
||||
data->sig[data->siglen - 1] ^= (i & 0xFF);
|
||||
data->sig[data->siglen - 2] ^= ((i >> 8) & 0xFF);
|
||||
data->sig[data->siglen - 3] ^= ((i >> 16) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_sign_setup(void* arg) {
|
||||
int i;
|
||||
bench_data *data = (bench_data*)arg;
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
data->msg[i] = i + 1;
|
||||
}
|
||||
for (i = 0; i < 32; i++) {
|
||||
data->key[i] = i + 65;
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_sign_run(void* arg, int iters) {
|
||||
int i;
|
||||
bench_data *data = (bench_data*)arg;
|
||||
|
||||
unsigned char sig[74];
|
||||
for (i = 0; i < iters; i++) {
|
||||
size_t siglen = 74;
|
||||
int j;
|
||||
secp256k1_ecdsa_signature signature;
|
||||
CHECK(secp256k1_ecdsa_sign(data->ctx, &signature, data->msg, data->key, NULL, NULL));
|
||||
CHECK(secp256k1_ecdsa_signature_serialize_der(data->ctx, sig, &siglen, &signature));
|
||||
for (j = 0; j < 32; j++) {
|
||||
data->msg[j] = sig[j];
|
||||
data->key[j] = sig[j + 32];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_keygen_setup(void* arg) {
|
||||
int i;
|
||||
bench_data *data = (bench_data*)arg;
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
data->key[i] = i + 65;
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_keygen_run(void *arg, int iters) {
|
||||
int i;
|
||||
bench_data *data = (bench_data*)arg;
|
||||
|
||||
for (i = 0; i < iters; i++) {
|
||||
unsigned char pub33[33];
|
||||
size_t len = 33;
|
||||
secp256k1_pubkey pubkey;
|
||||
CHECK(secp256k1_ec_pubkey_create(data->ctx, &pubkey, data->key));
|
||||
CHECK(secp256k1_ec_pubkey_serialize(data->ctx, pub33, &len, &pubkey, SECP256K1_EC_COMPRESSED));
|
||||
memcpy(data->key, pub33 + 1, 32);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef ENABLE_MODULE_ECDH
|
||||
# include "modules/ecdh/bench_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_RECOVERY
|
||||
# include "modules/recovery/bench_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_SCHNORRSIG
|
||||
# include "modules/schnorrsig/bench_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ELLSWIFT
|
||||
# include "modules/ellswift/bench_impl.h"
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int i;
|
||||
secp256k1_pubkey pubkey;
|
||||
secp256k1_ecdsa_signature sig;
|
||||
bench_data data;
|
||||
|
||||
int d = argc == 1;
|
||||
|
||||
/* Check for invalid user arguments */
|
||||
char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover",
|
||||
"ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec",
|
||||
"keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode",
|
||||
"ellswift_decode", "ellswift_keygen", "ellswift_ecdh"};
|
||||
int invalid_args = have_invalid_args(argc, argv, valid_args, ARRAY_SIZE(valid_args));
|
||||
|
||||
int default_iters = 20000;
|
||||
int iters = get_iters(default_iters);
|
||||
if (iters == 0) {
|
||||
help(argv[0], default_iters);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
if (have_flag(argc, argv, "-h")
|
||||
|| have_flag(argc, argv, "--help")
|
||||
|| have_flag(argc, argv, "help")) {
|
||||
help(argv[0], default_iters);
|
||||
return EXIT_SUCCESS;
|
||||
} else if (invalid_args) {
|
||||
fprintf(stderr, "./bench: unrecognized argument.\n\n");
|
||||
help(argv[0], default_iters);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the user tries to benchmark optional module without building it */
|
||||
#ifndef ENABLE_MODULE_ECDH
|
||||
if (have_flag(argc, argv, "ecdh")) {
|
||||
fprintf(stderr, "./bench: ECDH module not enabled.\n");
|
||||
fprintf(stderr, "See README.md for configuration instructions.\n\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_MODULE_RECOVERY
|
||||
if (have_flag(argc, argv, "recover") || have_flag(argc, argv, "ecdsa_recover")) {
|
||||
fprintf(stderr, "./bench: Public key recovery module not enabled.\n");
|
||||
fprintf(stderr, "See README.md for configuration instructions.\n\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_MODULE_SCHNORRSIG
|
||||
if (have_flag(argc, argv, "schnorrsig") || have_flag(argc, argv, "schnorrsig_sign") || have_flag(argc, argv, "schnorrsig_verify")) {
|
||||
fprintf(stderr, "./bench: Schnorr signatures module not enabled.\n");
|
||||
fprintf(stderr, "See README.md for configuration instructions.\n\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_MODULE_ELLSWIFT
|
||||
if (have_flag(argc, argv, "ellswift") || have_flag(argc, argv, "ellswift_encode") || have_flag(argc, argv, "ellswift_decode") ||
|
||||
have_flag(argc, argv, "encode") || have_flag(argc, argv, "decode") || have_flag(argc, argv, "ellswift_keygen") ||
|
||||
have_flag(argc, argv, "ellswift_ecdh")) {
|
||||
fprintf(stderr, "./bench: ElligatorSwift module not enabled.\n");
|
||||
fprintf(stderr, "See README.md for configuration instructions.\n\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ECDSA benchmark */
|
||||
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
|
||||
for (i = 0; i < 32; i++) {
|
||||
data.msg[i] = 1 + i;
|
||||
}
|
||||
for (i = 0; i < 32; i++) {
|
||||
data.key[i] = 33 + i;
|
||||
}
|
||||
data.siglen = 72;
|
||||
CHECK(secp256k1_ecdsa_sign(data.ctx, &sig, data.msg, data.key, NULL, NULL));
|
||||
CHECK(secp256k1_ecdsa_signature_serialize_der(data.ctx, data.sig, &data.siglen, &sig));
|
||||
CHECK(secp256k1_ec_pubkey_create(data.ctx, &pubkey, data.key));
|
||||
data.pubkeylen = 33;
|
||||
CHECK(secp256k1_ec_pubkey_serialize(data.ctx, data.pubkey, &data.pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED) == 1);
|
||||
|
||||
print_output_table_header_row();
|
||||
if (d || have_flag(argc, argv, "ecdsa") || have_flag(argc, argv, "verify") || have_flag(argc, argv, "ecdsa_verify")) run_benchmark("ecdsa_verify", bench_verify, NULL, NULL, &data, 10, iters);
|
||||
|
||||
if (d || have_flag(argc, argv, "ecdsa") || have_flag(argc, argv, "sign") || have_flag(argc, argv, "ecdsa_sign")) run_benchmark("ecdsa_sign", bench_sign_run, bench_sign_setup, NULL, &data, 10, iters);
|
||||
if (d || have_flag(argc, argv, "ec") || have_flag(argc, argv, "keygen") || have_flag(argc, argv, "ec_keygen")) run_benchmark("ec_keygen", bench_keygen_run, bench_keygen_setup, NULL, &data, 10, iters);
|
||||
|
||||
secp256k1_context_destroy(data.ctx);
|
||||
|
||||
#ifdef ENABLE_MODULE_ECDH
|
||||
/* ECDH benchmarks */
|
||||
run_ecdh_bench(iters, argc, argv);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_RECOVERY
|
||||
/* ECDSA recovery benchmarks */
|
||||
run_recovery_bench(iters, argc, argv);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_SCHNORRSIG
|
||||
/* Schnorr signature benchmarks */
|
||||
run_schnorrsig_bench(iters, argc, argv);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_ELLSWIFT
|
||||
/* ElligatorSwift benchmarks */
|
||||
run_ellswift_bench(iters, argc, argv);
|
||||
#endif
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
182
src/bench.h
182
src/bench.h
@@ -1,95 +1,51 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_BENCH_H
|
||||
#define SECP256K1_BENCH_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "sys/time.h"
|
||||
|
||||
#include "tests_common.h"
|
||||
|
||||
#define FP_EXP (6)
|
||||
#define FP_MULT (1000000LL)
|
||||
|
||||
/* Format fixed point number. */
|
||||
static void print_number(const int64_t x) {
|
||||
int64_t x_abs, y;
|
||||
int c, i, rounding, g; /* g = integer part size, c = fractional part size */
|
||||
size_t ptr;
|
||||
char buffer[30];
|
||||
|
||||
if (x == INT64_MIN) {
|
||||
/* Prevent UB. */
|
||||
printf("ERR");
|
||||
return;
|
||||
}
|
||||
x_abs = x < 0 ? -x : x;
|
||||
|
||||
/* Determine how many decimals we want to show (more than FP_EXP makes no
|
||||
* sense). */
|
||||
y = x_abs;
|
||||
c = 0;
|
||||
while (y > 0LL && y < 100LL * FP_MULT && c < FP_EXP) {
|
||||
y *= 10LL;
|
||||
c++;
|
||||
}
|
||||
|
||||
/* Round to 'c' decimals. */
|
||||
y = x_abs;
|
||||
rounding = 0;
|
||||
for (i = c; i < FP_EXP; ++i) {
|
||||
rounding = (y % 10) >= 5;
|
||||
y /= 10;
|
||||
}
|
||||
y += rounding;
|
||||
|
||||
/* Format and print the number. */
|
||||
ptr = sizeof(buffer) - 1;
|
||||
buffer[ptr] = 0;
|
||||
g = 0;
|
||||
if (c != 0) { /* non zero fractional part */
|
||||
for (i = 0; i < c; ++i) {
|
||||
buffer[--ptr] = '0' + (y % 10);
|
||||
y /= 10;
|
||||
}
|
||||
} else if (c == 0) { /* fractional part is 0 */
|
||||
buffer[--ptr] = '0';
|
||||
}
|
||||
buffer[--ptr] = '.';
|
||||
do {
|
||||
buffer[--ptr] = '0' + (y % 10);
|
||||
y /= 10;
|
||||
g++;
|
||||
} while (y != 0);
|
||||
if (x < 0) {
|
||||
buffer[--ptr] = '-';
|
||||
g++;
|
||||
}
|
||||
printf("%5.*s", g, &buffer[ptr]); /* Prints integer part */
|
||||
printf("%-*s", FP_EXP, &buffer[ptr + g]); /* Prints fractional part */
|
||||
static double gettimedouble(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_usec * 0.000001 + tv.tv_sec;
|
||||
}
|
||||
|
||||
static void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setup)(void*), void (*teardown)(void*, int), void* data, int count, int iter) {
|
||||
void print_number(double x) {
|
||||
double y = x;
|
||||
int c = 0;
|
||||
if (y < 0.0) {
|
||||
y = -y;
|
||||
}
|
||||
while (y > 0 && y < 100.0) {
|
||||
y *= 10.0;
|
||||
c++;
|
||||
}
|
||||
printf("%.*f", c, x);
|
||||
}
|
||||
|
||||
void run_benchmark(char *name, void (*benchmark)(void*), void (*setup)(void*), void (*teardown)(void*), void* data, int count, int iter) {
|
||||
int i;
|
||||
int64_t min = INT64_MAX;
|
||||
int64_t sum = 0;
|
||||
int64_t max = 0;
|
||||
double min = HUGE_VAL;
|
||||
double sum = 0.0;
|
||||
double max = 0.0;
|
||||
for (i = 0; i < count; i++) {
|
||||
int64_t begin, total;
|
||||
double begin, total;
|
||||
if (setup != NULL) {
|
||||
setup(data);
|
||||
}
|
||||
begin = gettime_i64();
|
||||
benchmark(data, iter);
|
||||
total = gettime_i64() - begin;
|
||||
begin = gettimedouble();
|
||||
benchmark(data);
|
||||
total = gettimedouble() - begin;
|
||||
if (teardown != NULL) {
|
||||
teardown(data, iter);
|
||||
teardown(data);
|
||||
}
|
||||
if (total < min) {
|
||||
min = total;
|
||||
@@ -99,20 +55,22 @@ static void run_benchmark(char *name, void (*benchmark)(void*, int), void (*setu
|
||||
}
|
||||
sum += total;
|
||||
}
|
||||
/* ',' is used as a column delimiter */
|
||||
printf("%-30s, ", name);
|
||||
print_number(min * FP_MULT / iter);
|
||||
printf(" , ");
|
||||
print_number(((sum * FP_MULT) / count) / iter);
|
||||
printf(" , ");
|
||||
print_number(max * FP_MULT / iter);
|
||||
printf("\n");
|
||||
printf("%s: min ", name);
|
||||
print_number(min * 1000000.0 / iter);
|
||||
printf("us / avg ");
|
||||
print_number((sum / count) * 1000000.0 / iter);
|
||||
printf("us / max ");
|
||||
print_number(max * 1000000.0 / iter);
|
||||
printf("us\n");
|
||||
}
|
||||
|
||||
static int have_flag(int argc, char** argv, char *flag) {
|
||||
int have_flag(int argc, char** argv, char *flag) {
|
||||
char** argm = argv + argc;
|
||||
argv++;
|
||||
while (argv != argm) {
|
||||
if (argv == argm) {
|
||||
return 1;
|
||||
}
|
||||
while (argv != NULL && argv != argm) {
|
||||
if (strcmp(*argv, flag) == 0) {
|
||||
return 1;
|
||||
}
|
||||
@@ -121,54 +79,4 @@ static int have_flag(int argc, char** argv, char *flag) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* takes an array containing the arguments that the user is allowed to enter on the command-line
|
||||
returns:
|
||||
- 1 if the user entered an invalid argument
|
||||
- 0 if all the user entered arguments are valid */
|
||||
static int have_invalid_args(int argc, char** argv, char** valid_args, size_t n) {
|
||||
size_t i;
|
||||
int found_valid;
|
||||
char** argm = argv + argc;
|
||||
argv++;
|
||||
|
||||
while (argv != argm) {
|
||||
found_valid = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (strcmp(*argv, valid_args[i]) == 0) {
|
||||
found_valid = 1; /* user entered a valid arg from the list */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found_valid == 0) {
|
||||
return 1; /* invalid arg found */
|
||||
}
|
||||
argv++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_iters(int default_iters) {
|
||||
char* env = getenv("SECP256K1_BENCH_ITERS");
|
||||
if (env) {
|
||||
char* endptr;
|
||||
long int iters = strtol(env, &endptr, 0);
|
||||
if (*endptr != '\0' || iters <= 0) {
|
||||
printf("Error: Value of SECP256K1_BENCH_ITERS is not a positive integer: %s\n\n", env);
|
||||
return 0;
|
||||
}
|
||||
return iters;
|
||||
} else {
|
||||
return default_iters;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_output_table_header_row(void) {
|
||||
char* bench_str = "Benchmark"; /* left justified */
|
||||
char* min_str = " Min(us) "; /* center alignment */
|
||||
char* avg_str = " Avg(us) ";
|
||||
char* max_str = " Max(us) ";
|
||||
printf("%-30s,%-15s,%-15s,%-15s\n", bench_str, min_str, avg_str, max_str);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_BENCH_H */
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2020 Andrew Poelstra *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../include/secp256k1_bppp.h"
|
||||
#include "util.h"
|
||||
#include "bench.h"
|
||||
|
||||
typedef struct {
|
||||
secp256k1_context* ctx;
|
||||
} bench_bppp_data;
|
||||
|
||||
static void bench_bppp_setup(void* arg) {
|
||||
(void) arg;
|
||||
}
|
||||
|
||||
static void bench_bppp(void* arg, int iters) {
|
||||
bench_bppp_data *data = (bench_bppp_data*)arg;
|
||||
|
||||
(void) data;
|
||||
(void) iters;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
bench_bppp_data data;
|
||||
int iters = get_iters(32);
|
||||
if (iters == 0) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
|
||||
run_benchmark("bppp_verify_bit", bench_bppp, bench_bppp_setup, NULL, &data, 10, iters);
|
||||
|
||||
secp256k1_context_destroy(data.ctx);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2015 Pieter Wuille, Andrew Poelstra *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2015 Pieter Wuille, Andrew Poelstra *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_MODULE_ECDH_BENCH_H
|
||||
#define SECP256K1_MODULE_ECDH_BENCH_H
|
||||
#include <string.h>
|
||||
|
||||
#include "../../../include/secp256k1_ecdh.h"
|
||||
#include "include/secp256k1.h"
|
||||
#include "include/secp256k1_ecdh.h"
|
||||
#include "util.h"
|
||||
#include "bench.h"
|
||||
|
||||
typedef struct {
|
||||
const secp256k1_context *ctx;
|
||||
secp256k1_context *ctx;
|
||||
secp256k1_pubkey point;
|
||||
unsigned char scalar[32];
|
||||
} bench_ecdh_data;
|
||||
@@ -26,29 +28,27 @@ static void bench_ecdh_setup(void* arg) {
|
||||
0xa2, 0xba, 0xd1, 0x84, 0xf8, 0x83, 0xc6, 0x9f
|
||||
};
|
||||
|
||||
/* create a context with no capabilities */
|
||||
data->ctx = secp256k1_context_create(SECP256K1_FLAGS_TYPE_CONTEXT);
|
||||
for (i = 0; i < 32; i++) {
|
||||
data->scalar[i] = i + 1;
|
||||
}
|
||||
CHECK(secp256k1_ec_pubkey_parse(data->ctx, &data->point, point, sizeof(point)) == 1);
|
||||
}
|
||||
|
||||
static void bench_ecdh(void* arg, int iters) {
|
||||
static void bench_ecdh(void* arg) {
|
||||
int i;
|
||||
unsigned char res[32];
|
||||
bench_ecdh_data *data = (bench_ecdh_data*)arg;
|
||||
|
||||
for (i = 0; i < iters; i++) {
|
||||
for (i = 0; i < 20000; i++) {
|
||||
CHECK(secp256k1_ecdh(data->ctx, res, &data->point, data->scalar, NULL, NULL) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void run_ecdh_bench(int iters, int argc, char** argv) {
|
||||
int main(void) {
|
||||
bench_ecdh_data data;
|
||||
int d = argc == 1;
|
||||
|
||||
data.ctx = secp256k1_context_static;
|
||||
|
||||
if (d || have_flag(argc, argv, "ecdh")) run_benchmark("ecdh", bench_ecdh, bench_ecdh_setup, NULL, &data, 10, iters);
|
||||
run_benchmark("ecdh", bench_ecdh, bench_ecdh_setup, NULL, &data, 10, 20000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_MODULE_ECDH_BENCH_H */
|
||||
@@ -1,41 +1,24 @@
|
||||
/***********************************************************************
|
||||
* Copyright (c) 2017 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2017 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "secp256k1.c"
|
||||
#include "../include/secp256k1.h"
|
||||
#include "include/secp256k1.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "hash_impl.h"
|
||||
#include "num_impl.h"
|
||||
#include "field_impl.h"
|
||||
#include "group_impl.h"
|
||||
#include "scalar_impl.h"
|
||||
#include "ecmult_impl.h"
|
||||
#include "bench.h"
|
||||
#include "secp256k1.c"
|
||||
|
||||
#define POINTS 32768
|
||||
|
||||
static void help(const char *executable_path, int default_iters) {
|
||||
printf("Benchmark EC multiplication algorithms\n");
|
||||
printf("\n");
|
||||
printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters);
|
||||
printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n");
|
||||
printf("\n");
|
||||
printf("Usage: %s [args]\n", executable_path);
|
||||
printf("The output shows the number of multiplied and summed points right after the\n");
|
||||
printf("function name. The letter 'g' indicates that one of the points is the generator.\n");
|
||||
printf("The benchmarks are divided by the number of points.\n");
|
||||
printf("\n");
|
||||
printf("default (ecmult_multi): picks pippenger_wnaf or strauss_wnaf depending on the\n");
|
||||
printf(" batch size\n");
|
||||
printf("pippenger_wnaf: for all batch sizes\n");
|
||||
printf("strauss_wnaf: for all batch sizes\n");
|
||||
printf("simple: multiply and sum each point individually\n");
|
||||
}
|
||||
#define ITERS 10000
|
||||
|
||||
typedef struct {
|
||||
/* Setup once in advance */
|
||||
@@ -43,178 +26,23 @@ typedef struct {
|
||||
secp256k1_scratch_space* scratch;
|
||||
secp256k1_scalar* scalars;
|
||||
secp256k1_ge* pubkeys;
|
||||
secp256k1_gej* pubkeys_gej;
|
||||
secp256k1_scalar* seckeys;
|
||||
secp256k1_gej* expected_output;
|
||||
secp256k1_ecmult_multi_func ecmult_multi;
|
||||
|
||||
/* Changes per benchmark */
|
||||
/* Changes per test */
|
||||
size_t count;
|
||||
int includes_g;
|
||||
|
||||
/* Changes per benchmark iteration, used to pick different scalars and pubkeys
|
||||
* in each run. */
|
||||
/* Changes per test iteration */
|
||||
size_t offset1;
|
||||
size_t offset2;
|
||||
|
||||
/* Benchmark output. */
|
||||
/* Test output. */
|
||||
secp256k1_gej* output;
|
||||
secp256k1_fe* output_xonly;
|
||||
} bench_data;
|
||||
|
||||
/* Hashes x into [0, POINTS) twice and store the result in offset1 and offset2. */
|
||||
static void hash_into_offset(bench_data* data, size_t x) {
|
||||
data->offset1 = (x * 0x537b7f6f + 0x8f66a481) % POINTS;
|
||||
data->offset2 = (x * 0x7f6f537b + 0x6a1a8f49) % POINTS;
|
||||
}
|
||||
|
||||
/* Check correctness of the benchmark by computing
|
||||
* sum(outputs) ?= (sum(scalars_gen) + sum(seckeys)*sum(scalars))*G */
|
||||
static void bench_ecmult_teardown_helper(bench_data* data, size_t* seckey_offset, size_t* scalar_offset, size_t* scalar_gen_offset, int iters) {
|
||||
int i;
|
||||
secp256k1_gej sum_output, tmp;
|
||||
secp256k1_scalar sum_scalars;
|
||||
|
||||
secp256k1_gej_set_infinity(&sum_output);
|
||||
secp256k1_scalar_set_int(&sum_scalars, 0);
|
||||
for (i = 0; i < iters; ++i) {
|
||||
secp256k1_gej_add_var(&sum_output, &sum_output, &data->output[i], NULL);
|
||||
if (scalar_gen_offset != NULL) {
|
||||
secp256k1_scalar_add(&sum_scalars, &sum_scalars, &data->scalars[(*scalar_gen_offset+i) % POINTS]);
|
||||
}
|
||||
if (seckey_offset != NULL) {
|
||||
secp256k1_scalar s = data->seckeys[(*seckey_offset+i) % POINTS];
|
||||
secp256k1_scalar_mul(&s, &s, &data->scalars[(*scalar_offset+i) % POINTS]);
|
||||
secp256k1_scalar_add(&sum_scalars, &sum_scalars, &s);
|
||||
}
|
||||
}
|
||||
secp256k1_ecmult_gen_gej(&data->ctx->ecmult_gen_ctx, &tmp, &sum_scalars);
|
||||
CHECK(secp256k1_gej_eq_var(&tmp, &sum_output));
|
||||
}
|
||||
|
||||
static void bench_ecmult_setup(void* arg) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
/* Re-randomize offset to ensure that we're using different scalars and
|
||||
* group elements in each run. */
|
||||
hash_into_offset(data, data->offset1);
|
||||
}
|
||||
|
||||
static void bench_ecmult_gen(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iters; ++i) {
|
||||
secp256k1_ecmult_gen_gej(&data->ctx->ecmult_gen_ctx, &data->output[i], &data->scalars[(data->offset1+i) % POINTS]);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_gen_teardown(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
bench_ecmult_teardown_helper(data, NULL, NULL, &data->offset1, iters);
|
||||
}
|
||||
|
||||
static void bench_ecmult_const(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iters; ++i) {
|
||||
secp256k1_ecmult_const(&data->output[i], &data->pubkeys[(data->offset1+i) % POINTS], &data->scalars[(data->offset2+i) % POINTS]);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_const_teardown(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
bench_ecmult_teardown_helper(data, &data->offset1, &data->offset2, NULL, iters);
|
||||
}
|
||||
|
||||
static void bench_ecmult_const_xonly(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iters; ++i) {
|
||||
const secp256k1_ge* pubkey = &data->pubkeys[(data->offset1+i) % POINTS];
|
||||
const secp256k1_scalar* scalar = &data->scalars[(data->offset2+i) % POINTS];
|
||||
int known_on_curve = 1;
|
||||
secp256k1_ecmult_const_xonly(&data->output_xonly[i], &pubkey->x, NULL, scalar, known_on_curve);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_const_xonly_teardown(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int i;
|
||||
|
||||
/* verify by comparing with x coordinate of regular ecmult result */
|
||||
for (i = 0; i < iters; ++i) {
|
||||
const secp256k1_gej* pubkey_gej = &data->pubkeys_gej[(data->offset1+i) % POINTS];
|
||||
const secp256k1_scalar* scalar = &data->scalars[(data->offset2+i) % POINTS];
|
||||
secp256k1_gej expected_gej;
|
||||
secp256k1_ecmult(&expected_gej, pubkey_gej, scalar, NULL);
|
||||
CHECK(secp256k1_gej_eq_x_var(&data->output_xonly[i], &expected_gej));
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_1p(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iters; ++i) {
|
||||
secp256k1_ecmult(&data->output[i], &data->pubkeys_gej[(data->offset1+i) % POINTS], &data->scalars[(data->offset2+i) % POINTS], NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_1p_teardown(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
bench_ecmult_teardown_helper(data, &data->offset1, &data->offset2, NULL, iters);
|
||||
}
|
||||
|
||||
static void bench_ecmult_0p_g(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iters; ++i) {
|
||||
secp256k1_ecmult(&data->output[i], NULL, &secp256k1_scalar_zero, &data->scalars[(data->offset1+i) % POINTS]);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_0p_g_teardown(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
bench_ecmult_teardown_helper(data, NULL, NULL, &data->offset1, iters);
|
||||
}
|
||||
|
||||
static void bench_ecmult_1p_g(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iters/2; ++i) {
|
||||
secp256k1_ecmult(&data->output[i], &data->pubkeys_gej[(data->offset1+i) % POINTS], &data->scalars[(data->offset2+i) % POINTS], &data->scalars[(data->offset1+i) % POINTS]);
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_1p_g_teardown(void* arg, int iters) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
bench_ecmult_teardown_helper(data, &data->offset1, &data->offset2, &data->offset1, iters/2);
|
||||
}
|
||||
|
||||
static void run_ecmult_bench(bench_data* data, int iters) {
|
||||
char str[32];
|
||||
sprintf(str, "ecmult_gen");
|
||||
run_benchmark(str, bench_ecmult_gen, bench_ecmult_setup, bench_ecmult_gen_teardown, data, 10, iters);
|
||||
sprintf(str, "ecmult_const");
|
||||
run_benchmark(str, bench_ecmult_const, bench_ecmult_setup, bench_ecmult_const_teardown, data, 10, iters);
|
||||
sprintf(str, "ecmult_const_xonly");
|
||||
run_benchmark(str, bench_ecmult_const_xonly, bench_ecmult_setup, bench_ecmult_const_xonly_teardown, data, 10, iters);
|
||||
/* ecmult with non generator point */
|
||||
sprintf(str, "ecmult_1p");
|
||||
run_benchmark(str, bench_ecmult_1p, bench_ecmult_setup, bench_ecmult_1p_teardown, data, 10, iters);
|
||||
/* ecmult with generator point */
|
||||
sprintf(str, "ecmult_0p_g");
|
||||
run_benchmark(str, bench_ecmult_0p_g, bench_ecmult_setup, bench_ecmult_0p_g_teardown, data, 10, iters);
|
||||
/* ecmult with generator and non-generator point. The reported time is per point. */
|
||||
sprintf(str, "ecmult_1p_g");
|
||||
run_benchmark(str, bench_ecmult_1p_g, bench_ecmult_setup, bench_ecmult_1p_g_teardown, data, 10, 2*iters);
|
||||
}
|
||||
|
||||
static int bench_ecmult_multi_callback(secp256k1_scalar* sc, secp256k1_ge* ge, size_t idx, void* arg) {
|
||||
static int bench_callback(secp256k1_scalar* sc, secp256k1_ge* ge, size_t idx, void* arg) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
if (data->includes_g) ++idx;
|
||||
if (idx == 0) {
|
||||
@@ -227,30 +55,31 @@ static int bench_ecmult_multi_callback(secp256k1_scalar* sc, secp256k1_ge* ge, s
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void bench_ecmult_multi(void* arg, int iters) {
|
||||
static void bench_ecmult(void* arg) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
|
||||
size_t count = data->count;
|
||||
int includes_g = data->includes_g;
|
||||
int iter;
|
||||
int count = data->count;
|
||||
iters = iters / data->count;
|
||||
size_t iters = 1 + ITERS / count;
|
||||
size_t iter;
|
||||
|
||||
for (iter = 0; iter < iters; ++iter) {
|
||||
data->ecmult_multi(&data->ctx->error_callback, data->scratch, &data->output[iter], data->includes_g ? &data->scalars[data->offset1] : NULL, bench_ecmult_multi_callback, arg, count - includes_g);
|
||||
data->ecmult_multi(&data->ctx->error_callback, &data->ctx->ecmult_ctx, data->scratch, &data->output[iter], data->includes_g ? &data->scalars[data->offset1] : NULL, bench_callback, arg, count - includes_g);
|
||||
data->offset1 = (data->offset1 + count) % POINTS;
|
||||
data->offset2 = (data->offset2 + count - 1) % POINTS;
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_ecmult_multi_setup(void* arg) {
|
||||
static void bench_ecmult_setup(void* arg) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
hash_into_offset(data, data->count);
|
||||
data->offset1 = (data->count * 0x537b7f6f + 0x8f66a481) % POINTS;
|
||||
data->offset2 = (data->count * 0x7f6f537b + 0x6a1a8f49) % POINTS;
|
||||
}
|
||||
|
||||
static void bench_ecmult_multi_teardown(void* arg, int iters) {
|
||||
static void bench_ecmult_teardown(void* arg) {
|
||||
bench_data* data = (bench_data*)arg;
|
||||
int iter;
|
||||
iters = iters / data->count;
|
||||
size_t iters = 1 + ITERS / data->count;
|
||||
size_t iter;
|
||||
/* Verify the results in teardown, to avoid doing comparisons while benchmarking. */
|
||||
for (iter = 0; iter < iters; ++iter) {
|
||||
secp256k1_gej tmp;
|
||||
@@ -259,9 +88,9 @@ static void bench_ecmult_multi_teardown(void* arg, int iters) {
|
||||
}
|
||||
}
|
||||
|
||||
static void generate_scalar(const secp256k1_context *ctx, uint32_t num, secp256k1_scalar* scalar) {
|
||||
static void generate_scalar(uint32_t num, secp256k1_scalar* scalar) {
|
||||
secp256k1_sha256 sha256;
|
||||
unsigned char c[10] = {'e', 'c', 'm', 'u', 'l', 't', 0, 0, 0, 0};
|
||||
unsigned char c[11] = {'e', 'c', 'm', 'u', 'l', 't', 0, 0, 0, 0};
|
||||
unsigned char buf[32];
|
||||
int overflow = 0;
|
||||
c[6] = num;
|
||||
@@ -269,22 +98,24 @@ static void generate_scalar(const secp256k1_context *ctx, uint32_t num, secp256k
|
||||
c[8] = num >> 16;
|
||||
c[9] = num >> 24;
|
||||
secp256k1_sha256_initialize(&sha256);
|
||||
secp256k1_sha256_write(secp256k1_get_hash_context(ctx), &sha256, c, sizeof(c));
|
||||
secp256k1_sha256_finalize(secp256k1_get_hash_context(ctx), &sha256, buf);
|
||||
secp256k1_sha256_write(&sha256, c, sizeof(c));
|
||||
secp256k1_sha256_finalize(&sha256, buf);
|
||||
secp256k1_scalar_set_b32(scalar, buf, &overflow);
|
||||
CHECK(!overflow);
|
||||
}
|
||||
|
||||
static void run_ecmult_multi_bench(bench_data* data, size_t count, int includes_g, int num_iters) {
|
||||
static void run_test(bench_data* data, size_t count, int includes_g) {
|
||||
char str[32];
|
||||
size_t iters = 1 + num_iters / count;
|
||||
static const secp256k1_scalar zero = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
size_t iters = 1 + ITERS / count;
|
||||
size_t iter;
|
||||
|
||||
data->count = count;
|
||||
data->includes_g = includes_g;
|
||||
|
||||
/* Compute (the negation of) the expected results directly. */
|
||||
hash_into_offset(data, data->count);
|
||||
data->offset1 = (data->count * 0x537b7f6f + 0x8f66a481) % POINTS;
|
||||
data->offset2 = (data->count * 0x7f6f537b + 0x6a1a8f49) % POINTS;
|
||||
for (iter = 0; iter < iters; ++iter) {
|
||||
secp256k1_scalar tmp;
|
||||
secp256k1_scalar total = data->scalars[(data->offset1++) % POINTS];
|
||||
@@ -294,39 +125,27 @@ static void run_ecmult_multi_bench(bench_data* data, size_t count, int includes_
|
||||
secp256k1_scalar_add(&total, &total, &tmp);
|
||||
}
|
||||
secp256k1_scalar_negate(&total, &total);
|
||||
secp256k1_ecmult(&data->expected_output[iter], NULL, &secp256k1_scalar_zero, &total);
|
||||
secp256k1_ecmult(&data->ctx->ecmult_ctx, &data->expected_output[iter], NULL, &zero, &total);
|
||||
}
|
||||
|
||||
/* Run the benchmark. */
|
||||
if (includes_g) {
|
||||
sprintf(str, "ecmult_multi_%ip_g", (int)count - 1);
|
||||
} else {
|
||||
sprintf(str, "ecmult_multi_%ip", (int)count);
|
||||
}
|
||||
run_benchmark(str, bench_ecmult_multi, bench_ecmult_multi_setup, bench_ecmult_multi_teardown, data, 10, count * iters);
|
||||
sprintf(str, includes_g ? "ecmult_%ig" : "ecmult_%i", (int)count);
|
||||
run_benchmark(str, bench_ecmult, bench_ecmult_setup, bench_ecmult_teardown, data, 10, count * (1 + ITERS / count));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bench_data data;
|
||||
int i, p;
|
||||
secp256k1_gej* pubkeys_gej;
|
||||
size_t scratch_size;
|
||||
|
||||
int default_iters = 10000;
|
||||
int iters = get_iters(default_iters);
|
||||
if (iters == 0) {
|
||||
help(argv[0], default_iters);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||
scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*16;
|
||||
data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size);
|
||||
data.ecmult_multi = secp256k1_ecmult_multi_var;
|
||||
|
||||
if (argc > 1) {
|
||||
if(have_flag(argc, argv, "-h")
|
||||
|| have_flag(argc, argv, "--help")
|
||||
|| have_flag(argc, argv, "help")) {
|
||||
help(argv[0], default_iters);
|
||||
return EXIT_SUCCESS;
|
||||
} else if(have_flag(argc, argv, "pippenger_wnaf")) {
|
||||
if(have_flag(argc, argv, "pippenger_wnaf")) {
|
||||
printf("Using pippenger_wnaf:\n");
|
||||
data.ecmult_multi = secp256k1_ecmult_pippenger_batch_single;
|
||||
} else if(have_flag(argc, argv, "strauss_wnaf")) {
|
||||
@@ -334,76 +153,55 @@ int main(int argc, char **argv) {
|
||||
data.ecmult_multi = secp256k1_ecmult_strauss_batch_single;
|
||||
} else if(have_flag(argc, argv, "simple")) {
|
||||
printf("Using simple algorithm:\n");
|
||||
data.ecmult_multi = secp256k1_ecmult_multi_var;
|
||||
secp256k1_scratch_space_destroy(data.ctx, data.scratch);
|
||||
data.scratch = NULL;
|
||||
} else {
|
||||
fprintf(stderr, "%s: unrecognized argument '%s'.\n\n", argv[0], argv[1]);
|
||||
help(argv[0], default_iters);
|
||||
return EXIT_FAILURE;
|
||||
fprintf(stderr, "%s: unrecognized argument '%s'.\n", argv[0], argv[1]);
|
||||
fprintf(stderr, "Use 'pippenger_wnaf', 'strauss_wnaf', 'simple' or no argument to benchmark a combined algorithm.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT;
|
||||
if (!have_flag(argc, argv, "simple")) {
|
||||
data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size);
|
||||
} else {
|
||||
data.scratch = NULL;
|
||||
}
|
||||
|
||||
/* Allocate stuff */
|
||||
data.scalars = malloc(sizeof(secp256k1_scalar) * POINTS);
|
||||
data.seckeys = malloc(sizeof(secp256k1_scalar) * POINTS);
|
||||
data.pubkeys = malloc(sizeof(secp256k1_ge) * POINTS);
|
||||
data.pubkeys_gej = malloc(sizeof(secp256k1_gej) * POINTS);
|
||||
data.expected_output = malloc(sizeof(secp256k1_gej) * (iters + 1));
|
||||
data.output = malloc(sizeof(secp256k1_gej) * (iters + 1));
|
||||
data.output_xonly = malloc(sizeof(secp256k1_fe) * (iters + 1));
|
||||
data.expected_output = malloc(sizeof(secp256k1_gej) * (ITERS + 1));
|
||||
data.output = malloc(sizeof(secp256k1_gej) * (ITERS + 1));
|
||||
|
||||
/* Generate a set of scalars, and private/public keypairs. */
|
||||
secp256k1_gej_set_ge(&data.pubkeys_gej[0], &secp256k1_ge_const_g);
|
||||
pubkeys_gej = malloc(sizeof(secp256k1_gej) * POINTS);
|
||||
secp256k1_gej_set_ge(&pubkeys_gej[0], &secp256k1_ge_const_g);
|
||||
secp256k1_scalar_set_int(&data.seckeys[0], 1);
|
||||
for (i = 0; i < POINTS; ++i) {
|
||||
generate_scalar(data.ctx, i, &data.scalars[i]);
|
||||
generate_scalar(i, &data.scalars[i]);
|
||||
if (i) {
|
||||
secp256k1_gej_double_var(&data.pubkeys_gej[i], &data.pubkeys_gej[i - 1], NULL);
|
||||
secp256k1_gej_double_var(&pubkeys_gej[i], &pubkeys_gej[i - 1], NULL);
|
||||
secp256k1_scalar_add(&data.seckeys[i], &data.seckeys[i - 1], &data.seckeys[i - 1]);
|
||||
}
|
||||
}
|
||||
secp256k1_ge_set_all_gej_var(data.pubkeys, data.pubkeys_gej, POINTS);
|
||||
|
||||
|
||||
print_output_table_header_row();
|
||||
/* Initialize offset1 and offset2 */
|
||||
hash_into_offset(&data, 0);
|
||||
run_ecmult_bench(&data, iters);
|
||||
secp256k1_ge_set_all_gej_var(data.pubkeys, pubkeys_gej, POINTS);
|
||||
free(pubkeys_gej);
|
||||
|
||||
for (i = 1; i <= 8; ++i) {
|
||||
run_ecmult_multi_bench(&data, i, 1, iters);
|
||||
run_test(&data, i, 1);
|
||||
}
|
||||
|
||||
/* This is disabled with low count of iterations because the loop runs 77 times even with iters=1
|
||||
* and the higher it goes the longer the computation takes(more points)
|
||||
* So we don't run this benchmark with low iterations to prevent slow down */
|
||||
if (iters > 2) {
|
||||
for (p = 0; p <= 11; ++p) {
|
||||
for (i = 9; i <= 16; ++i) {
|
||||
run_ecmult_multi_bench(&data, i << p, 1, iters);
|
||||
}
|
||||
for (p = 0; p <= 11; ++p) {
|
||||
for (i = 9; i <= 16; ++i) {
|
||||
run_test(&data, i << p, 1);
|
||||
}
|
||||
} else {
|
||||
printf("Skipping some benchmarks due to SECP256K1_BENCH_ITERS <= 2\n");
|
||||
}
|
||||
|
||||
if (data.scratch != NULL) {
|
||||
secp256k1_scratch_space_destroy(data.ctx, data.scratch);
|
||||
}
|
||||
secp256k1_context_destroy(data.ctx);
|
||||
free(data.scalars);
|
||||
free(data.pubkeys);
|
||||
free(data.pubkeys_gej);
|
||||
free(data.seckeys);
|
||||
free(data.output_xonly);
|
||||
free(data.output);
|
||||
free(data.expected_output);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
return(0);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
**********************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../include/secp256k1_generator.h"
|
||||
#include "include/secp256k1_generator.h"
|
||||
#include "util.h"
|
||||
#include "bench.h"
|
||||
|
||||
@@ -24,22 +23,22 @@ static void bench_generator_setup(void* arg) {
|
||||
memset(data->blind, 0x13, 32);
|
||||
}
|
||||
|
||||
static void bench_generator_generate(void* arg, int iters) {
|
||||
static void bench_generator_generate(void* arg) {
|
||||
int i;
|
||||
bench_generator_t *data = (bench_generator_t*)arg;
|
||||
|
||||
for (i = 0; i < iters; i++) {
|
||||
for (i = 0; i < 20000; i++) {
|
||||
secp256k1_generator gen;
|
||||
CHECK(secp256k1_generator_generate(data->ctx, &gen, data->key));
|
||||
data->key[i & 31]++;
|
||||
}
|
||||
}
|
||||
|
||||
static void bench_generator_generate_blinded(void* arg, int iters) {
|
||||
static void bench_generator_generate_blinded(void* arg) {
|
||||
int i;
|
||||
bench_generator_t *data = (bench_generator_t*)arg;
|
||||
|
||||
for (i = 0; i < iters; i++) {
|
||||
for (i = 0; i < 20000; i++) {
|
||||
secp256k1_generator gen;
|
||||
CHECK(secp256k1_generator_generate_blinded(data->ctx, &gen, data->key, data->blind));
|
||||
data->key[1 + (i & 30)]++;
|
||||
@@ -49,16 +48,12 @@ static void bench_generator_generate_blinded(void* arg, int iters) {
|
||||
|
||||
int main(void) {
|
||||
bench_generator_t data;
|
||||
int iters = get_iters(20000);
|
||||
if (iters == 0) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||
|
||||
run_benchmark("generator_generate", bench_generator_generate, bench_generator_setup, NULL, &data, 10, iters);
|
||||
run_benchmark("generator_generate_blinded", bench_generator_generate_blinded, bench_generator_setup, NULL, &data, 10, iters);
|
||||
run_benchmark("generator_generate", bench_generator_generate, bench_generator_setup, NULL, &data, 10, 20000);
|
||||
run_benchmark("generator_generate_blinded", bench_generator_generate_blinded, bench_generator_setup, NULL, &data, 10, 20000);
|
||||
|
||||
secp256k1_context_destroy(data.ctx);
|
||||
return EXIT_SUCCESS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/***********************************************************************
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "secp256k1.c"
|
||||
#include "../include/secp256k1.h"
|
||||
#include "util.h"
|
||||
#include "bench.h"
|
||||
#include "modules/iceberg/bench_impl.h"
|
||||
|
||||
/* Small sizes, spread out enough to show the growth curve. These are not
|
||||
* deployment recommendations: all five are below the n >= 3t-2 that the
|
||||
* agreement around the scheme needs, and 4-of-7 versus 5-of-9 is here because
|
||||
* the seed count roughly triples between them, not because anyone runs it. */
|
||||
static const unsigned int CONFIGS[][2] = { {3,2}, {5,3}, {7,4}, {9,5}, {10,5} };
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
bench_iceberg_data data;
|
||||
int iters = get_iters(1000);
|
||||
size_t c;
|
||||
|
||||
(void)argc; (void)argv;
|
||||
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
||||
|
||||
printf("%-22s", "");
|
||||
for (c = 0; c < sizeof(CONFIGS)/sizeof(CONFIGS[0]); c++) {
|
||||
char label[16];
|
||||
sprintf(label, "%u-of-%u", CONFIGS[c][1], CONFIGS[c][0]);
|
||||
printf("%12s", label);
|
||||
}
|
||||
printf("\n");
|
||||
printf("%-22s", "seeds per participant");
|
||||
for (c = 0; c < sizeof(CONFIGS)/sizeof(CONFIGS[0]); c++) {
|
||||
printf("%12u", secp256k1_rss_binom(CONFIGS[c][0] - 1, CONFIGS[c][1] - 1));
|
||||
}
|
||||
printf("\n\n");
|
||||
|
||||
{
|
||||
/* per_sig marks the rows a signing session actually pays for, so the
|
||||
* total at the bottom comes from the same run as the rows above it
|
||||
* rather than from someone adding them up by hand. */
|
||||
struct { const char *name; void (*fn)(void*, int); int per_sig; const char *note; } benches[] = {
|
||||
{ "shares_gen", bench_iceberg_shares_gen, 0, " (setup only)" },
|
||||
{ "share_cache_create",bench_iceberg_cache_create, 0, " (setup only)" },
|
||||
{ "pubshare_gen", bench_iceberg_pubshare_gen, 0, " (setup only)" },
|
||||
{ "pubkey_agg", bench_iceberg_pubkey_agg, 0, " (setup only)" },
|
||||
{ "nonce_gen", bench_iceberg_nonce_gen, 1, "" },
|
||||
{ "nonce_agg", bench_iceberg_nonce_agg, 1, "" },
|
||||
{ "partial_sign", bench_iceberg_partial_sign, 1, "" },
|
||||
{ "partial_sig_agg", bench_iceberg_partial_sig_agg, 1, "" },
|
||||
{ "partial_sig_verify",bench_iceberg_partial_sig_verify, 0, " (optional, per share)" },
|
||||
{ " degree_check", bench_iceberg_degree_check, 0, " (inside nonce_agg, partial_sign and partial_sig_verify)" },
|
||||
{ " interpolate", bench_iceberg_interpolate, 0, " (inside nonce_agg, partial_sign and partial_sig_verify)" },
|
||||
{ " lagrange_basis", bench_iceberg_lagrange_basis, 0, " (inside degree_check)" }
|
||||
};
|
||||
double per_signature[sizeof(CONFIGS)/sizeof(CONFIGS[0])] = { 0 };
|
||||
size_t b;
|
||||
for (b = 0; b < sizeof(benches)/sizeof(benches[0]); b++) {
|
||||
printf("%-22s", benches[b].name);
|
||||
for (c = 0; c < sizeof(CONFIGS)/sizeof(CONFIGS[0]); c++) {
|
||||
int64_t begin, total;
|
||||
double each;
|
||||
int i;
|
||||
data.n = CONFIGS[c][0];
|
||||
data.t = CONFIGS[c][1];
|
||||
data.mu = 2 * data.t - 1;
|
||||
bench_iceberg_setup(&data);
|
||||
benches[b].fn(&data, 2); /* warm up */
|
||||
begin = gettime_i64();
|
||||
for (i = 0; i < iters; i++) {
|
||||
benches[b].fn(&data, 1);
|
||||
}
|
||||
total = gettime_i64() - begin;
|
||||
each = (double)total / iters;
|
||||
if (benches[b].per_sig) {
|
||||
per_signature[c] += each;
|
||||
}
|
||||
printf("%9.1f us", each);
|
||||
}
|
||||
printf("%s\n", benches[b].note);
|
||||
}
|
||||
|
||||
printf("\n%-22s", "per signature");
|
||||
for (c = 0; c < sizeof(CONFIGS)/sizeof(CONFIGS[0]); c++) {
|
||||
printf("%9.1f us", per_signature[c]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
secp256k1_context_destroy(data.ctx);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user