def test_spend_unvault_tx_two_traders(bitcoind): """ This tests the unvault_tx spending with the signature of the two traders. """ # The stakeholders, the first two are the traders. stk_privkeys = [os.urandom(32) for i in range(4)] stk_pubkeys = [CKey(k).pub for k in stk_privkeys] # The co-signing server, required by the spend tx serv_privkey = os.urandom(32) serv_pubkey = CKey(serv_privkey).pub # Create the vault and unvault transactions amount_vault = 50 * COIN - 500 amount_unvault = amount_vault - 500 txid = send_unvault_tx(bitcoind, stk_privkeys, stk_pubkeys, serv_pubkey, amount_vault, amount_unvault) amount_spend = amount_unvault - 500 # The address to spend to addr = bitcoind.getnewaddress() CTx = create_spend_tx(txid, 0, {addr: amount_spend}) # The first two stakeholders are the traders sigs = sign_spend_tx(CTx, stk_privkeys[:2], stk_pubkeys, serv_pubkey, amount_unvault) # We need the cosigning server sig, too ! sig_serv = sign_spend_tx(CTx, [serv_privkey], stk_pubkeys, serv_pubkey, amount_unvault) # Ok we have all the sigs we need, let's spend it... CTx = form_spend_tx(CTx, stk_pubkeys, serv_pubkey, [*sigs, bytes(0), *sig_serv]) # ... After the relative locktime ! for i in range(5): with pytest.raises(VerifyRejectedError, match="non-BIP68-final"): bitcoind.send_tx(b2x(CTx.serialize())) bitcoind.generate_block(1) bitcoind.send_tx(b2x(CTx.serialize())) assert bitcoind.has_utxo(addr)
def test_emergency_unvault_tx(bitcoind): """This tests the emergency_unvault_tx() function.""" # The stakeholders, the first two are the traders. stk_privkeys = [os.urandom(32) for i in range(4)] stk_pubkeys = [CKey(k).pub for k in stk_privkeys] # The stakeholders emergency keys emer_privkeys = [os.urandom(32) for i in range(4)] emer_pubkeys = [CKey(k).pub for k in emer_privkeys] # The co-signing server, required by the spend tx serv_privkey = CKey(os.urandom(32)) serv_pubkey = serv_privkey.pub # Create the vault and unvault transactions amount_vault = 50 * COIN - 500 amount_unvault = amount_vault - 500 txid = send_unvault_tx(bitcoind, stk_privkeys, stk_pubkeys, serv_pubkey, amount_vault, amount_unvault) amount_emer = amount_unvault - 500 # Actually vout MUST be 0. CTx = create_emer_unvault_tx(txid, 0, emer_pubkeys, amount_emer) sigs = [ sign_emer_unvault_tx(CTx, p, stk_pubkeys, serv_pubkey, amount_unvault) for p in stk_privkeys ] CTx = form_emer_unvault_tx(CTx, sigs, stk_pubkeys, serv_pubkey) bitcoind.send_tx(b2x(CTx.serialize()))
def creates_add_input(bitcoind, tx): """Creates and add an input to a CMutableTransaction, SIGHASH_ALL. :returns: The txid of the first stage fee bumping tx (for convenience) """ # First we get some coins privkey = CKey(os.urandom(32)) scriptPubKey = CScript([OP_0, Hash160(privkey.pub)]) address = CBitcoinAddress.from_scriptPubKey(scriptPubKey) # Let's say we want to increase the fees by 5000 sats amount = 5000 # Bitcoind is nice and will create the first stage transaction first_txid = bitcoind.rpc.sendtoaddress(str(address), amount / COIN) vout_index = get_output_index( bitcoind.rpc.getrawtransaction(first_txid, 1), amount) # === We don't generate a block yet ! === tx.vin.append( CTxIn(COutPoint(lx(first_txid), vout_index), nSequence=0xfffffffe)) # Sign the new input with ALL tx_hash = SignatureHash(address.to_redeemScript(), tx, 1, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append(CTxInWitness(CScriptWitness([sig, privkey.pub]))) return first_txid
def __init__(self): """Uncommon pattern, but a handy one. We setup everything when the wrapper is initialized.""" self.server = Flask(__name__) self.privkey = os.urandom(32) self.pubkey = CKey(self.privkey).pub # List of txids we already signed self.already_signed = [] bitcoin.SelectParams("regtest") self.setup_routes()
def test_spend_creation(vault_factory): """Test that the signature exchange between the traders and cosigner leads to a well-formed spend_tx, and that we can spend to ACKed addresses.""" wallets = vault_factory.get_wallets() trader_A, trader_B = wallets[0], wallets[1] bitcoind = trader_A.bitcoind create_new_vaults(wallets, 1) # Try to spend from the newly created vault vault = trader_A.vaults[0] unvault_amount = vault["unvault_tx"].vout[0].nValue spend_amount = unvault_amount - 50000 # We choose a valid address.. addresses = { random.choice(trader_A.acked_addresses): spend_amount, } # The first trader creates the tx, signs it, pass both the tx and sig to B trader_A.initiate_spend(vault, addresses) # B hands his signature to A sigB = trader_B.accept_spend(vault["txid"], addresses) pubkeyB = CKey(trader_B.vaults[0]["privkey"]).pub # Then A forms the transaction and tells everyone, we can broadcast it. tx, accepted = trader_A.complete_spend(vault, pubkeyB, sigB, addresses) assert accepted bitcoind.broadcast_and_mine( trader_A.get_signed_unvault_tx(vault).serialize().hex()) # At this point we should have remarked the spend, and have removed # the vault. wait_for(lambda: all(len(w.vaults) == 0 for w in wallets)) # Generate 5 blocks for the locktime ! addr = bitcoind.getnewaddress() bitcoind.generatetoaddress(5, addr) bitcoind.broadcast_and_mine(b2x(tx.serialize()))
def test_emergency_txout(bitcoind): """Test mostly the emergency tx locktime""" amount = Decimal("50") - Decimal("500") / Decimal(COIN) privkeys = [CKey(os.urandom(32)) for _ in range(4)] pubkeys = [k.pub for k in privkeys] txo = emergency_txout(pubkeys, COIN * amount) addr = str(CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey)) # This makes a transaction with only one vout txid = bitcoind.pay_to(addr, amount) new_amount = amount - Decimal("500") / Decimal(COIN) addr = bitcoind.getnewaddress() txin = CTxIn(COutPoint(lx(txid), 0), nSequence=4464) txout = CTxOut(new_amount * COIN, CBitcoinAddress(addr).to_scriptPubKey()) tx = CMutableTransaction([txin], [txout], nVersion=2) tx_hash = SignatureHash(emergency_script(pubkeys), tx, 0, SIGHASH_ALL, int(amount * COIN), SIGVERSION_WITNESS_V0) sigs = [k.sign(tx_hash) + bytes([SIGHASH_ALL]) for k in privkeys] witness_script = [bytes(0), *sigs, emergency_script(pubkeys)] tx.wit = CTxWitness([CTxInWitness(CScriptWitness(witness_script))]) # 1 month of locktime bitcoind.generate_block(4464 - 2) with pytest.raises(VerifyRejectedError, match="non-BIP68-final"): bitcoind.send_tx(tx.serialize().hex()) bitcoind.generate_block(1) bitcoind.send_tx(tx.serialize().hex()) assert bitcoind.has_utxo(addr)
def sign_unvault_revault(tx, privkey, pubkeys, pub_server, prev_value, sign_all=False): """Signs a transaction revaulting an unvault transaction. This is the "all stakeholders sign" path of the script, not encumbered by a timelock. This path is used for both the emergency and cancel transactions. These transactions are crucial to preserve our security assumptions (i.e. a revaulting transaction will be confirmed), so stakeholders exchange SINGLE | ANYONECANPAY signatures to allow any of them to increase the feerate by appending an input and an output. :param tx: The unsigned transaction, a CMutableTransaction. :param privkey: (bytes) The private key to sign the transaction with. :param pubkeys: The pubkeys of the stakeholders. :param pub_server: The pubkey of the cosigning server. :param prev_value: The prevout's value in satoshis. :param sign_all: If set to True, sign we SIGHASH_ALL instead. :return: The signatures for the provided privkeys (a list). """ sighash = SIGHASH_ALL if sign_all else SINGLE_ANYONECANPAY tx_hash = SignatureHash(unvault_script(*pubkeys, pub_server), tx, 0, sighash, prev_value, SIGVERSION_WITNESS_V0) return CKey(privkey).sign(tx_hash) + bytes([sighash])
def sign_emergency_vault_tx(tx, privkey, pubkeys, prev_value, sign_all=False): """Signs the transaction which moves a vault's coins to the offline 4of4. This transaction is crucial to preserve our security assumptions (i.e. a revaulting transaction will be confirmed), so stakeholders exchange SINGLE | ANYONECANPAY signatures to allow any of them to increase the feerate by appending an input and an output. :param vault_txid: The id of the transaction funding the vault, as bytes. :param privkey: (bytes) The private key to sign the transaction with. :param pubkeys: A list containing the public key of each stakeholder. :param prev_value: The vault output (previout output) value in satoshis. :param sign_all: If set to True, sign we SIGHASH_ALL instead. :return: A list, one signature per given privkey. """ sighash = SIGHASH_ALL if sign_all else SINGLE_ANYONECANPAY tx_hash = SignatureHash(vault_script(pubkeys), tx, 0, sighash, amount=prev_value, sigversion=SIGVERSION_WITNESS_V0) # A signature per pubkey return CKey(privkey).sign(tx_hash) + bytes([sighash])
def add_input_output(bitcoind, tx): """Add an input and an output to a CMutableTransaction, SIGHASH_ALL.""" # First we get some coins privkey = CKey(os.urandom(32)) scriptPubKey = CScript([OP_0, Hash160(privkey.pub)]) address = CBitcoinAddress.from_scriptPubKey(scriptPubKey) amount = Decimal("50") * Decimal(COIN) - Decimal("500") # This creates a one-output transaction txid = bitcoind.pay_to(str(address), amount / Decimal(COIN)) # We bump the fees by 5000 tx.vout.append(CTxOut(amount - Decimal("5000"), scriptPubKey)) tx.vin.append(CTxIn(COutPoint(lx(txid), 0))) # Sign the new output with ALL tx_hash = SignatureHash(address.to_redeemScript(), tx, 1, SIGHASH_ALL, int(amount), SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append(CTxInWitness(CScriptWitness([sig, privkey.pub])))
def test_emergency_vault_tx(bitcoind): """This tests the emergency_vault_tx() function.""" # The stakeholders, the first two are the traders. stk_privkeys = [os.urandom(32) for i in range(4)] stk_pubkeys = [CKey(k).pub for k in stk_privkeys] # The stakeholders emergency keys emer_privkeys = [os.urandom(32) for i in range(4)] emer_pubkeys = [CKey(k).pub for k in emer_privkeys] # Create the transaction funding the vault amount = 50 * COIN - 500 vault_txid = lx(send_vault_tx(bitcoind, stk_pubkeys, amount)) # Create the emergency transaction spending from the vault amount_min_fees = amount - 500 emer_tx = create_emergency_vault_tx(vault_txid, 0, amount_min_fees, emer_pubkeys) # Simulate that each stakeholder sign the transaction separately sigs = [sign_emergency_vault_tx(emer_tx, stk_pubkeys, amount, [k])[0] for k in stk_privkeys] emer_tx = form_emergency_vault_tx(emer_tx, stk_pubkeys, sigs) bitcoind.send_tx(b2x(emer_tx.serialize()))
def test_cancel_unvault_tx(bitcoind): """This tests that cancel_unvault_tx() produces a valid transaction.""" # The stakeholders, the first two are the traders. stk_privkeys = [os.urandom(32) for i in range(4)] stk_pubkeys = [CKey(k).pub for k in stk_privkeys] # The co-signing server, required by the spend tx serv_privkey = CKey(os.urandom(32)) serv_pubkey = serv_privkey.pub # Create the vault and unvault transactions amount_vault = 50 * COIN - 500 amount_unvault = amount_vault - 500 txid = send_unvault_tx(bitcoind, stk_privkeys, stk_pubkeys, serv_pubkey, amount_vault, amount_unvault) amount_cancel = amount_unvault - 500 # We re-spend to the same vault CTx = create_cancel_tx(txid, 0, stk_pubkeys, amount_cancel) sigs = [sign_cancel_tx(CTx, p, stk_pubkeys, serv_pubkey, amount_unvault) for p in stk_privkeys] CTx = form_cancel_tx(CTx, sigs, stk_pubkeys, serv_pubkey) bitcoind.send_tx(b2x(CTx.serialize()))
def test_unvault_tx(bitcoind): """This tests the unvault_tx() function.""" # The stakeholders, the first two are the traders. stk_privkeys = [os.urandom(32) for i in range(4)] stk_pubkeys = [CKey(k).pub for k in stk_privkeys] # The co-signing server, required by the spend tx serv_privkey = CKey(os.urandom(32)) serv_pubkey = serv_privkey.pub # Create the transaction funding the vault amount = 50 * COIN - 500 vault_txid = lx(send_vault_tx(bitcoind, stk_pubkeys, amount)) # Create the transaction spending from the vault amount_min_fees = amount - 500 unvtx = create_unvault_tx(vault_txid, 0, stk_pubkeys, serv_pubkey, amount_min_fees) assert len(unvtx.vout) == 1 # Simulate that each stakeholder sign the transaction separately sigs = [sign_unvault_tx(unvtx, stk_pubkeys, amount, [k])[0] for k in stk_privkeys] unvtx = form_unvault_tx(unvtx, stk_pubkeys, sigs) bitcoind.send_tx(b2x(unvtx.serialize()))
def test_vault_address_reuse(vault_factory): """Test that we are still safe if coins are sent to an already used vault. """ wallets = vault_factory.get_wallets() trader_A = wallets[0] bitcoind = trader_A.bitcoind reused_address = trader_A.getnewaddress() # Concurrent sends to the same address should be fine for _ in range(2): bitcoind.pay_to(reused_address, 12) wait_for(lambda: all(len(wallet.vaults) == 2 for wallet in wallets)) for wallet in wallets: wait_for(lambda: all(v["emergency_signed"] and v["unvault_signed"] and v["unvault_secure"] for v in wallet.vaults)) # Now test address reuse after a vault has been spent # We'll spend this one v = random.choice(trader_A.vaults) # And the second trader will sign with us the spend trader_B = wallets[1] spend_amount = 12 * COIN - 50000 # We choose a valid address.. addresses = { random.choice(trader_A.acked_addresses): spend_amount, } # The spend process, sig exchange, etc.. trader_A.initiate_spend(v, addresses) sigB = trader_B.accept_spend(v["txid"], addresses) pubkeyB = CKey(trader_B.vaults[0]["privkey"]).pub tx, spend_accepted = trader_A.complete_spend(v, pubkeyB, sigB, addresses) assert spend_accepted bitcoind.broadcast_and_mine( trader_A.get_signed_unvault_tx(v).serialize().hex()) # At this point we should have remarked the spend, and have removed the # vault. wait_for(lambda: all(len(wallet.vaults) == 1 for wallet in wallets)) # Generate 5 blocks for the locktime ! addr = bitcoind.getnewaddress() bitcoind.generatetoaddress(5, addr) bitcoind.broadcast_and_mine(b2x(tx.serialize())) # Creating new vaults to this address should still be fine for _ in range(2): bitcoind.pay_to(reused_address, 8) # 2 - 1 + 2 wait_for(lambda: all(len(wallet.vaults) == 3 for wallet in wallets)) for wallet in wallets: # Separated for reseting the timeout wait_for(lambda: all(v["emergency_signed"] for v in wallet.vaults)) wait_for(lambda: all(v["unvault_signed"] for v in wallet.vaults)) assert all(v["unvault_secure"] for v in wallet.vaults)
def add_input(bitcoind, tx, fees): """Add another input to the transaction to bump the feerate.""" # Don't be dust! if fees < 294: fees = 294 # Create the first stage transaction new_prevout_addr = P2WPKHBitcoinAddress(bitcoind.getnewaddress()) txid = bitcoind.sendtoaddress(str(new_prevout_addr), fees / COIN) out_index = get_output_index(bitcoind.getrawtransaction(txid, decode=True), fees) # Then gather the private key to unlock its output privkey = CKey(wif_decode(bitcoind.dumpprivkey(str(new_prevout_addr)))) # Add the fetched coin as a new input. tx.vin.append(CTxIn(COutPoint(lx(txid), out_index))) # We only do this once, sign it with ALL tx_hash = SignatureHash(new_prevout_addr.to_redeemScript(), tx, 1, SIGHASH_ALL, fees, SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append(CTxInWitness(CScriptWitness([sig, privkey.pub]))) return tx
def add_input_output(bitcoind, tx, coin, fees): """Add another input to the transaction to bump the feerate.""" coin_amount = Decimal(coin["amount"]) * Decimal(COIN) # First get the private key from bitcoind's wallet. privkey = CKey(wif_decode(bitcoind.dumpprivkey(coin["address"]))) # Add the fetched coin as a new input. tx.vin.append(CTxIn(COutPoint(lx(coin["txid"]), coin["vout"]))) # And likely add an output, otherwise all goes to the fees. scriptPubKey = CScript([OP_0, Hash160(privkey.pub)]) if coin_amount > fees + 294: # For simplicity, pay to the same script tx.vout.append(CTxOut(coin_amount - Decimal(fees), scriptPubKey)) address = CBitcoinAddress.from_scriptPubKey(scriptPubKey) # We only do this once, sign it with ALL tx_hash = SignatureHash(address.to_redeemScript(), tx, 1, SIGHASH_ALL, int(coin_amount), SIGVERSION_WITNESS_V0) sig = privkey.sign(tx_hash) + bytes([SIGHASH_ALL]) tx.wit.vtxinwit.append( CTxInWitness(CScriptWitness([sig, privkey.pub])) ) return tx
def test_vault_address_reuse(vault_factory): """Test that we are still safe if coins are sent to an already used vault. """ wallets = vault_factory.get_wallets() trader_A = wallets[0] # FIXME: separate the Bitcoin backends !! bitcoind = trader_A.bitcoind reused_address = trader_A.getnewaddress() # Concurrent sends to the same address should be fine for _ in range(3): bitcoind.pay_to(reused_address, 12) wait_for(lambda: len(trader_A.vaults) == 3) for wallet in wallets: wait_for(lambda: all(v["emergency_signed"] and v["unvault_signed"] and v["unvault_secure"] for v in wallet.vaults)) # Now test address reuse after a vault has been spent # We'll spend this one v = random.choice(trader_A.vaults) # And the second trader will sign with us the spend trader_B = wallets[1] # FIXME hardcoded fees.. spend_amount = 12 * COIN - 50000 # We choose a valid address.. addresses = { random.choice(trader_A.acked_addresses): spend_amount, } trader_A.initiate_spend(v, addresses) sigB = trader_B.accept_spend(v["txid"], addresses) pubkeyB = CKey(trader_B.vaults[0]["privkey"]).pub tx = trader_A.complete_spend(v, pubkeyB, sigB, addresses) bitcoind.broadcast_and_mine(b2x(v["unvault_tx"].serialize())) # At this point we should have remarked the spend, and have either # broadcast the cancel_tx, or removed the vault. wait_for(lambda: all(len(trader.vaults) == 2 for trader in [trader_A, trader_B])) # Generate 5 blocks for the locktime ! addr = bitcoind.getnewaddress() bitcoind.generatetoaddress(5, addr) bitcoind.broadcast_and_mine(b2x(tx.serialize())) # Creating new vaults should to this address should still be fine for _ in range(3): bitcoind.pay_to(reused_address, 8) # 3 - 1 + 3 wait_for(lambda: all(len(trader.vaults) == 5 for trader in [trader_A, trader_B])) for trader in [trader_A, trader_B]: wait_for(lambda: all(v["emergency_signed"] and v["unvault_signed"] and v["unvault_secure"] for v in trader.vaults))
def sign_spend_tx(tx, privkey, pubkeys, pub_server, prev_value): """Signs the transaction which spends the unvault_tx after the relative locktime with the given private keys. :param tx: The unsigned transaction, a CMutableTransaction. :param privkey: (bytes) The private key to sign the transaction with. :param pubkeys: A list of the 4 stakeholders' pubkeys, to form the script. :param pub_server: The public key of the cosigning server, to form the script. :param prev_value: The prevout's value in satoshis. :return: A list of the signature for each given private key. """ tx_hash = SignatureHash(unvault_script(*pubkeys, pub_server), tx, 0, SIGHASH_ALL, prev_value, SIGVERSION_WITNESS_V0) return CKey(privkey).sign(tx_hash) + bytes([SIGHASH_ALL])
def complete_spend(self, vault, peer_pubkey, peer_sig, addresses): """Our fellow trader also signed the spend, now ask the cosigner and notify other stakeholders we are about to spend a vault. We wait synchronously for their response, once again an assumption that's a demo! :param vault: The vault to spend, an entry of self.vaults[] :param peer_pubkey: The other peer's pubkey. :param peer_sig: A signature for this spend_tx with the above pubkey. :param addresses: A dictionary containing address as keys and amount to send in sats as value. :return: The fully signed transaction. """ our_sig = self.create_sign_spend_tx(vault, addresses) unvault_txid = vault["unvault_tx"].GetTxid() assert len(vault["unvault_tx"].vout) == 1 unvault_value = vault["unvault_tx"].vout[0].nValue cosig = \ self.cosigner.get_cosignature(unvault_txid[::-1].hex(), vault["pubkeys"], addresses, unvault_value) spend_tx = create_spend_tx(unvault_txid, 0, addresses) # Now the fun part, correctly reconstruct the script all_sigs = [bytes(0)] * 3 + [cosig] our_pos = vault["pubkeys"].index(CKey(vault["privkey"]).pub) peer_pos = vault["pubkeys"].index(peer_pubkey) all_sigs[our_pos] = our_sig all_sigs[peer_pos] = peer_sig spend_tx = form_spend_tx(spend_tx, vault["pubkeys"], self.cosigner_pubkey, all_sigs) # Notify others self.sigserver.request_spend(vault["txid"], addresses) # Wait for their response, keep it simple.. while True: res = self.sigserver.spend_accepted(vault["txid"]) if res: break # May also be None ! elif res is False: raise Exception("Spend rejected.") time.sleep(0.5) return spend_tx
def broadcast_unvault(wallets, vault): """This broadcasts the unvault transaction in a clean manner.""" trader_A, trader_B = (wallets[0], wallets[1]) spend_amount = vault["amount"] - 50000 # We choose a valid address.. addresses = { random.choice(trader_A.acked_addresses): spend_amount, } # The first trader creates the tx, signs it, pass both the tx and sig to B trader_A.initiate_spend(vault, addresses) # B hands his signature to A sigB = trader_B.accept_spend(vault["txid"], addresses) pubkeyB = CKey(trader_B.vaults[0]["privkey"]).pub # Then A forms the transaction and tells everyone, we can broadcast it. tx, accepted = trader_A.complete_spend(vault, pubkeyB, sigB, addresses) assert accepted trader_A.bitcoind.broadcast_and_mine( trader_A.get_signed_unvault_tx(vault).serialize().hex())
def sign_unvault_spend(tx, privkeys, pubkeys, pub_server, prev_value): """Signs a transaction spending from an unvault transaction. This is the "all stakeholders sign" path of the script, not encumbered by a timelock. This path is used for both the emergency and cancel transactions. :param tx: The unsigned transaction, a CMutableTransaction. :param privkeys: The private keys to sign the transaction with (a list). :param pubkeys: The pubkeys of the stakeholders. :param pub_server: The pubkey of the cosigning server. :param prev_value: The prevout's value in satoshis. :return: The signatures for the provided privkeys (a list). """ tx_hash = SignatureHash(unvault_script(*pubkeys, pub_server), tx, 0, SIGHASH_ALL, prev_value, SIGVERSION_WITNESS_V0) return [CKey(key).sign(tx_hash) + bytes([SIGHASH_ALL]) for key in privkeys]
class CosigningServer: """ A wrapper around a dead simple server co-signing spend transactions, but only once. """ def __init__(self): """Uncommon pattern, but a handy one. We setup everything when the wrapper is initialized.""" self.server = Flask(__name__) self.privkey = os.urandom(32) self.pubkey = CKey(self.privkey).pub # List of txids we already signed self.already_signed = [] bitcoin.SelectParams("regtest") self.setup_routes() def setup_routes(self): @self.server.route("/sign", methods=["POST"]) def get_signature(): """Sign a spend transaction.""" params = request.get_json() # Crash if it doesn't contain all entries ! txid = params["txid"] if txid in self.already_signed: return jsonify({"sig": None}), 403 pubkeys = params["pubkeys"] addresses = params["addresses"] prev_value = params["prev_value"] spend_tx = create_spend_tx(lx(txid), 0, addresses) pubkeys = [bytes.fromhex(pub) for pub in pubkeys] return jsonify({ "sig": sign_spend_tx(spend_tx, self.privkey, pubkeys, self.pubkey, prev_value).hex(), }), 200 @self.server.route("/getpubkey", methods=["GET"]) def get_pubkey(): """Get our pubkey for the vault wallets to form the scripts.""" return jsonify({"pubkey": self.pubkey.hex()}), 200 def run(self, host, port, debug): self.server.run(host, port, debug)
def get_wallets(self, emergency_privkeys=None): """Get 4 vaults, one for each stakeholder. Spin up the servers.""" bip32s = [BIP32.from_seed(os.urandom(32), "test") for _ in range(4)] xpubs = [bip32.get_master_xpub() for bip32 in bip32s] if emergency_privkeys is None: emergency_privkeys = [CKey(os.urandom(32)) for _ in range(4)] emergency_pubkeys = [k.pub for k in emergency_privkeys] self.vaults = [] # Generate some random 'OK' addresses acked_addresses = [self.bitcoind.getnewaddress() for _ in range(5)] for bip32 in bip32s: xpriv = bip32.get_master_xpriv() conf = self.bitcoind.rpc.__btc_conf_file__ cosigner_url = "http://localhost:{}".format(self.cosigning_port) sigserv_url = "http://localhost:{}".format(self.sigserver_port) self.vaults.append( Vault(xpriv, xpubs, emergency_pubkeys, conf, cosigner_url, sigserv_url, acked_addresses)) return self.vaults
def sign_spend_vault_txout(tx, pubkeys, prev_value, privkeys): """Signs a transaction spending a vault txout it with the given private keys. :param tx: The CMutableTransaction to sign. :param pubkeys: A list of each stakeholder's pubkey. :param prev_value: The value of the vault txout we spend. :param privkeys: A list of the private keys of the four stakeholders to sign the transaction. :return: The signatures in the same order as the private keys. """ tx_hash = SignatureHash(vault_script(pubkeys), tx, 0, SIGHASH_ALL, amount=prev_value, sigversion=SIGVERSION_WITNESS_V0) # A signature per pubkey sigs = [CKey(key).sign(tx_hash) + bytes([SIGHASH_ALL]) for key in privkeys] return sigs
def test_revoke_spend(vault_factory): """Test that unvaults that aren't authorized are revoked.""" wallets = vault_factory.get_wallets() create_new_vaults(wallets, 1) trader_A, trader_B = wallets[0], wallets[1] bitcoind = trader_A.bitcoind # We spend this one! vault = trader_A.vaults[0] unvault_amount = vault["unvault_tx"].vout[0].nValue spend_amount = unvault_amount - 50000 # Choose an unauthorized address addresses = { bitcoind.getnewaddress(): spend_amount, } # The first trader creates the tx, signs it, pass both the tx and sig to B trader_A.initiate_spend(vault, addresses) # B hands his signature to A sigB = trader_B.accept_spend(vault["txid"], addresses) pubkeyB = CKey(trader_B.vaults[0]["privkey"]).pub # Then A forms the transaction and tells everyone, we can broadcast it. tx, accepted = trader_A.complete_spend(vault, pubkeyB, sigB, addresses) assert not accepted first_vault_txid = vault["txid"] bitcoind.broadcast_and_mine( trader_A.get_signed_unvault_tx(vault).serialize().hex()) # At this point we should have remarked the spend, and have broadcast the # cancel_tx. This means still len(vaults) == 1, but a different one ! wait_for(lambda: all(len(wallet.vaults) == 1 for wallet in wallets)) wait_for(lambda: all(wallet.vaults[0]["txid"] != first_vault_txid if len( wallet.vaults) > 0 else False for wallet in wallets)) addr = bitcoind.getnewaddress() bitcoind.generatetoaddress(5, addr) try: with pytest.raises(bitcoin.rpc.VerifyError, match="bad-txns-inputs-missingorspent"): bitcoind.broadcast_and_mine(b2x(tx.serialize())) except AssertionError: with pytest.raises(bitcoin.rpc.VerifyError, match="Missing inputs"): bitcoind.broadcast_and_mine(b2x(tx.serialize()))
def sign_unvault_tx(tx, privkey, pubkeys, prev_value): """Signs the unvaulting transaction. As it's not a revaulting transaction it's signed with SIGHASH_ALL. We can imagine updating this transaction as the fees evolve, though (but always with SIGHASH_ALL !). :param tx: The id of the transaction funding the vault. :param privkey: (bytes) The private key to sign the transaction with. :param pubkeys: A list containing the public key of each stakeholder. :param prev_value: The vault output (previout output) value in satoshis. :return: The signatures in the same order as the given privkeys. """ tx_hash = SignatureHash(vault_script(pubkeys), tx, 0, SIGHASH_ALL, amount=prev_value, sigversion=SIGVERSION_WITNESS_V0) return CKey(privkey).sign(tx_hash) + bytes([SIGHASH_ALL])
def test_spend_creation(vault_factory): """Test that the signature exchange between the traders and cosigner leads to a well-formed spend_tx.""" wallets = vault_factory.get_wallets() trader_A, trader_B = wallets[0], wallets[1] # FIXME: separate the Bitcoin backends !! bitcoind = trader_B.bitcoind bitcoind.pay_to(trader_A.getnewaddress(), 10) wait_for(lambda: all(len(w.vaults) == 1 for w in wallets)) wait_for(lambda: all(v["emergency_signed"] for v in trader_A.vaults)) wait_for(lambda: all(v["unvault_signed"] for v in trader_B.vaults)) assert all(v["unvault_secure"] for v in trader_A.vaults) # Try to spend from the newly created vault vault = trader_A.vaults[0] # FIXME hardcoded fees.. spend_amount = 10 * COIN - 50000 # We choose a valid address.. addresses = { random.choice(trader_A.acked_addresses): spend_amount, } # The first trader creates the tx, signs it, pass both the tx and sig to B trader_A.initiate_spend(vault, addresses) # B hands his signature to A sigB = trader_B.accept_spend(vault["txid"], addresses) pubkeyB = CKey(trader_B.vaults[0]["privkey"]).pub # Then A forms the transaction and tells everyone, we can broadcast it. tx = trader_A.complete_spend(vault, pubkeyB, sigB, addresses) bitcoind.broadcast_and_mine(b2x(vault["unvault_tx"].serialize())) # At this point we should have remarked the spend, and have either # broadcast the cancel_tx, or removed the vault. wait_for(lambda: all(len(trader.vaults) == 0 for trader in [trader_A, trader_B])) # Generate 5 blocks for the locktime ! addr = bitcoind.getnewaddress() bitcoind.generatetoaddress(5, addr) bitcoind.broadcast_and_mine(b2x(tx.serialize()))
apikey = "" secret = "" def SignECDSA(key, message): sig, i = key.sign_compact(message) meta = 27 + i if key.is_compressed: meta += 4 return base64.b64encode(chr(meta) + sig) privkey = CKey(base64.b64decode(secret), False) nonce = str(int(time.time())) msg = "nonce=" + nonce sign = SignECDSA( privkey, hashlib.sha256(hashlib.sha256("Bitmaszyna.pl API:\n" + msg).digest()).digest()) print( requests.post('https://bitmaszyna.pl/api/funds', data={ 'nonce': nonce }, headers={ 'Rest-Key': apikey, 'Rest-Sign': sign }).json())
def test_unvault_txout(bitcoind): """Test that unvault_txout() produces a valid and conform txo. Note that we use python-bitcoinlib for this one, as signrawtransactionwithkey is (apparently?) not happy dealing with exotic scripts. Note also that bitcoinlib's API uses sats, while bitcoind's one uses BTC.. """ amount = 50 * COIN - 500 # The stakeholders stk_privkeys = [CKey(os.urandom(32)) for i in range(4)] stk_pubkeys = [k.pub for k in stk_privkeys] # The cosigning server serv_privkey = CKey(os.urandom(32)) # First, pay to the unvault tx script txo = unvault_txout(stk_pubkeys, serv_privkey.pub, amount) txo_addr = str(CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey)) amount_for_bitcoind = float(Decimal(amount) / Decimal(COIN)) txid = bitcoind.pay_to(txo_addr, amount_for_bitcoind) # We can spend it immediately if all stakeholders sign (emergency or cancel # tx) txin = CTxIn(COutPoint(lx(txid), 0)) amount_min_fees = amount - 500 addr = bitcoind.getnewaddress() new_txo = CTxOut(amount_min_fees, CBitcoinAddress(addr).to_scriptPubKey()) tx = CMutableTransaction([txin], [new_txo], nVersion=2) # We can't test the signing against bitcoind, but we can at least test the # transaction format bitcoind_tx = bitcoind.rpc.createrawtransaction([ {"txid": txid, "vout": 0} ], [ {addr: float(Decimal(amount_min_fees) / Decimal(COIN))} ]) assert b2x(tx.serialize()) == bitcoind_tx tx_hash = SignatureHash(unvault_script(*stk_pubkeys, serv_privkey.pub), tx, 0, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0) sigs = [key.sign(tx_hash) + bytes([SIGHASH_ALL]) for key in stk_privkeys[::-1]] # Note the reverse here witness_script = [*sigs, unvault_script(*stk_pubkeys, serv_privkey.pub)] witness = CTxInWitness(CScriptWitness(witness_script)) tx.wit = CTxWitness([witness]) bitcoind.send_tx(b2x(tx.serialize())) assert bitcoind.has_utxo(addr) # If two out of three stakeholders sign, we need the signature from the # cosicosigning server and we can't spend it before 6 blocks (csv). # Pay back to the unvault tx script txo = unvault_txout(stk_pubkeys, serv_privkey.pub, amount) txo_addr = str(CBitcoinAddress.from_scriptPubKey(txo.scriptPubKey)) txid = bitcoind.pay_to(txo_addr, amount_for_bitcoind) # Reconstruct the transaction but with only two stakeholders signatures txin = CTxIn(COutPoint(lx(txid), 0), nSequence=6) amount_min_fees = amount - 500 addr = bitcoind.getnewaddress() new_txo = CTxOut(amount_min_fees, CBitcoinAddress(addr).to_scriptPubKey()) tx = CMutableTransaction([txin], [new_txo], nVersion=2) # We can't test the signing against bitcoind, but we can at least test the # transaction format bitcoind_tx = bitcoind.rpc.createrawtransaction([ {"txid": txid, "vout": 0, "sequence": 6} ], [ {addr: float(Decimal(amount_min_fees) / Decimal(COIN))} ]) assert b2x(tx.serialize()) == bitcoind_tx tx_hash = SignatureHash(unvault_script(*stk_pubkeys, serv_privkey.pub), tx, 0, SIGHASH_ALL, amount, SIGVERSION_WITNESS_V0) # The cosigning server sigs = [serv_privkey.sign(tx_hash) + bytes([SIGHASH_ALL])] # We fail the third CHECKSIG !! sigs += [empty_signature()] sigs += [key.sign(tx_hash) + bytes([SIGHASH_ALL]) for key in stk_privkeys[::-1][2:]] # Just the first two witness_script = [*sigs, unvault_script(*stk_pubkeys, serv_privkey.pub)] witness = CTxInWitness(CScriptWitness(witness_script)) tx.wit = CTxWitness([witness]) # Relative locktime ! for i in range(5): with pytest.raises(VerifyRejectedError, match="non-BIP68-final"): bitcoind.send_tx(b2x(tx.serialize())) bitcoind.generate_block(1) # It's been 6 blocks now bitcoind.send_tx(b2x(tx.serialize())) assert bitcoind.has_utxo(addr)
def test_increase_revault_tx_feerate(bitcoind): """This tests that any of the stakeholders can increase the feerate of any of the revaulting transactions in a timely manner. Will justice rule?""" # The stakeholders, the first two are the traders. stk_privkeys = [os.urandom(32) for i in range(4)] stk_pubkeys = [CKey(k).pub for k in stk_privkeys] # Same, but for the EDV emer_privkeys = [os.urandom(32) for i in range(4)] emer_pubkeys = [CKey(k).pub for k in emer_privkeys] # The co-signing server, required by the spend tx serv_privkey = os.urandom(32) serv_pubkey = CKey(serv_privkey).pub # Test the vault emergency amount_vault = 50 * COIN - 500 txid = send_vault_tx(bitcoind, stk_pubkeys, amount_vault) amount_emer = amount_vault - 500 CTx = create_emergency_vault_tx(lx(txid), 0, amount_emer, emer_pubkeys) sigs = [ sign_emergency_vault_tx(CTx, p, stk_pubkeys, amount_vault) for p in stk_privkeys ] # Sanity checks don't hurt assert all(sig[-1] == SIGHASH_ALL | SIGHASH_ANYONECANPAY for sig in sigs) CMTx = CMutableTransaction.from_tx( form_emergency_vault_tx(CTx, stk_pubkeys, sigs)) fees_before = tx_fees(bitcoind, CMTx) first_txid = creates_add_input(bitcoind, CMTx) fees_after = tx_fees(bitcoind, CMTx) assert fees_after > fees_before bitcoind.send_tx(CMTx.serialize().hex(), wait_for_mempool=[first_txid]) # Test the emer unvault amount_vault = 50 * COIN - 500 amount_unvault = amount_vault - 500 txid = send_unvault_tx(bitcoind, stk_privkeys, stk_pubkeys, serv_pubkey, amount_vault, amount_unvault) amount_emer = amount_unvault - 500 CTx = create_emer_unvault_tx(txid, 0, emer_pubkeys, amount_emer) sigs = [ sign_emer_unvault_tx(CTx, p, stk_pubkeys, serv_pubkey, amount_unvault) for p in stk_privkeys ] # Sanity checks don't hurt assert all(sig[-1] == SIGHASH_ALL | SIGHASH_ANYONECANPAY for sig in sigs) CMTx = CMutableTransaction.from_tx( form_emer_unvault_tx(CTx, sigs, stk_pubkeys, serv_pubkey)) fees_before = tx_fees(bitcoind, CMTx) first_txid = creates_add_input(bitcoind, CMTx) fees_after = tx_fees(bitcoind, CMTx) assert fees_after > fees_before bitcoind.send_tx(CMTx.serialize().hex(), wait_for_mempool=[first_txid]) # Test the cancel unvault amount_vault = 50 * COIN - 500 amount_unvault = amount_vault - 500 txid = send_unvault_tx(bitcoind, stk_privkeys, stk_pubkeys, serv_pubkey, amount_vault, amount_unvault) amount_cancel = amount_unvault - 500 CTx = create_cancel_tx(txid, 0, emer_pubkeys, amount_cancel) sigs = [ sign_cancel_tx(CTx, p, stk_pubkeys, serv_pubkey, amount_unvault) for p in stk_privkeys ] # Sanity checks don't hurt assert all(sig[-1] == SIGHASH_ALL | SIGHASH_ANYONECANPAY for sig in sigs) CMTx = CMutableTransaction.from_tx( form_cancel_tx(CTx, sigs, stk_pubkeys, serv_pubkey)) fees_before = tx_fees(bitcoind, CMTx) first_txid = creates_add_input(bitcoind, CMTx) fees_after = tx_fees(bitcoind, CMTx) assert fees_after > fees_before bitcoind.send_tx(CMTx.serialize().hex(), wait_for_mempool=[first_txid])
SelectParams('testnet') MONGOCONNECTION = pymongo.Connection('52.1.141.196', 27017) MONGODB = MONGOCONNECTION.escrow.demo escrow = MONGODB.find_one( {'buyerurlhash': "906618b107da70ed301d701ce8dbff533f35812d"}) phrase = "sample core fitness wrong unusual inch hurry chaos myself credit welcome margin" seed = mnemonic.Mnemonic.to_seed(phrase) wallet = BIP32Node.from_master_secret(seed, 'XTN') toddkeys = [] keys = [] for k in escrow['keys']: print k['subkey'] hdkey = wallet.subkey_for_path(k['subkey']) print b2x(CKey(hdkey.sec()).pub) print hdkey.sec_as_hex() print k['publickey'] print "" toddkeys.append(CKey(hdkey.sec())) keys.append(CPubKey(hdkey.address())) """ keys = [] for pubkey in escrow['keys']: print "PUBLIC KEY", pubkey['publickey'] keys.append(CPubKey(pubkey['publickey'])) """ # Create a redeemScript. Similar to a scriptPubKey the redeemScript must be # satisfied for the funds to be spent. redeemScript = CScript(keys)