Wladimir J. van der Laan
9 years ago
12 changed files with 702 additions and 53 deletions
@ -0,0 +1,272 @@
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env python2 |
||||
|
||||
# |
||||
# Distributed under the MIT/X11 software license, see the accompanying |
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
||||
# |
||||
|
||||
from test_framework.test_framework import ComparisonTestFramework |
||||
from test_framework.util import * |
||||
from test_framework.comptool import TestManager, TestInstance |
||||
from test_framework.mininode import * |
||||
from test_framework.blocktools import * |
||||
import logging |
||||
import copy |
||||
import time |
||||
import numbers |
||||
from test_framework.key import CECKey |
||||
from test_framework.script import CScript, CScriptOp, SignatureHash, SIGHASH_ALL, OP_TRUE |
||||
|
||||
class PreviousSpendableOutput(object): |
||||
def __init__(self, tx = CTransaction(), n = -1): |
||||
self.tx = tx |
||||
self.n = n # the output we're spending |
||||
|
||||
''' |
||||
This reimplements tests from the bitcoinj/FullBlockTestGenerator used |
||||
by the pull-tester. |
||||
|
||||
We use the testing framework in which we expect a particular answer from |
||||
each test. |
||||
''' |
||||
|
||||
class FullBlockTest(ComparisonTestFramework): |
||||
|
||||
''' Can either run this test as 1 node with expected answers, or two and compare them. |
||||
Change the "outcome" variable from each TestInstance object to only do the comparison. ''' |
||||
def __init__(self): |
||||
self.num_nodes = 1 |
||||
self.block_heights = {} |
||||
self.coinbase_key = CECKey() |
||||
self.coinbase_key.set_secretbytes(bytes("horsebattery")) |
||||
self.coinbase_pubkey = self.coinbase_key.get_pubkey() |
||||
self.block_time = int(time.time())+1 |
||||
self.tip = None |
||||
self.blocks = {} |
||||
|
||||
def run_test(self): |
||||
test = TestManager(self, self.options.tmpdir) |
||||
test.add_all_connections(self.nodes) |
||||
NetworkThread().start() # Start up network handling in another thread |
||||
test.run() |
||||
|
||||
def add_transactions_to_block(self, block, tx_list): |
||||
[ tx.rehash() for tx in tx_list ] |
||||
block.vtx.extend(tx_list) |
||||
block.hashMerkleRoot = block.calc_merkle_root() |
||||
block.rehash() |
||||
return block |
||||
|
||||
# Create a block on top of self.tip, and advance self.tip to point to the new block |
||||
# if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend output, |
||||
# and rest will go to fees. |
||||
def next_block(self, number, spend=None, additional_coinbase_value=0, script=None): |
||||
if self.tip == None: |
||||
base_block_hash = self.genesis_hash |
||||
else: |
||||
base_block_hash = self.tip.sha256 |
||||
# First create the coinbase |
||||
height = self.block_heights[base_block_hash] + 1 |
||||
coinbase = create_coinbase(height, self.coinbase_pubkey) |
||||
coinbase.vout[0].nValue += additional_coinbase_value |
||||
if (spend != None): |
||||
coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees |
||||
coinbase.rehash() |
||||
block = create_block(base_block_hash, coinbase, self.block_time) |
||||
if (spend != None): |
||||
tx = CTransaction() |
||||
tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), "", 0xffffffff)) # no signature yet |
||||
# This copies the java comparison tool testing behavior: the first |
||||
# txout has a garbage scriptPubKey, "to make sure we're not |
||||
# pre-verifying too much" (?) |
||||
tx.vout.append(CTxOut(0, CScript([random.randint(0,255), height & 255]))) |
||||
if script == None: |
||||
tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) |
||||
else: |
||||
tx.vout.append(CTxOut(1, script)) |
||||
# Now sign it if necessary |
||||
scriptSig = "" |
||||
scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) |
||||
if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend |
||||
scriptSig = CScript([OP_TRUE]) |
||||
else: |
||||
# We have to actually sign it |
||||
(sighash, err) = SignatureHash(spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL) |
||||
scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) |
||||
tx.vin[0].scriptSig = scriptSig |
||||
# Now add the transaction to the block |
||||
block = self.add_transactions_to_block(block, [tx]) |
||||
block.solve() |
||||
self.tip = block |
||||
self.block_heights[block.sha256] = height |
||||
self.block_time += 1 |
||||
assert number not in self.blocks |
||||
self.blocks[number] = block |
||||
return block |
||||
|
||||
def get_tests(self): |
||||
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) |
||||
self.block_heights[self.genesis_hash] = 0 |
||||
spendable_outputs = [] |
||||
|
||||
# save the current tip so it can be spent by a later block |
||||
def save_spendable_output(): |
||||
spendable_outputs.append(self.tip) |
||||
|
||||
# get an output that we previous marked as spendable |
||||
def get_spendable_output(): |
||||
return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) |
||||
|
||||
# returns a test case that asserts that the current tip was accepted |
||||
def accepted(): |
||||
return TestInstance([[self.tip, True]]) |
||||
|
||||
# returns a test case that asserts that the current tip was rejected |
||||
def rejected(): |
||||
return TestInstance([[self.tip, False]]) |
||||
|
||||
# move the tip back to a previous block |
||||
def tip(number): |
||||
self.tip = self.blocks[number] |
||||
|
||||
# creates a new block and advances the tip to that block |
||||
block = self.next_block |
||||
|
||||
|
||||
# Create a new block |
||||
block(0) |
||||
save_spendable_output() |
||||
yield accepted() |
||||
|
||||
|
||||
# Now we need that block to mature so we can spend the coinbase. |
||||
test = TestInstance(sync_every_block=False) |
||||
for i in range(100): |
||||
block(1000 + i) |
||||
test.blocks_and_transactions.append([self.tip, True]) |
||||
save_spendable_output() |
||||
yield test |
||||
|
||||
|
||||
# Start by bulding a couple of blocks on top (which output is spent is in parentheses): |
||||
# genesis -> b1 (0) -> b2 (1) |
||||
out0 = get_spendable_output() |
||||
block(1, spend=out0) |
||||
save_spendable_output() |
||||
yield accepted() |
||||
|
||||
out1 = get_spendable_output() |
||||
block(2, spend=out1) |
||||
# Inv again, then deliver twice (shouldn't break anything). |
||||
yield accepted() |
||||
|
||||
|
||||
# so fork like this: |
||||
# |
||||
# genesis -> b1 (0) -> b2 (1) |
||||
# \-> b3 (1) |
||||
# |
||||
# Nothing should happen at this point. We saw b2 first so it takes priority. |
||||
tip(1) |
||||
block(3, spend=out1) |
||||
# Deliver twice (should still not break anything) |
||||
yield rejected() |
||||
|
||||
|
||||
# Now we add another block to make the alternative chain longer. |
||||
# |
||||
# genesis -> b1 (0) -> b2 (1) |
||||
# \-> b3 (1) -> b4 (2) |
||||
out2 = get_spendable_output() |
||||
block(4, spend=out2) |
||||
yield accepted() |
||||
|
||||
|
||||
# ... and back to the first chain. |
||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) |
||||
# \-> b3 (1) -> b4 (2) |
||||
tip(2) |
||||
block(5, spend=out2) |
||||
save_spendable_output() |
||||
yield rejected() |
||||
|
||||
out3 = get_spendable_output() |
||||
block(6, spend=out3) |
||||
yield accepted() |
||||
|
||||
|
||||
# Try to create a fork that double-spends |
||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) |
||||
# \-> b7 (2) -> b8 (4) |
||||
# \-> b3 (1) -> b4 (2) |
||||
tip(5) |
||||
block(7, spend=out2) |
||||
yield rejected() |
||||
|
||||
out4 = get_spendable_output() |
||||
block(8, spend=out4) |
||||
yield rejected() |
||||
|
||||
|
||||
# Try to create a block that has too much fee |
||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) |
||||
# \-> b9 (4) |
||||
# \-> b3 (1) -> b4 (2) |
||||
tip(6) |
||||
block(9, spend=out4, additional_coinbase_value=1) |
||||
yield rejected() |
||||
|
||||
|
||||
# Create a fork that ends in a block with too much fee (the one that causes the reorg) |
||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) |
||||
# \-> b10 (3) -> b11 (4) |
||||
# \-> b3 (1) -> b4 (2) |
||||
tip(5) |
||||
block(10, spend=out3) |
||||
yield rejected() |
||||
|
||||
block(11, spend=out4, additional_coinbase_value=1) |
||||
yield rejected() |
||||
|
||||
|
||||
# Try again, but with a valid fork first |
||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) |
||||
# \-> b12 (3) -> b13 (4) -> b14 (5) |
||||
# (b12 added last) |
||||
# \-> b3 (1) -> b4 (2) |
||||
tip(5) |
||||
b12 = block(12, spend=out3) |
||||
save_spendable_output() |
||||
#yield TestInstance([[b12, False]]) |
||||
b13 = block(13, spend=out4) |
||||
# Deliver the block header for b12, and the block b13. |
||||
# b13 should be accepted but the tip won't advance until b12 is delivered. |
||||
yield TestInstance([[CBlockHeader(b12), None], [b13, False]]) |
||||
|
||||
save_spendable_output() |
||||
out5 = get_spendable_output() |
||||
# b14 is invalid, but the node won't know that until it tries to connect |
||||
# Tip still can't advance because b12 is missing |
||||
block(14, spend=out5, additional_coinbase_value=1) |
||||
yield rejected() |
||||
|
||||
yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13. |
||||
|
||||
|
||||
# Test that a block with a lot of checksigs is okay |
||||
lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50 - 1)) |
||||
tip(13) |
||||
block(15, spend=out5, script=lots_of_checksigs) |
||||
yield accepted() |
||||
|
||||
|
||||
# Test that a block with too many checksigs is rejected |
||||
out6 = get_spendable_output() |
||||
too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50)) |
||||
block(16, spend=out6, script=too_many_checksigs) |
||||
yield rejected() |
||||
|
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
FullBlockTest().main() |
@ -0,0 +1,215 @@
@@ -0,0 +1,215 @@
|
||||
# Copyright (c) 2011 Sam Rushing |
||||
# |
||||
# key.py - OpenSSL wrapper |
||||
# |
||||
# This file is modified from python-bitcoinlib. |
||||
# |
||||
|
||||
"""ECC secp256k1 crypto routines |
||||
|
||||
WARNING: This module does not mlock() secrets; your private keys may end up on |
||||
disk in swap! Use with caution! |
||||
""" |
||||
|
||||
import ctypes |
||||
import ctypes.util |
||||
import hashlib |
||||
import sys |
||||
|
||||
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_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 |
||||
|
||||
# Thx to Sam Devlin for the ctypes magic 64-bit fix. |
||||
def _check_result(val, func, args): |
||||
if val == 0: |
||||
raise ValueError |
||||
else: |
||||
return ctypes.c_void_p (val) |
||||
|
||||
ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p |
||||
ssl.EC_KEY_new_by_curve_name.errcheck = _check_result |
||||
|
||||
class CECKey(object): |
||||
"""Wrapper around OpenSSL's EC_KEY""" |
||||
|
||||
POINT_CONVERSION_COMPRESSED = 2 |
||||
POINT_CONVERSION_UNCOMPRESSED = 4 |
||||
|
||||
def __init__(self): |
||||
self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1) |
||||
|
||||
def __del__(self): |
||||
if ssl: |
||||
ssl.EC_KEY_free(self.k) |
||||
self.k = None |
||||
|
||||
def set_secretbytes(self, secret): |
||||
priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new()) |
||||
group = ssl.EC_KEY_get0_group(self.k) |
||||
pub_key = ssl.EC_POINT_new(group) |
||||
ctx = ssl.BN_CTX_new() |
||||
if not ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx): |
||||
raise ValueError("Could not derive public key from the supplied secret.") |
||||
ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx) |
||||
ssl.EC_KEY_set_private_key(self.k, priv_key) |
||||
ssl.EC_KEY_set_public_key(self.k, pub_key) |
||||
ssl.EC_POINT_free(pub_key) |
||||
ssl.BN_CTX_free(ctx) |
||||
return self.k |
||||
|
||||
def set_privkey(self, key): |
||||
self.mb = ctypes.create_string_buffer(key) |
||||
return ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) |
||||
|
||||
def set_pubkey(self, key): |
||||
self.mb = ctypes.create_string_buffer(key) |
||||
return ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) |
||||
|
||||
def get_privkey(self): |
||||
size = ssl.i2d_ECPrivateKey(self.k, 0) |
||||
mb_pri = ctypes.create_string_buffer(size) |
||||
ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri))) |
||||
return mb_pri.raw |
||||
|
||||
def get_pubkey(self): |
||||
size = ssl.i2o_ECPublicKey(self.k, 0) |
||||
mb = ctypes.create_string_buffer(size) |
||||
ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb))) |
||||
return mb.raw |
||||
|
||||
def get_raw_ecdh_key(self, other_pubkey): |
||||
ecdh_keybuffer = ctypes.create_string_buffer(32) |
||||
r = ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32, |
||||
ssl.EC_KEY_get0_public_key(other_pubkey.k), |
||||
self.k, 0) |
||||
if r != 32: |
||||
raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed') |
||||
return ecdh_keybuffer.raw |
||||
|
||||
def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()): |
||||
# FIXME: be warned it's not clear what the kdf should be as a default |
||||
r = self.get_raw_ecdh_key(other_pubkey) |
||||
return kdf(r) |
||||
|
||||
def sign(self, hash): |
||||
# FIXME: need unit tests for below cases |
||||
if not isinstance(hash, bytes): |
||||
raise TypeError('Hash must be bytes instance; got %r' % hash.__class__) |
||||
if len(hash) != 32: |
||||
raise ValueError('Hash must be exactly 32 bytes long') |
||||
|
||||
sig_size0 = ctypes.c_uint32() |
||||
sig_size0.value = ssl.ECDSA_size(self.k) |
||||
mb_sig = ctypes.create_string_buffer(sig_size0.value) |
||||
result = ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k) |
||||
assert 1 == result |
||||
return mb_sig.raw[:sig_size0.value] |
||||
|
||||
def verify(self, hash, sig): |
||||
"""Verify a DER signature""" |
||||
return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1 |
||||
|
||||
def set_compressed(self, compressed): |
||||
if compressed: |
||||
form = self.POINT_CONVERSION_COMPRESSED |
||||
else: |
||||
form = self.POINT_CONVERSION_UNCOMPRESSED |
||||
ssl.EC_KEY_set_conv_form(self.k, form) |
||||
|
||||
|
||||
class CPubKey(bytes): |
||||
"""An encapsulated public key |
||||
|
||||
Attributes: |
||||
|
||||
is_valid - Corresponds to CPubKey.IsValid() |
||||
is_fullyvalid - Corresponds to CPubKey.IsFullyValid() |
||||
is_compressed - Corresponds to CPubKey.IsCompressed() |
||||
""" |
||||
|
||||
def __new__(cls, buf, _cec_key=None): |
||||
self = super(CPubKey, cls).__new__(cls, buf) |
||||
if _cec_key is None: |
||||
_cec_key = CECKey() |
||||
self._cec_key = _cec_key |
||||
self.is_fullyvalid = _cec_key.set_pubkey(self) != 0 |
||||
return self |
||||
|
||||
@property |
||||
def is_valid(self): |
||||
return len(self) > 0 |
||||
|
||||
@property |
||||
def is_compressed(self): |
||||
return len(self) == 33 |
||||
|
||||
def verify(self, hash, sig): |
||||
return self._cec_key.verify(hash, sig) |
||||
|
||||
def __str__(self): |
||||
return repr(self) |
||||
|
||||
def __repr__(self): |
||||
# Always have represent as b'<secret>' so test cases don't have to |
||||
# change for py2/3 |
||||
if sys.version > '3': |
||||
return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) |
||||
else: |
||||
return '%s(b%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) |
||||
|
Loading…
Reference in new issue