Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bip-0374.mediawiki
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ This proposal is compatible with all older clients.
== Test Vectors and Reference Code ==

A reference python implementation is included [https://github.com/bitcoin/bips/blob/master/bip-0374/reference.py here].
It uses a vendored copy of the [https://github.com/secp256k1lab/secp256k1lab/ secp256k1lab] library at version 1.0.0
(commit [https://github.com/secp256k1lab/secp256k1lab/commit/44dc4bd893b8f03e621585e3bf255253e0e0fbfb
44dc4bd893b8f03e621585e3bf255253e0e0fbfb]).

Test vectors can be generated by running <code>./bip-0374/gen_test_vectors.py</code> which will produce a CSV file of random test vectors for both generating and verifying proofs. These can be run against the reference implementation with <code>./bip-0374/run_test_vectors.py</code>.

== Changelog ==
Expand Down
15 changes: 7 additions & 8 deletions bip-0374/gen_test_vectors.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
#!/usr/bin/env python3
"""Generate the BIP-0374 test vectors."""
import csv
import os
import sys
from pathlib import Path
from reference import (
TaggedHash,
dleq_generate_proof,
dleq_verify_proof,
)
from secp256k1 import G as GENERATOR, GE
from secp256k1lab.secp256k1 import G as GENERATOR, GE
from secp256k1lab.util import tagged_hash


NUM_SUCCESS_TEST_VECTORS = 8
DLEQ_TAG_TESTVECTORS_RNG = "BIP0374/testvectors_rng"

FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv')
FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv')
FILENAME_GENERATE_PROOF_TEST = Path(__file__).parent / 'test_vectors_generate_proof.csv'
FILENAME_VERIFY_PROOF_TEST = Path(__file__).parent / 'test_vectors_verify_proof.csv'


def random_scalar_int(vector_i, purpose):
rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
rng_out = tagged_hash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
return int.from_bytes(rng_out, 'big') % GE.ORDER


def random_bytes(vector_i, purpose):
rng_out = TaggedHash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
rng_out = tagged_hash(DLEQ_TAG_TESTVECTORS_RNG, purpose.encode() + vector_i.to_bytes(4, 'little'))
return rng_out


Expand Down
26 changes: 9 additions & 17 deletions bip-0374/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,30 @@

"""Reference implementation of DLEQ BIP for secp256k1 with unit tests."""

from hashlib import sha256
from pathlib import Path
import random
from secp256k1 import G, GE
import sys
import unittest

# Prefer the vendored copy of secp256k1lab
sys.path.insert(0, str(Path(__file__).parent / "secp256k1lab/src"))
from secp256k1lab.secp256k1 import G, GE
from secp256k1lab.util import tagged_hash, xor_bytes


DLEQ_TAG_AUX = "BIP0374/aux"
DLEQ_TAG_NONCE = "BIP0374/nonce"
DLEQ_TAG_CHALLENGE = "BIP0374/challenge"


def TaggedHash(tag: str, data: bytes) -> bytes:
ss = sha256(tag.encode()).digest()
ss += ss
ss += data
return sha256(ss).digest()


def xor_bytes(lhs: bytes, rhs: bytes) -> bytes:
assert len(lhs) == len(rhs)
return bytes([lhs[i] ^ rhs[i] for i in range(len(lhs))])


def dleq_challenge(
A: GE, B: GE, C: GE, R1: GE, R2: GE, m: bytes | None, G: GE,
) -> int:
if m is not None:
assert len(m) == 32
m = bytes([]) if m is None else m
return int.from_bytes(
TaggedHash(
tagged_hash(
DLEQ_TAG_CHALLENGE,
A.to_bytes_compressed()
+ B.to_bytes_compressed()
Expand All @@ -59,9 +51,9 @@ def dleq_generate_proof(
assert len(m) == 32
A = a * G
C = a * B
t = xor_bytes(a.to_bytes(32, "big"), TaggedHash(DLEQ_TAG_AUX, r))
t = xor_bytes(a.to_bytes(32, "big"), tagged_hash(DLEQ_TAG_AUX, r))
m_prime = bytes([]) if m is None else m
rand = TaggedHash(
rand = tagged_hash(
DLEQ_TAG_NONCE, t + A.to_bytes_compressed() + C.to_bytes_compressed() + m_prime
)
k = int.from_bytes(rand, "big") % GE.ORDER
Expand Down
8 changes: 4 additions & 4 deletions bip-0374/run_test_vectors.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
#!/usr/bin/env python3
"""Run the BIP-DLEQ test vectors."""
import csv
import os
from pathlib import Path
import sys
from reference import (
dleq_generate_proof,
dleq_verify_proof,
)
from secp256k1 import GE
from secp256k1lab.secp256k1 import GE


FILENAME_GENERATE_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_generate_proof.csv')
FILENAME_VERIFY_PROOF_TEST = os.path.join(sys.path[0], 'test_vectors_verify_proof.csv')
FILENAME_GENERATE_PROOF_TEST = Path(__file__).parent / 'test_vectors_generate_proof.csv'
FILENAME_VERIFY_PROOF_TEST = Path(__file__).parent / 'test_vectors_verify_proof.csv'


all_passed = True
Expand Down
17 changes: 17 additions & 0 deletions bip-0374/secp256k1lab/.github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Tests
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v5
- run: uvx ruff check .
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v5
- run: uvx mypy .
1 change: 1 addition & 0 deletions bip-0374/secp256k1lab/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9
10 changes: 10 additions & 0 deletions bip-0374/secp256k1lab/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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).

## [1.0.0] - 2025-03-31

Initial release.
23 changes: 23 additions & 0 deletions bip-0374/secp256k1lab/COPYING
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
The MIT License (MIT)

Copyright (c) 2009-2024 The Bitcoin Core developers
Copyright (c) 2009-2024 Bitcoin Developers
Copyright (c) 2025- The secp256k1lab Developers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
13 changes: 13 additions & 0 deletions bip-0374/secp256k1lab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
secp256k1lab
============

![Dependencies: None](https://img.shields.io/badge/dependencies-none-success)

An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes written in Python, intended for prototyping, experimentation and education.

Features:
* Low-level secp256k1 field and group arithmetic.
* Schnorr signing/verification and key generation according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
* ECDH key exchange.

WARNING: The code in this library is slow and trivially vulnerable to side channel attacks.
34 changes: 34 additions & 0 deletions bip-0374/secp256k1lab/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[project]
name = "secp256k1lab"
version = "1.0.0"
description = "An INSECURE implementation of the secp256k1 elliptic curve and related cryptographic schemes, intended for prototyping, experimentation and education"
readme = "README.md"
authors = [
{ name = "Pieter Wuille", email = "pieter@wuille.net" },
{ name = "Tim Ruffing", email = "me@real-or-random.org" },
{ name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
{ name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
]
maintainers = [
{ name = "Tim Ruffing", email = "me@real-or-random.org" },
{ name = "Jonas Nick", email = "jonasd.nick@gmail.com" },
{ name = "Sebastian Falbesoner", email = "sebastian.falbesoner@gmail.com" }
]
requires-python = ">=3.9"
license = "MIT"
license-files = ["COPYING"]
keywords = ["secp256k1", "elliptic curves", "cryptography", "Bitcoin"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Topic :: Security :: Cryptography",
]
dependencies = []

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Empty file.
73 changes: 73 additions & 0 deletions bip-0374/secp256k1lab/src/secp256k1lab/bip340.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# The following functions are based on the BIP 340 reference implementation:
# https://github.com/bitcoin/bips/blob/master/bip-0340/reference.py

from .secp256k1 import FE, GE, G
from .util import int_from_bytes, bytes_from_int, xor_bytes, tagged_hash


def pubkey_gen(seckey: bytes) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= GE.ORDER - 1):
raise ValueError("The secret key must be an integer in the range 1..n-1.")
P = d0 * G
assert not P.infinity
return P.to_bytes_xonly()


def schnorr_sign(
msg: bytes, seckey: bytes, aux_rand: bytes, tag_prefix: str = "BIP0340"
) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= GE.ORDER - 1):
raise ValueError("The secret key must be an integer in the range 1..n-1.")
if len(aux_rand) != 32:
raise ValueError("aux_rand must be 32 bytes instead of %i." % len(aux_rand))
P = d0 * G
assert not P.infinity
d = d0 if P.has_even_y() else GE.ORDER - d0
t = xor_bytes(bytes_from_int(d), tagged_hash(tag_prefix + "/aux", aux_rand))
k0 = (
int_from_bytes(tagged_hash(tag_prefix + "/nonce", t + P.to_bytes_xonly() + msg))
% GE.ORDER
)
if k0 == 0:
raise RuntimeError("Failure. This happens only with negligible probability.")
R = k0 * G
assert not R.infinity
k = k0 if R.has_even_y() else GE.ORDER - k0
e = (
int_from_bytes(
tagged_hash(
tag_prefix + "/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg
)
)
% GE.ORDER
)
sig = R.to_bytes_xonly() + bytes_from_int((k + e * d) % GE.ORDER)
assert schnorr_verify(msg, P.to_bytes_xonly(), sig, tag_prefix=tag_prefix)
return sig


def schnorr_verify(
msg: bytes, pubkey: bytes, sig: bytes, tag_prefix: str = "BIP0340"
) -> bool:
if len(pubkey) != 32:
raise ValueError("The public key must be a 32-byte array.")
if len(sig) != 64:
raise ValueError("The signature must be a 64-byte array.")
try:
P = GE.from_bytes_xonly(pubkey)
except ValueError:
return False
r = int_from_bytes(sig[0:32])
s = int_from_bytes(sig[32:64])
if (r >= FE.SIZE) or (s >= GE.ORDER):
return False
e = (
int_from_bytes(tagged_hash(tag_prefix + "/challenge", sig[0:32] + pubkey + msg))
% GE.ORDER
)
R = s * G - e * P
if R.infinity or (not R.has_even_y()) or (R.x != r):
return False
return True
16 changes: 16 additions & 0 deletions bip-0374/secp256k1lab/src/secp256k1lab/ecdh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import hashlib

from .secp256k1 import GE, Scalar


def ecdh_compressed_in_raw_out(seckey: bytes, pubkey: bytes) -> GE:
"""TODO"""
shared_secret = Scalar.from_bytes_checked(seckey) * GE.from_bytes_compressed(pubkey)
assert not shared_secret.infinity # prime-order group
return shared_secret


def ecdh_libsecp256k1(seckey: bytes, pubkey: bytes) -> bytes:
"""TODO"""
shared_secret = ecdh_compressed_in_raw_out(seckey, pubkey)
return hashlib.sha256(shared_secret.to_bytes_compressed()).digest()
15 changes: 15 additions & 0 deletions bip-0374/secp256k1lab/src/secp256k1lab/keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .secp256k1 import GE, G
from .util import int_from_bytes

# The following function is based on the BIP 327 reference implementation
# https://github.com/bitcoin/bips/blob/master/bip-0327/reference.py


# Return the plain public key corresponding to a given secret key
def pubkey_gen_plain(seckey: bytes) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= GE.ORDER - 1):
raise ValueError("The secret key must be an integer in the range 1..n-1.")
P = d0 * G
assert not P.infinity
return P.to_bytes_compressed()
Empty file.
Loading