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): def default_signature(ctx):
"""Default expression for "signature": BIP340 signature or ECDSA signature depending on mode.""" """Default expression for "signature": BIP340 signature or ECDSA signature depending on mode."""
sighash = get(ctx, "sighash") sighash = get(ctx, "sighash")
deterministic = get(ctx, "deterministic")
if get(ctx, "mode") == "taproot": if get(ctx, "mode") == "taproot":
key = get(ctx, "key_tweaked") key = get(ctx, "key_tweaked")
flip_r = get(ctx, "flag_flip_r") flip_r = get(ctx, "flag_flip_r")
flip_p = get(ctx, "flag_flip_p") 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: else:
key = get(ctx, "key") key = get(ctx, "key")
return key.sign_ecdsa(sighash) return key.sign_ecdsa(sighash, rfc6979=deterministic)
def default_hashtype_actual(ctx): def default_hashtype_actual(ctx):
"""Default expression for "hashtype_actual": hashtype, unless mismatching SIGHASH_SINGLE in taproot.""" """Default expression for "hashtype_actual": hashtype, unless mismatching SIGHASH_SINGLE in taproot."""
@ -392,6 +396,8 @@ DEFAULT_CONTEXT = {
"leaf": None, "leaf": None,
# The input arguments to provide to the executed script # The input arguments to provide to the executed script
"inputs": [], "inputs": [],
# Use deterministic signing nonces
"deterministic": False,
# == Parameters to be set before evaluation: == # == Parameters to be set before evaluation: ==
# - mode: what spending style to use ("taproot", "witv0", or "legacy"). # - 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.""" anything but tests."""
import csv import csv
import hashlib import hashlib
import hmac
import os import os
import random import random
import unittest import unittest
@ -326,6 +327,16 @@ def generate_privkey():
"""Generate a valid random 32-byte private key.""" """Generate a valid random 32-byte private key."""
return random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big') 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(): class ECKey():
"""A secp256k1 private key""" """A secp256k1 private key"""
@ -368,15 +379,18 @@ class ECKey():
ret.compressed = self.compressed ret.compressed = self.compressed
return ret 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. """Construct a DER-encoded ECDSA signature with this key.
See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the
ECDSA signer algorithm.""" ECDSA signer algorithm."""
assert(self.valid) assert(self.valid)
z = int.from_bytes(msg, 'big') z = int.from_bytes(msg, 'big')
# Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation) # Note: no RFC6979 by default, but a simple random nonce (some tests rely on distinct transactions for the same operation)
k = random.randrange(1, SECP256K1_ORDER) 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 = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)]))
r = R[0] % SECP256K1_ORDER r = R[0] % SECP256K1_ORDER
s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER