Commit cd7dbbc0 authored by Calin Culianu's avatar Calin Culianu
Browse files

Merge branch 'backport/D5965' into 'master'

[backport] Pure python EC

See merge request !423
parents 16bab221 7025e91e
Pipeline #166151450 passed with stages
in 49 minutes and 10 seconds
......@@ -16,7 +16,7 @@ from test_framework.blocktools import (
create_tx_with_script,
)
from test_framework.cdefs import MAX_STANDARD_TX_SIGOPS
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
COutPoint,
CTransaction,
......@@ -65,9 +65,10 @@ class FullBlockTest(BitcoinTestFramework):
self.num_nodes = 1
self.setup_clean_chain = True
self.block_heights = {}
self.coinbase_key = CECKey()
self.coinbase_key.set_secretbytes(b"horsebattery")
self.coinbase_pubkey = self.coinbase_key.get_pubkey()
self.coinbase_key = ECKey()
# The test expects uncompressed keys
self.coinbase_key.generate(compressed=False)
self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes()
self.tip = None
self.blocks = {}
self.extra_args = [
......@@ -101,7 +102,7 @@ class FullBlockTest(BitcoinTestFramework):
sighash = SignatureHashForkId(
spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue)
tx.vin[0].scriptSig = CScript(
[self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])
[self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])
def create_and_sign_transaction(
self, spend_tx, n, value, script=CScript([OP_TRUE])):
......@@ -223,7 +224,7 @@ class FullBlockTest(BitcoinTestFramework):
# Sign the transaction using the redeem script
sighash = SignatureHashForkId(
redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx_to_spend.vout[0].nValue)
sig = self.coinbase_key.sign(
sig = self.coinbase_key.sign_ecdsa(
sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
assert len(
......
......@@ -25,7 +25,7 @@ from test_framework.blocktools import (
create_tx_with_script,
make_conform_to_ctor,
)
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
COIN,
COutPoint,
......@@ -72,9 +72,9 @@ def create_fund_and_activation_specific_spending_tx(spend, pre_fork_only):
# create transactions that are only valid before or after the fork.
# Generate a key pair to test
private_key = CECKey()
private_key.set_secretbytes(b"replayprotection")
public_key = private_key.get_pubkey()
private_key = ECKey()
private_key.generate()
public_key = private_key.get_pubkey().get_bytes()
# Fund transaction
script = CScript([public_key, OP_CHECKSIG])
......@@ -94,7 +94,7 @@ def create_fund_and_activation_specific_spending_tx(spend, pre_fork_only):
sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID
sighash = SignatureHashForkId(
script, txspend, 0, sighashtype, 50 * COIN)
sig = private_key.sign(sighash) + \
sig = private_key.sign_ecdsa(sighash) + \
bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
txspend.vin[0].scriptSig = CScript([sig])
txspend.rehash()
......
......@@ -26,7 +26,7 @@ from test_framework.cdefs import (
MAX_TX_SIGOPS_COUNT,
ONE_MEGABYTE,
)
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
COutPoint,
CTransaction,
......@@ -346,9 +346,9 @@ class FullBlockTest(BitcoinTestFramework):
tip(26)
# Generate a key pair to test P2SH sigops count
private_key = CECKey()
private_key.set_secretbytes(b"fatstacks")
public_key = private_key.get_pubkey()
private_key = ECKey()
private_key.generate()
public_key = private_key.get_pubkey().get_bytes()
# P2SH
# Build the redeem script, hash it, use hash to create the p2sh script
......@@ -375,7 +375,7 @@ class FullBlockTest(BitcoinTestFramework):
# Sign the transaction using the redeem script
sighash = SignatureHashForkId(
redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue)
sig = private_key.sign(sighash) + \
sig = private_key.sign_ecdsa(sighash) + \
bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script])
spent_p2sh_tx.rehash()
......
......@@ -17,7 +17,7 @@ from test_framework.blocktools import (
create_tx_with_script,
make_conform_to_ctor,
)
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
COIN,
COutPoint,
......@@ -146,9 +146,9 @@ class ReplayProtectionTest(BitcoinTestFramework):
out.append(get_spendable_output())
# Generate a key pair to test P2SH sigops count
private_key = CECKey()
private_key.set_secretbytes(b"replayprotection")
public_key = private_key.get_pubkey()
private_key = ECKey()
private_key.generate()
public_key = private_key.get_pubkey().get_bytes()
# This is a little handier to use than the version in blocktools.py
def create_fund_and_spend_tx(spend, forkvalue=0):
......@@ -167,7 +167,7 @@ class ReplayProtectionTest(BitcoinTestFramework):
sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID
sighash = SignatureHashForkId(
script, txspend, 0, sighashtype, 50 * COIN - 1000)
sig = private_key.sign(sighash) + \
sig = private_key.sign_ecdsa(sighash) + \
bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
txspend.vin[0].scriptSig = CScript([sig])
txspend.rehash()
......
......@@ -16,7 +16,7 @@ from test_framework.blocktools import (
create_tx_with_script,
make_conform_to_ctor,
)
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
CBlock,
COutPoint,
......@@ -153,10 +153,10 @@ class SchnorrTest(BitcoinTestFramework):
# Generate a key pair
privkeybytes = b"Schnorr!" * 4
private_key = CECKey()
private_key.set_secretbytes(privkeybytes)
private_key = ECKey()
private_key.set(privkeybytes, True)
# get uncompressed public key serialization
public_key = private_key.get_pubkey()
public_key = private_key.get_pubkey().get_bytes()
def create_fund_and_spend_tx(multi=False, sig='schnorr'):
spendfrom = spendable_outputs.pop()
......@@ -188,7 +188,7 @@ class SchnorrTest(BitcoinTestFramework):
if sig == 'schnorr':
txsig = schnorr.sign(privkeybytes, sighash) + hashbyte
elif sig == 'ecdsa':
txsig = private_key.sign(sighash) + hashbyte
txsig = private_key.sign_ecdsa(sighash) + hashbyte
elif isinstance(sig, bytes):
txsig = sig + hashbyte
if multi:
......
......@@ -19,7 +19,7 @@ from test_framework.blocktools import (
create_tx_with_script,
make_conform_to_ctor,
)
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
CBlock,
COutPoint,
......@@ -156,10 +156,10 @@ class SchnorrMultisigTest(BitcoinTestFramework):
# Generate a key pair
privkeybytes = b"Schnorr!" * 4
private_key = CECKey()
private_key.set_secretbytes(privkeybytes)
private_key = ECKey()
private_key.set(privkeybytes, True)
# get uncompressed public key serialization
public_key = private_key.get_pubkey()
public_key = private_key.get_pubkey().get_bytes()
def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'):
spendfrom = spendable_outputs.pop()
......@@ -188,7 +188,7 @@ class SchnorrMultisigTest(BitcoinTestFramework):
if sigtype == 'schnorr':
txsig = schnorr.sign(privkeybytes, sighash) + hashbyte
elif sigtype == 'ecdsa':
txsig = private_key.sign(sighash) + hashbyte
txsig = private_key.sign_ecdsa(sighash) + hashbyte
txspend.vin[0].scriptSig = CScript([dummy, txsig])
txspend.rehash()
......
......@@ -32,7 +32,7 @@ Start three nodes:
import time
from test_framework.blocktools import (create_block, create_coinbase)
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
CBlockHeader,
COutPoint,
......@@ -111,9 +111,9 @@ class AssumeValidTest(BitcoinTestFramework):
self.blocks = []
# Get a pubkey for the coinbase TXO
coinbase_key = CECKey()
coinbase_key.set_secretbytes(b"horsebattery")
coinbase_pubkey = coinbase_key.get_pubkey()
coinbase_key = ECKey()
coinbase_key.generate()
coinbase_pubkey = coinbase_key.get_pubkey().get_bytes()
# Create the first block with a coinbase output to our key
height = 1
......
......@@ -15,7 +15,7 @@ from test_framework.blocktools import (
make_conform_to_ctor,
)
from test_framework.cdefs import LEGACY_MAX_BLOCK_SIZE
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
CBlock,
COIN,
......@@ -80,9 +80,9 @@ class FullBlockTest(BitcoinTestFramework):
self.bootstrap_p2p() # Add one p2p connection to the node
self.block_heights = {}
self.coinbase_key = CECKey()
self.coinbase_key.set_secretbytes(b"horsebattery")
self.coinbase_pubkey = self.coinbase_key.get_pubkey()
self.coinbase_key = ECKey()
self.coinbase_key.generate()
self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes()
self.tip = None
self.blocks = {}
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
......@@ -1053,7 +1053,7 @@ class FullBlockTest(BitcoinTestFramework):
sighash = SignatureHashForkId(
spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[0].nValue)
tx.vin[0].scriptSig = CScript(
[self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])
[self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])
def create_and_sign_transaction(
self, spend_tx, value, script=CScript([OP_TRUE])):
......
......@@ -18,7 +18,7 @@ from test_framework.blocktools import (
make_conform_to_ctor,
)
from test_framework.cdefs import LEGACY_MAX_BLOCK_SIZE, MAX_BLOCK_SIGOPS_PER_MB
from test_framework.key import CECKey
from test_framework.key import ECKey
from test_framework.messages import (
COutPoint,
CTransaction,
......@@ -61,9 +61,9 @@ class FullBlockSigOpsTest(BitcoinTestFramework):
self.bootstrap_p2p() # Add one p2p connection to the node
self.block_heights = {}
self.coinbase_key = CECKey()
self.coinbase_key.set_secretbytes(b"horsebattery")
self.coinbase_pubkey = self.coinbase_key.get_pubkey()
self.coinbase_key = ECKey()
self.coinbase_key.generate()
self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes()
self.tip = None
self.blocks = {}
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
......@@ -263,7 +263,7 @@ class FullBlockSigOpsTest(BitcoinTestFramework):
sighash = SignatureHashForkId(
redeem_script, tx, 1, SIGHASH_ALL | SIGHASH_FORKID,
lastAmount)
sig = self.coinbase_key.sign(
sig = self.coinbase_key.sign_ecdsa(
sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))
scriptSig = CScript([sig, redeem_script])
......@@ -420,7 +420,7 @@ class FullBlockSigOpsTest(BitcoinTestFramework):
sighash = SignatureHashForkId(
spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[0].nValue)
tx.vin[0].scriptSig = CScript(
[self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])
[self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))])
def create_and_sign_transaction(
self, spend_tx, value, script=CScript([OP_TRUE])):
......
#!/usr/bin/env python3
# Copyright (c) 2011 Sam Rushing
"""ECC secp256k1 OpenSSL wrapper.
# Copyright (c) 2019 Pieter Wuille
WARNING: This module does not mlock() secrets; your private keys may end up on
disk in swap! Use with caution!
"""Test-only secp256k1 elliptic curve implementation
This file is modified from python-bitcoinlib.
WARNING: This code is slow, uses bad randomness, does not properly protect
keys, and is trivially vulnerable to side channel attacks. Do not use for
anything but tests.
"""
import ctypes
import ctypes.util
import hashlib
import random
ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32')
ssl.BN_new.restype = ctypes.c_void_p
ssl.BN_new.argtypes = []
ssl.BN_free.restype = None
ssl.BN_free.argtypes = [ctypes.c_void_p]
ssl.BN_bin2bn.restype = ctypes.c_void_p
ssl.BN_bin2bn.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p]
ssl.BN_CTX_free.restype = None
ssl.BN_CTX_free.argtypes = [ctypes.c_void_p]
ssl.BN_CTX_new.restype = ctypes.c_void_p
ssl.BN_CTX_new.argtypes = []
ssl.ECDH_compute_key.restype = ctypes.c_int
ssl.ECDH_compute_key.argtypes = [ctypes.c_void_p,
ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
ssl.ECDSA_sign.restype = ctypes.c_int
ssl.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p,
ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
ssl.ECDSA_verify.restype = ctypes.c_int
ssl.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p,
ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
ssl.EC_KEY_free.restype = None
ssl.EC_KEY_free.argtypes = [ctypes.c_void_p]
ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
ssl.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int]
ssl.EC_KEY_get0_group.restype = ctypes.c_void_p
ssl.EC_KEY_get0_group.argtypes = [ctypes.c_void_p]
ssl.EC_KEY_get0_public_key.restype = ctypes.c_void_p
ssl.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p]
ssl.EC_KEY_set_private_key.restype = ctypes.c_int
ssl.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
ssl.EC_KEY_set_conv_form.restype = None
ssl.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p, ctypes.c_int]
ssl.EC_KEY_set_public_key.restype = ctypes.c_int
ssl.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
ssl.i2o_ECPublicKey.restype = ctypes.c_void_p
ssl.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
ssl.EC_POINT_new.restype = ctypes.c_void_p
ssl.EC_POINT_new.argtypes = [ctypes.c_void_p]
ssl.EC_POINT_free.restype = None
ssl.EC_POINT_free.argtypes = [ctypes.c_void_p]
ssl.EC_POINT_mul.restype = ctypes.c_int
ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p,
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
# this specifies the curve used with ECDSA.
NID_secp256k1 = 714 # from openssl/obj_mac.h
def modinv(a, n):
"""Compute the modular inverse of a modulo n
See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers
"""
t1, t2 = 0, 1
r1, r2 = n, a
while r2 != 0:
q = r1 // r2
t1, t2 = t2, t1 - q * t2
r1, r2 = r2, r1 - q * r2
if r1 > 1:
return None
if t1 < 0:
t1 += n
return t1
def jacobi_symbol(n, k):
"""Compute the Jacobi symbol of n modulo k
See http://en.wikipedia.org/wiki/Jacobi_symbol
"""
assert k > 0 and k & 1
n %= k
t = 0
while n != 0:
while n & 1 == 0:
n >>= 1
r = k & 7
t ^= (r == 3 or r == 5)
n, k = k, n
t ^= (n & k & 3 == 3)
n = n % k
if k == 1:
return -1 if t else 1
return 0
def modsqrt(a, p):
"""Compute the square root of a modulo p
For p = 3 mod 4, if a square root exists, it is equal to a**((p+1)/4) mod p.
"""
assert(p % 4 == 3) # Only p = 3 mod 4 is implemented
sqrt = pow(a, (p + 1) // 4, p)
if pow(sqrt, 2, p) == a % p:
return sqrt
return None
class EllipticCurve:
def __init__(self, p, a, b):
"""Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p)."""
self.p = p
self.a = a % p
self.b = b % p
def affine(self, p1):
"""Convert a Jacobian point tuple p1 to affine form, or None if at infinity."""
x1, y1, z1 = p1
if z1 == 0:
return None
inv = modinv(z1, self.p)
inv_2 = (inv**2) % self.p
inv_3 = (inv_2 * inv) % self.p
return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1)
def negate(self, p1):
"""Negate a Jacobian point tuple p1."""
x1, y1, z1 = p1
return (x1, (self.p - y1) % self.p, z1)
def on_curve(self, p1):
"""Determine whether a Jacobian tuple p is on the curve (and not infinity)"""
x1, y1, z1 = p1
z2 = pow(z1, 2, self.p)
z4 = pow(z2, 2, self.p)
return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 *
z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0
def is_x_coord(self, x):
"""Test whether x is a valid X coordinate on the curve."""
x_3 = pow(x, 3, self.p)
return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1
def lift_x(self, x):
"""Given an X coordinate on the curve, return a corresponding affine point."""
x_3 = pow(x, 3, self.p)
v = x_3 + self.a * x + self.b
y = modsqrt(v, self.p)
if y is None:
return None
return (x, y, 1)
def double(self, p1):
"""Double a Jacobian tuple p1"""
x1, y1, z1 = p1
if z1 == 0:
return (0, 1, 0)
y1_2 = (y1**2) % self.p
y1_4 = (y1_2**2) % self.p
x1_2 = (x1**2) % self.p
s = (4 * x1 * y1_2) % self.p
m = 3 * x1_2
if self.a:
m += self.a * pow(z1, 4, self.p)
m = m % self.p
x2 = (m**2 - 2 * s) % self.p
y2 = (m * (s - x2) - 8 * y1_4) % self.p
z2 = (2 * y1 * z1) % self.p
return (x2, y2, z2)
def add_mixed(self, p1, p2):
"""Add a Jacobian tuple p1 and an affine tuple p2"""
x1, y1, z1 = p1
x2, y2, z2 = p2
assert(z2 == 1)
if z1 == 0:
return p2
z1_2 = (z1**2) % self.p
z1_3 = (z1_2 * z1) % self.p
u2 = (x2 * z1_2) % self.p
s2 = (y2 * z1_3) % self.p
if x1 == u2:
if (y1 != s2):
return (0, 1, 0)
return self.double(p1)
h = u2 - x1
r = s2 - y1
h_2 = (h**2) % self.p
h_3 = (h_2 * h) % self.p
u1_h_2 = (x1 * h_2) % self.p
x3 = (r**2 - h_3 - 2 * u1_h_2) % self.p
y3 = (r * (u1_h_2 - x3) - y1 * h_3) % self.p
z3 = (h * z1) % self.p
return (x3, y3, z3)
def add(self, p1, p2):
"""Add two Jacobian tuples p1 and p2"""
x1, y1, z1 = p1
x2, y2, z2 = p2
if z1 == 0:
return p2
if z2 == 0:
return p1
if z1 == 1:
return self.add_mixed(p2, p1)
if z2 == 1:
return self.add_mixed(p1, p2)
z1_2 = (z1**2) % self.p
z1_3 = (z1_2 * z1) % self.p
z2_2 = (z2**2) % self.p
z2_3 = (z2_2 * z2) % self.p
u1 = (x1 * z2_2) % self.p
u2 = (x2 * z1_2) % self.p
s1 = (y1 * z2_3) % self.p
s2 = (y2 * z1_3) % self.p
if u1 == u2:
if (s1 != s2):
return (0, 1, 0)
return self.double(p1)
h = u2 - u1
r = s2 - s1
h_2 = (h**2) % self.p
h_3 = (h_2 * h) % self.p
u1_h_2 = (u1 * h_2) % self.p
x3 = (r**2 - h_3 - 2 * u1_h_2) % self.p
y3 = (r * (u1_h_2 - x3) - s1 * h_3) % self.p
z3 = (h * z1 * z2) % self.p
return (x3, y3, z3)
def mul(self, ps):
"""Compute a (multi) point multiplication
ps is a list of (Jacobian tuple, scalar) pairs.
"""
r = (0, 1, 0)
for i in range(255, -1, -1):
r = self.double(r)
for (p, n) in ps:
if ((n >> i) & 1):
r = self.add(r, p)
return r
SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7)
SECP256K1_G = (
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8,
1)
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2
# Thx to Sam Devlin for the ctypes magic 64-bit fix.
def _check_result(val, func, args):