tests: add deterministic signing mode to ECDSA

This does the following:
* Adds a rfc6979 argument to test_framework/key.py's sign_ecdsa to
  select (deterministic) RFC6979-based nonce generation.
* Add a flag in feature_taproot.py's framework called "deterministic".
* Make the Schnorr signing in feature_taproot.py randomized by default,
  reverting to the old deterministic (aux_rnd=0x0000...00) behavior
  if the deterministic context flag is set.
* Make the ECDSA signing in feature_taproot.py use RFC6979-based nonces
  when the deterministic context flag is set (keeping the old randomized
  behavior otherwise).
This commit is contained in:
Pieter Wuille 2021-10-27 15:46:03 -04:00
parent c98c53f20c
commit ca83ffc2ea
2 changed files with 25 additions and 5 deletions

View file

@ -253,14 +253,18 @@ def default_key_tweaked(ctx):
def default_signature(ctx):
"""Default expression for "signature": BIP340 signature or ECDSA signature depending on mode."""
sighash = get(ctx, "sighash")
deterministic = get(ctx, "deterministic")
if get(ctx, "mode") == "taproot":
key = get(ctx, "key_tweaked")
flip_r = get(ctx, "flag_flip_r")
flip_p = get(ctx, "flag_flip_p")
return sign_schnorr(key, sighash, flip_r=flip_r, flip_p=flip_p)
aux = bytes([0] * 32)
if not deterministic:
aux = random.getrandbits(256).to_bytes(32, 'big')
return sign_schnorr(key, sighash, flip_r=flip_r, flip_p=flip_p, aux=aux)
else:
key = get(ctx, "key")
return key.sign_ecdsa(sighash)
return key.sign_ecdsa(sighash, rfc6979=deterministic)
def default_hashtype_actual(ctx):
"""Default expression for "hashtype_actual": hashtype, unless mismatching SIGHASH_SINGLE in taproot."""
@ -392,6 +396,8 @@ DEFAULT_CONTEXT = {
"leaf": None,
# The input arguments to provide to the executed script
"inputs": [],
# Use deterministic signing nonces
"deterministic": False,
# == Parameters to be set before evaluation: ==
# - mode: what spending style to use ("taproot", "witv0", or "legacy").

View file

@ -8,6 +8,7 @@ keys, and is trivially vulnerable to side channel attacks. Do not use for
anything but tests."""
import csv
import hashlib
import hmac
import os
import random
import unittest
@ -326,6 +327,16 @@ def generate_privkey():
"""Generate a valid random 32-byte private key."""
return random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big')
def rfc6979_nonce(key):
"""Compute signing nonce using RFC6979."""
v = bytes([1] * 32)
k = bytes([0] * 32)
k = hmac.new(k, v + b"\x00" + key, 'sha256').digest()
v = hmac.new(k, v, 'sha256').digest()
k = hmac.new(k, v + b"\x01" + key, 'sha256').digest()
v = hmac.new(k, v, 'sha256').digest()
return hmac.new(k, v, 'sha256').digest()
class ECKey():
"""A secp256k1 private key"""
@ -368,15 +379,18 @@ class ECKey():
ret.compressed = self.compressed
return ret
def sign_ecdsa(self, msg, low_s=True):
def sign_ecdsa(self, msg, low_s=True, rfc6979=False):
"""Construct a DER-encoded ECDSA signature with this key.
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
ECDSA signer algorithm."""
assert(self.valid)
z = int.from_bytes(msg, 'big')
# Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation)
k = random.randrange(1, SECP256K1_ORDER)
# Note: no RFC6979 by default, but a simple random nonce (some tests rely on distinct transactions for the same operation)
if rfc6979:
k = int.from_bytes(rfc6979_nonce(self.secret.to_bytes(32, 'big') + msg), 'big')
else:
k = random.randrange(1, SECP256K1_ORDER)
R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)]))
r = R[0] % SECP256K1_ORDER
s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER