Skip to content
Draft
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
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM python:3.12-slim

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
git \
ca-certificates \
autoconf \
automake \
libtool \
pkg-config \
&& rm -rf /var/lib/apt/lists/*

# Set workdir
WORKDIR /app

# Copy project files
COPY . /app

# Install Python dependencies (including dev)
RUN pip install --upgrade pip && pip install .[dev]

# Make run_tests.sh executable
RUN chmod +x /app/run_tests.sh

# Entrypoint
ENTRYPOINT ["/bin/bash"]
34 changes: 34 additions & 0 deletions run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/sh
set -e

if [ "$BUILD_SECP256K1_FROM_SOURCE" = "1" ]; then
echo "Cloning and building secp256k1 from https://github.com/bitcoin-core/secp256k1 ..."
Comment on lines +1 to +5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could join your approach with the one in #98

git clone --branch v0.7.0 --depth 1 https://github.com/bitcoin-core/secp256k1.git /tmp/secp256k1
cd /tmp/secp256k1
./autogen.sh
./configure --enable-module-ecdh --enable-module-recovery --enable-module-schnorrsig
make
make install
ldconfig
cd /app
pip install -e .
if [ "$SEEDSIGNER" = "1" ]; then
cd /
apt-get update && apt-get install -y libzbar0
git clone https://github.com/kdmukai/seedsigner.git
cd seedsigner
git checkout exhaustive_psbtparser_tests
git submodule init
git submodule update
pip install -r requirements.txt
pip install -e .
else
echo "Using system or prebuilt secp256k1."
fi


else
echo "Using system or prebuilt secp256k1."
fi

pytest -v
70 changes: 50 additions & 20 deletions src/embit/util/ctypes_secp256k1.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@
create_string_buffer,
CFUNCTYPE,
POINTER,
Structure,
)

class secp256k1_schnorrsig_extraparams(Structure):
_fields_ = [
("magic", c_char * 4),
("noncefp", c_void_p), # secp256k1_nonce_function_hardened is a function pointer
("ndata", c_void_p),
]

_lock = threading.Lock()


Expand Down Expand Up @@ -99,17 +107,17 @@ def _init(flags=(CONTEXT_SIGN | CONTEXT_VERIFY)):
secp256k1.secp256k1_ec_seckey_verify.argtypes = [c_void_p, c_char_p]
secp256k1.secp256k1_ec_seckey_verify.restype = c_int

secp256k1.secp256k1_ec_privkey_negate.argtypes = [c_void_p, c_char_p]
secp256k1.secp256k1_ec_privkey_negate.restype = c_int
secp256k1.secp256k1_ec_seckey_negate.argtypes = [c_void_p, c_char_p]
secp256k1.secp256k1_ec_seckey_negate.restype = c_int

secp256k1.secp256k1_ec_pubkey_negate.argtypes = [c_void_p, c_char_p]
secp256k1.secp256k1_ec_pubkey_negate.restype = c_int

secp256k1.secp256k1_ec_privkey_tweak_add.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ec_privkey_tweak_add.restype = c_int
secp256k1.secp256k1_ec_seckey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ec_seckey_tweak_mul.restype = c_int

secp256k1.secp256k1_ec_privkey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ec_privkey_tweak_mul.restype = c_int
secp256k1.secp256k1_ec_seckey_tweak_mul.argtypes = [c_void_p, c_char_p, c_char_p]
secp256k1.secp256k1_ec_seckey_tweak_mul.restype = c_int

secp256k1.secp256k1_ec_pubkey_create.argtypes = [c_void_p, c_void_p, c_char_p]
secp256k1.secp256k1_ec_pubkey_create.restype = c_int
Expand Down Expand Up @@ -211,19 +219,29 @@ def _init(flags=(CONTEXT_SIGN | CONTEXT_VERIFY)):
c_void_p, # ctx
c_char_p, # sig
c_char_p, # msg
c_size_t, # msglen
c_char_p, # pubkey
]
secp256k1.secp256k1_schnorrsig_verify.restype = c_int

secp256k1.secp256k1_schnorrsig_sign.argtypes = [
secp256k1.secp256k1_schnorrsig_sign32.argtypes = [
c_void_p, # ctx
c_char_p, # sig
c_char_p, # msg
c_char_p, # keypair
c_char_p, # aux_rand
]
secp256k1.secp256k1_schnorrsig_sign32.restype = c_int

secp256k1.secp256k1_schnorrsig_sign_custom.argtypes = [
c_void_p, # ctx
c_char_p, # sig
c_char_p, # msg
c_size_t, # msglen
c_char_p, # keypair
c_void_p, # nonce_function
c_char_p, # extra data
POINTER(secp256k1_schnorrsig_extraparams), # pointer to extraparams (can be NULL)
]
secp256k1.secp256k1_schnorrsig_sign.restype = c_int
secp256k1.secp256k1_schnorrsig_sign_custom.restype = c_int

secp256k1.secp256k1_keypair_create.argtypes = [
c_void_p, # ctx
Expand Down Expand Up @@ -624,7 +642,7 @@ def ec_privkey_negate(secret, context=_secp.ctx):
if len(secret) != 32:
raise ValueError("Secret should be 32 bytes long")
b = _copy(secret)
_secp.secp256k1_ec_privkey_negate(context, b)
_secp.secp256k1_ec_seckey_negate(context, b)
return b


Expand All @@ -644,7 +662,7 @@ def ec_privkey_tweak_add(secret, tweak, context=_secp.ctx):
if len(secret) != 32 or len(tweak) != 32:
raise ValueError("Secret and tweak should both be 32 bytes long")
t = _copy(tweak)
if _secp.secp256k1_ec_privkey_tweak_add(context, secret, tweak) == 0:
if _secp.secp256k1_ec_seckey_tweak_add(context, secret, tweak) == 0:
raise ValueError("Failed to tweak the secret")
return None

Expand All @@ -668,7 +686,7 @@ def ec_privkey_add(secret, tweak, context=_secp.ctx):
# ugly copy that works in mpy and py
s = _copy(secret)
t = _copy(tweak)
if _secp.secp256k1_ec_privkey_tweak_add(context, s, t) == 0:
if _secp.secp256k1_ec_seckey_tweak_add(context, s, t) == 0:
raise ValueError("Failed to tweak the secret")
return s

Expand All @@ -689,7 +707,7 @@ def ec_pubkey_add(pub, tweak, context=_secp.ctx):
def ec_privkey_tweak_mul(secret, tweak, context=_secp.ctx):
if len(secret) != 32 or len(tweak) != 32:
raise ValueError("Secret and tweak should both be 32 bytes long")
if _secp.secp256k1_ec_privkey_tweak_mul(context, secret, tweak) == 0:
if _secp.secp256k1_ec_seckey_tweak_mul(context, secret, tweak) == 0:
raise ValueError("Failed to tweak the secret")


Expand Down Expand Up @@ -764,7 +782,7 @@ def schnorrsig_verify(sig, msg, pubkey, context=_secp.ctx):
assert len(sig) == 64
assert len(msg) == 32
assert len(pubkey) == 64
res = _secp.secp256k1_schnorrsig_verify(context, sig, msg, pubkey)
res = _secp.secp256k1_schnorrsig_verify(context, sig, msg, len(msg), pubkey)
return bool(res)


Expand All @@ -779,22 +797,34 @@ def keypair_create(secret, context=_secp.ctx):


# not @locked because it uses keypair_create inside
def schnorrsig_sign(
msg, keypair, nonce_function=None, extra_data=None, context=_secp.ctx
):
def schnorrsig_sign_32(msg, keypair, nonce_function=None, aux_rand=None, context=_secp.ctx):
assert len(msg) == 32
if len(keypair) == 32:
keypair = keypair_create(keypair, context=context)
with _lock:
assert len(keypair) == 96
sig = bytes(64)
r = _secp.secp256k1_schnorrsig_sign(
context, sig, msg, keypair, nonce_function, extra_data
r = _secp.secp256k1_schnorrsig_sign32(
context, sig, msg, keypair, aux_rand
)
if r == 0:
raise ValueError("Failed to sign")
return sig

# not @locked because it uses keypair_create inside
def schnorrsig_sign(msg, keypair, nonce_function=None, aux_rand=None, context=_secp.ctx):
assert len(msg) == 32
if len(keypair) == 32:
keypair = keypair_create(keypair, context=context)
with _lock:
assert len(keypair) == 96
sig = bytes(64)
r = _secp.secp256k1_schnorrsig_sign_custom(
context, sig, msg, len(msg), keypair, None
)
if r == 0:
raise ValueError("Failed to sign")
return sig

# recoverable
@locked
Expand Down
51 changes: 17 additions & 34 deletions src/embit/util/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,45 +553,28 @@ def verify_schnorr(key, sig, msg):
return True


def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
def sign_schnorr(key, msg, aux=None):
"""Create a Schnorr signature (see BIP 340)."""

assert len(key) == 32
assert len(msg) == 32
if aux is not None:
assert len(aux) == 32

sec = int.from_bytes(key, "big")
if sec == 0 or sec >= SECP256K1_ORDER:
d0 = int.from_bytes(key, "big")
if not (1 <= d0 <= SECP256K1_ORDER - 1):
return None
P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)]))
if SECP256K1.has_even_y(P) == flip_p:
sec = SECP256K1_ORDER - sec
if aux is not None:
t = (sec ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), "big")).to_bytes(
32, "big"
)
else:
t = sec.to_bytes(32, "big")
kp = (
int.from_bytes(
TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, "big") + msg), "big"
)
% SECP256K1_ORDER
)
assert kp != 0
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)]))
k = kp if SECP256K1.has_even_y(R) != flip_r else SECP256K1_ORDER - kp
e = (
int.from_bytes(
TaggedHash(
"BIP0340/challenge",
R[0].to_bytes(32, "big") + P[0].to_bytes(32, "big") + msg,
),
"big",
)
% SECP256K1_ORDER
)
return R[0].to_bytes(32, "big") + ((k + e * sec) % SECP256K1_ORDER).to_bytes(
32, "big"
)
if aux is None:
aux = b'\x00' * 32
P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, d0)]))
assert P is not None
d = d0 if SECP256K1.has_even_y(P) else SECP256K1_ORDER - d0
t = xor_bytes(d.to_bytes(32, "big"), TaggedHash("BIP0340/aux", aux))
k0 = int.from_bytes(TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, "big") + msg), "big") % SECP256K1_ORDER
if k0 == 0:
return None
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k0)]))
assert R is not None
k = SECP256K1_ORDER - k0 if not SECP256K1.has_even_y(R) else k0
e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, "big") + P[0].to_bytes(32, "big") + msg), "big") % SECP256K1_ORDER
return R[0].to_bytes(32, "big") + ((k + e * d) % SECP256K1_ORDER).to_bytes(32, "big")
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.