def __init__(self, paypubkey, refundpubkey, secret, timeout, revoke, revoke_side, delay, privkey=None, secret_pre=None, revoke_pre=None, secret_type='sha'): assert revoke_side in ["pay", "refund"] assert secret_type == 'sha' assert len(secret) == 20 assert secret_pre is None or len(secret_pre) == 32 assert len(revoke) == 20 if revoke_pre is not None: assert len(revoke_pre) == 32 assert Hash160(revoke_pre) == revoke assert secret_pre is None assert privkey is not None assert privkey.pub == (paypubkey if revoke_side == "pay" else refundpubkey) elif secret_pre is not None: assert privkey is not None assert privkey.pub == paypubkey assert Hash160(secret_pre) == secret elif privkey is not None: assert privkey.pub == refundpubkey if self._h_nil not in [revoke, secret]: self.timeout_pre = OP_FALSE elif self._h_1 not in [revoke, secret]: self.timeout_pre = OP_TRUE else: self.timeout_pre = OP_2 self.paypubkey = paypubkey self.refundpubkey = refundpubkey self.secret = secret self.timeout = timeout self.privkey = privkey self.secret_pre = secret_pre self.secret_type = secret_type self.delay = delay self.revoke_side = revoke_side self.revoke = revoke self.revoke_pre = revoke_pre
def get_public_key(self, f="b64", public_key=None): if public_key == None: public_key = self.public_key #O4 = uncompressed. public_key_hex = "04" + binascii.hexlify(public_key).decode("utf-8") if self.use_compression: public_key = binascii.unhexlify(public_key_hex) public_key = self.compress_public_key(public_key) public_key_hex = binascii.hexlify(public_key).decode("utf-8") else: public_key = binascii.unhexlify(public_key_hex) cpub = CPubKey(x(public_key_hex)) if f == "bin": return public_key elif f == "hex": return public_key_hex elif f == "cpub": return cpub elif f == "hash": return Hash160(cpub) else: return base64.b64encode(public_key).decode("utf-8")
def close_tx(self, fee: int, privkey_dest: str) -> str: """Create a (mutual) close tx""" txin = CTxIn(COutPoint(bytes.fromhex(self.txid), self.output_index)) out_privkey = privkey_expand(privkey_dest) txout = CTxOut(self.amount - fee, CScript([script.OP_0, Hash160(coincurve.PublicKey.from_secret(out_privkey.secret).format())])) tx = CMutableTransaction(vin=[txin], vout=[txout]) sighash = script.SignatureHash(self.redeemscript(), tx, inIdx=0, hashtype=script.SIGHASH_ALL, amount=self.amount, sigversion=script.SIGVERSION_WITNESS_V0) sigs = [key.sign(sighash, hasher=None) for key in self.funding_privkeys_for_tx()] # BOLT #3: # ## Closing Transaction # ... # * `txin[0]` witness: `0 <signature_for_pubkey1> <signature_for_pubkey2>` witness = CScriptWitness([bytes(), sigs[0] + bytes([script.SIGHASH_ALL]), sigs[1] + bytes([script.SIGHASH_ALL]), self.redeemscript()]) tx.wit = CTxWitness([CTxInWitness(witness)]) return tx.serialize().hex()
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 analyze_key_pair(key_pair): """ Converts a key pair to different formats which is useful for working with Bitcoin Script. """ if "priv" not in key_pair: key_pair["priv"] = None pub = CPubKey(x(key_pair["pub"])) addr = None if "addr" in key_pair: addr = key_pair["addr"] """ The Hash160 function in Python-bitcoin lib actually wraps around sha256 hash so every call to Hash160 also sha256 hashes the input before ripemd160 hashing, meaning it the output is valid for address hashes. """ return { "addr": { "base58": addr }, "pub": { "hash": Hash160(pub), "hex": key_pair["pub"], "bin": pub }, "priv": { "wif": key_pair["priv"], "hex": None, "bin": None } }
def get_payment_address(time_lock, secret_hash, pub_0, pub_1): pubkey_script = payment_script(time_lock, secret_hash, pub_0, pub_1) print(pubkey_script.hex()) pubkey_hash = Hash160(pubkey_script) data = b'\x55' + pubkey_hash checksum = sha256(sha256(data).digest()).digest()[:4] byte_address = data + checksum address = bitcoin.base58.encode(byte_address) return address
def get_address_from_pubkey(cls, pubkey): """ Get bitcoin address for a specific hrp (human readable part) bech32 encoded from a public key(secp256k1). :param string pubkey: public key :returns: string bech32 encoded address """ script_pubkey = CScript([OP_0, Hash160(pubkey)]) return str(P2WPKHBitcoinAddress.from_scriptPubKey(script_pubkey))
def htlc_p2sh(self) -> str: # TODO: cache the generated address # We create connections on the fly because they'll time out quickly if # we don't bitcoind = rpc.Proxy() # We can decipher the hash of the preimage without explicitly asking # for it by taking it out of the payment request supplied decoded_pr = json.loads( to_json(lnd.decode_payment_request(self.bolt11_invoice))) hashed_preimage = decoded_pr['payment_hash'] # Once these assignments are made, we want to lock them in so this # functions generates deterministically if not self.final_address_pubkey: final_address = bitcoind.getnewaddress() seckey = bitcoind.dumpprivkey(final_address) self.final_address_pubkey = seckey.pub.hex() if not self.redeemblock: curr_blockheight = bitcoind.getblockcount() self.redeemblock = curr_blockheight + self.lockduration # HTLC locking script txin_redeemScript = CScript([ OP_IF, OP_SHA256, bytes(hashed_preimage, 'utf8'), OP_EQUALVERIFY, OP_DUP, OP_HASH160, bytes(Hash160(bytes(self.final_address_pubkey, 'utf8'))), OP_ELSE, self.redeemblock, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_DUP, OP_HASH160, bytes(Hash160(bytes(self.refund_address, 'utf8'))), OP_ENDIF, OP_EQUALVERIFY, OP_CHECKSIG ]) self.save() # Generate a P2SH address from the locking script txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() txin_p2sh_address = CBitcoinAddress.from_scriptPubKey( txin_scriptPubKey) return str(txin_p2sh_address)
def make_paytopubkeyhash(self, n = None): if n is None: n = random.randrange(0, len(self.keypairs)) secret_bytes = Hash(self.seed + struct.pack('>L', n)) secret_key = CBitcoinSecret.from_secret_bytes(secret_bytes) # pay-to-pubkeyhash scriptPubKey = CScript([OP_DUP, OP_HASH160, Hash160(secret_key.pub), OP_EQUALVERIFY, OP_CHECKSIG]) self.keypairs[scriptPubKey] = secret_key return scriptPubKey
def from_utxo(txid_in: str, tx_index_in: int, sats: int, privkey: str, fee: int, local_node_privkey: str, local_funding_privkey: str, remote_node_privkey: str, remote_funding_privkey: str, chain_hash: str = regtest_hash) -> Tuple['Funding', str]: """Make a funding transaction by spending this utxo using privkey: return Funding, tx.""" # Create dummy one to start: we will fill in txid at the end. funding = Funding('', 0, sats - fee, local_node_privkey, local_funding_privkey, remote_node_privkey, remote_funding_privkey, chain_hash) # input private key. inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) # use RBF'able input (requirement for dual-funded things) txin = CTxIn(COutPoint(bytes.fromhex(txid_in), tx_index_in), nSequence=0xFFFFFFFD) txout = CTxOut( sats - fee, CScript([script.OP_0, sha256(funding.redeemscript()).digest()])) tx = CMutableTransaction([txin], [txout], nVersion=2, nLockTime=funding.locktime) # now fill in funding txid. funding.txid = tx.GetTxid().hex() funding.tx = tx # while we're here, sign the transaction. address = P2WPKHBitcoinAddress.from_scriptPubKey( CScript([script.OP_0, Hash160(inkey_pub.format())])) sighash = script.SignatureHash(address.to_redeemScript(), tx, 0, script.SIGHASH_ALL, amount=sats, sigversion=script.SIGVERSION_WITNESS_V0) sig = inkey.sign(sighash, hasher=None) + bytes([script.SIGHASH_ALL]) tx.wit = CTxWitness( [CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))]) return funding, tx.serialize().hex()
def maketx(tx): #txid from blockr. bitcoind does not support tx index! txid = lx(tx) vout = 0 outp = COutPoint(txid, vout) print("output: %s" % outp) # Create the txin structure, which includes the outpoint txin = CMutableTxIn(outp) print(txin) txin_scriptPubKey = CScript( [OP_DUP, OP_HASH160, Hash160(seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG]) print(txin_scriptPubKey) amount = 0.001 * COIN txout = CMutableTxOut(amount, CBitcoinAddress(a).to_scriptPubKey()) print(txout) # Create the unsigned transaction. newtx = CMutableTransaction([txin], [txout]) print(newtx) sighash = SignatureHash(txin_scriptPubKey, newtx, 0, SIGHASH_ALL) print(sighash) # Now sign it. We have to append the type of signature we want to the end, in # this case the usual SIGHASH_ALL. sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) print(sig) # Set the scriptSig of our transaction input appropriately. txin.scriptSig = CScript([sig, seckey.pub]) try: VerifyScript(txin.scriptSig, txin_scriptPubKey, newtx, 0, (SCRIPT_VERIFY_P2SH, )) except: pass print('*' * 20) print(b2x(newtx.serialize())) """
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 generate_tx(self): tx_chain = self.get_next_tx_chain() txid = lx(tx_chain.current_unspent_tx) txins = [ CMutableTxIn(COutPoint(txid, 0)), CMutableTxIn(COutPoint(txid, 1)) ] txin_seckeys = [tx_chain.seckey, self._spent_to.seckey] amount_in = tx_chain.amount tx_chain.amount -= int(config.transaction_fee / 2) txout1 = CMutableTxOut( tx_chain.amount, CBitcoinAddress(tx_chain.address).to_scriptPubKey()) txout2 = CMutableTxOut( tx_chain.amount, CBitcoinAddress(self._spent_to.address).to_scriptPubKey()) tx = CMutableTransaction(txins, [txout1, txout2], nVersion=2) for i, txin in enumerate(txins): txin_scriptPubKey = CScript([ OP_DUP, OP_HASH160, Hash160(txin_seckeys[i].pub), OP_EQUALVERIFY, OP_CHECKSIG ]) sighash = SignatureHash(txin_scriptPubKey, tx, i, SIGHASH_ALL) sig = txin_seckeys[i].sign(sighash) + bytes([SIGHASH_ALL]) txin.scriptSig = CScript([sig, txin_seckeys[i].pub]) tx_serialized = tx.serialize() logging.debug('{} trying to sendrawtransaction' ' (in=2x{} out=2x{} fee={} bytes={})' ' using tx_chain number={}'.format( self._name, amount_in, txout1.nValue, (amount_in * 2) - (txout1.nValue * 2), len(tx_serialized), self._current_tx_chain_index)) tx_hash = self.execute_rpc('sendrawtransaction', b2x(tx_serialized)) tx_chain.current_unspent_tx = tx_hash logging.info( '{} sendrawtransaction was successful; tx got hash={}'.format( self._name, tx_hash))
def _received_htlc_output(self, htlc: HTLC, side: Side) -> Tuple[script.CScript, int]: # BOLT #3: This output sends funds to either the remote node after the # HTLC-timeout or using the revocation key, or to an HTLC-success # transaction with a successful payment preimage. The output is a # P2WSH, with a witness script: # # # To remote node with revocation key # OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL # OP_IF # OP_CHECKSIG # OP_ELSE # <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL # OP_IF # # To local node via HTLC-success transaction. # OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY # 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG # OP_ELSE # # To remote node after timeout. # OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP # OP_CHECKSIG # OP_ENDIF # OP_ENDIF htlc_script = script.CScript([ script.OP_DUP, script.OP_HASH160, Hash160(self.revocation_pubkey(side).format()), script.OP_EQUAL, script.OP_IF, script.OP_CHECKSIG, script.OP_ELSE, self.remote_htlc_pubkey(side).format(), script.OP_SWAP, script.OP_SIZE, 32, script.OP_EQUAL, script.OP_IF, script.OP_HASH160, self.ripemd160(htlc.raw_payment_hash()), script.OP_EQUALVERIFY, 2, script.OP_SWAP, self.local_htlc_pubkey(side).format(), 2, script.OP_CHECKMULTISIG, script.OP_ELSE, script.OP_DROP, htlc.cltv_expiry, script.OP_CHECKLOCKTIMEVERIFY, script.OP_DROP, script.OP_CHECKSIG, script.OP_ENDIF, script.OP_ENDIF ]) # BOLT #3: The amounts for each output MUST be rounded down to whole # satoshis. return htlc_script, htlc.amount_msat // 1000
def generate_p2pkh(Params, pubkey, inputs, inIndex, outputs, details): # 1. SelectParams SelectParams(Params) # 2. make trx input scriptPubKey txin_scriptPubKey = CScript( [OP_DUP, OP_HASH160, Hash160(pubkey), OP_EQUALVERIFY, OP_CHECKSIG]) current_input = 0 current_output = 0 txout = [None] * len(outputs) txin_all = [] txout_all = [] # 3. make inputs data while current_input < len(inputs): txid = lx(list(inputs)[current_input].prev_hash.hex()) vout = list(inputs)[current_input].prev_index txin = CMutableTxIn(COutPoint(txid, vout), nSequence=list(inputs)[current_input].sequence) txin_all.append(txin) current_input += 1 # 4. make outputs data while current_output < len(outputs): out_address = list(outputs)[current_output].address out_amount = list(outputs)[current_output].amount txout[current_output] = CMutableTxOut( out_amount, CBitcoinAddress(out_address).to_scriptPubKey()) txout_all.append(txout[current_output]) current_output += 1 # 5. make Trx data tmp_trx = CMutableTransaction(txin_all, txout_all, nLockTime=details.lock_time, nVersion=details.version) # 6. generate Trx Hash sighash = SignatureHash(txin_scriptPubKey, tmp_trx, inIndex, SIGHASH_ALL) return sighash
def make_signed_tx(ins, outs, priv): print('####### in make_signed_tx #######') print('ins: {}'.format(ins)) print('outs: {}'.format(outs)) print('priv: {}'.format(priv)) txins = [] txouts = [] txin_scriptPubKeys = [] # txin_scriptPubKeys = [] for i, inp in enumerate(ins): print('inp[tx_id]: {}'.format(inp['tx_id'])) txin = CMutableTxIn(COutPoint(lx(inp['tx_id']), inp['index'])) seckey = CBitcoinSecret.from_secret_bytes( pygcoinlib.script_to_address(inp['script']).encode('utf-8')) txin_scriptPubKeys.append( CScript([ OP_DUP, OP_HASH160, Hash160(seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG ])) # txin_scriptPubKeys.append(CScript([OP_DUP, OP_HASH160, Hash160(seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG])) txins.append(txin) for o, out in enumerate(outs): # print('out[\'address\']: {}'.format(out['address'])) if 'script' in out: # txouts.append(CMutableTxOut(0, CScript([bytes(out['script'], encoding='UTF-8')]), 2)) print('song') else: txouts.append( CMutableTxOut( out['value'], CBitcoinAddress(out['address']).to_scriptPubKey(), out['color'])) # print('address: {}'.format(pygcoinlib.script_to_address(spk['script']))) tx = CMutableTransaction(txins, txouts) for i, inp in enumerate(ins): sighash = SignatureHash(txin_scriptPubKeys[i], tx, i, SIGHASH_ALL) sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) txins[i].scriptSig = CScript([sig, seckey.pub]) VerifyScript(txins[i].scriptSig, txin_scriptPubKeys[i], tx, i, (SCRIPT_VERIFY_P2SH, )) return b2x(tx.serialize())
def add_witnesses(self, witness_stack) -> str: wits = [] for idx, _in in enumerate(self.inputs): privkey = _in['privkey'] serial_id = _in['serial_id'] if privkey: inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) address = P2WPKHBitcoinAddress.from_scriptPubKey( CScript([script.OP_0, Hash160(inkey_pub.format())])) sighash = script.SignatureHash( address.to_redeemScript(), self.tx, idx, script.SIGHASH_ALL, amount=_in['sats'], sigversion=script.SIGVERSION_WITNESS_V0) sig = inkey.sign(sighash, hasher=None) + bytes( [script.SIGHASH_ALL]) wits.append( CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))) continue # Every input from the witness stack will be the accepter's # which is always an odd serial assert (serial_id % 2 == 1) elems = witness_stack.pop(0)['witness_element'] stack = [] for elem in elems: stack.append(bytes.fromhex(elem['witness'])) wits.append(CTxInWitness(CScriptWitness(stack))) self.tx.wit = CTxWitness(wits) return self.tx.serialize().hex()
def prepare_signatures(transaction, public_keys): """ Create the hashes of transaction :param transaction: unsigned transaction hex code :param pubkeys: list of public keys :return: list of hashes to be signed """ tx_inputs = transaction.vin hashes = [] if len(tx_inputs) != len(public_keys): raise ValueError( "Mismatching transaction inputs and list of public keys") for i in range(len(tx_inputs)): txin_scriptPubKey = CScript([ OP_DUP, OP_HASH160, Hash160(binascii.unhexlify(public_keys[i])), OP_EQUALVERIFY, OP_CHECKSIG ]) sighash = SignatureHash(txin_scriptPubKey, transaction, i, SIGHASH_ALL) hashes.append(sighash) return hashes
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 _offered_htlc_output(self, htlc: HTLC, side: Side) -> Tuple[script.CScript, int]: # BOLT #3: This output sends funds to either an HTLC-timeout # transaction after the HTLC-timeout or to the remote node # using the payment preimage or the revocation key. The output # is a P2WSH, with a witness script: # # # To remote node with revocation key # OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL # OP_IF # OP_CHECKSIG # OP_ELSE # <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL # OP_NOTIF # # To local node via HTLC-timeout transaction (timelocked). # OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG # OP_ELSE # # To remote node with preimage. # OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY # OP_CHECKSIG # OP_ENDIF # OP_ENDIF htlc_script = script.CScript([ script.OP_DUP, script.OP_HASH160, Hash160(self.revocation_pubkey(side).format()), script.OP_EQUAL, script.OP_IF, script.OP_CHECKSIG, script.OP_ELSE, self.remote_htlc_pubkey(side).format(), script.OP_SWAP, script.OP_SIZE, 32, script.OP_EQUAL, script.OP_NOTIF, script.OP_DROP, 2, script.OP_SWAP, self.local_htlc_pubkey(side).format(), 2, script.OP_CHECKMULTISIG, script.OP_ELSE, script.OP_HASH160, self.ripemd160(htlc.raw_payment_hash()), script.OP_EQUALVERIFY, script.OP_CHECKSIG, script.OP_ENDIF, script.OP_ENDIF ]) # BOLT #3: The amounts for each output MUST be rounded down to whole # satoshis. return htlc_script, htlc.amount_msat // 1000
def transfer(self, src_addrs_key_map: OrderedDict, dst_addrs_amount_map: dict, txfee: Decimal, auto_calc_pay_back: bool, pay_back_index: int = 0xfffffff, ensure_one_txout: bool = False) -> str: """ :param src_addrs_key_map: {'addr1': 'privkey1', 'addr2': 'privkey2' } 私钥为 hex字符串 :param dst_addrs_amount_map: {'addr1':Decimal(0.1234), 'addr2':Deciaml(0.234) } :param txfee: 矿工费 :param auto_calc_pay_back: 是否自动计算并找零 :param pay_back_index: 找零地址索引 即在 src_addrs_key_map 中的索引 :param ensure_one_txout: 确认只有一个交易输出(比如: 提币不需要找零的情况, 归集) :return: txid """ #仅支持 P2PKH 类型的from地址 assert isinstance(src_addrs_key_map, OrderedDict), 'src_addrs_key_map is not OrderedDict' assert isinstance(dst_addrs_amount_map, dict), 'dst_addrs_amount_map is not dict' assert Decimal('0.00001') <= txfee <= Decimal( '0.001'), 'invalid txfee, please check txfee' assert len( src_addrs_key_map) >= 1, 'src_addrs_key_map length must >= 1' assert not (True == auto_calc_pay_back == ensure_one_txout) , \ 'True == auto_calc_pay_back == ensure_one_txout , must be mutex ' if ensure_one_txout: assert (len(dst_addrs_amount_map) == 1 ), 'dst_addrs_amount_map length must equal 1' elif not auto_calc_pay_back: assert (len(dst_addrs_amount_map) >= 1), 'dst_addrs_amount_map length must >= 2' if auto_calc_pay_back and pay_back_index >= len(src_addrs_key_map): raise Exception('pay_back_index is to large') self.logger.info( f'dst_addrs_amount_map is { json.dumps(dst_addrs_amount_map, indent=4, default=decimal_default) }' ) assert self.ping() == True, 'bitcoind rpc is gone' total_amount = sum(dst_addrs_amount_map.values()) + txfee self.logger.info(f'total_amount is {total_amount}') source_addrs = list(src_addrs_key_map.keys()) #WARNING: 禁止打印私钥!!! is_enough, founded_utxos = self.search_utxo(addrs=source_addrs, total_amount=total_amount) if not is_enough: msg = 'balance is not enough' self.logger.error(msg) raise Exception(msg) self.logger.info( f'founded_utxos is { json.dumps(founded_utxos, indent=4, default=decimal_default) }' ) #设置全局变量 SelectParams(self.net_type) #构造inputs txins = [] utxo_owner_map = dict() for addr, utxos in founded_utxos.items(): assert addr in src_addrs_key_map, 'addr is not in src_addrs_key_map' for utxo in utxos: txin = CMutableTxIn( prevout=COutPoint(hash=lx(utxo['txid']), n=utxo['vout'])) txins.append(txin) #因为顺序不会被打乱, 所以可以直接使用 索引进行对应 utxo_owner_map[len(txins) - 1] = addr #构造outputs txouts = [] for to_addr, amount in dst_addrs_amount_map.items(): out = CMutableTxOut( nValue=amount * COIN, scriptPubKey=CBitcoinAddress(to_addr).to_scriptPubKey()) txouts.append(out) #自动结算 if auto_calc_pay_back: sum_in_satoshi = 0 for addr, utxos in founded_utxos.items(): for utxo in utxos: sum_in_satoshi += utxo['value'] pay_back_in_satoshi = int(sum_in_satoshi - int(total_amount * COIN)) #判断找零的金额, 如果太小就不要了, 作为矿工费 if pay_back_in_satoshi >= MIN_AVAILABLE_UTXO_VALUE_IN_SATOSHI: pay_back_addr = list(src_addrs_key_map.keys())[pay_back_index] pay_back_out = CMutableTxOut( nValue=pay_back_in_satoshi, scriptPubKey=CBitcoinAddress( pay_back_addr).to_scriptPubKey()) txouts.append(pay_back_out) muttx = CMutableTransaction(vin=txins, vout=txouts) #对每个 input 进行签名 for n in range(len(txins)): #查找这个utxo属于哪个地址 owner_addr = utxo_owner_map[n] privkey = src_addrs_key_map[owner_addr] if len(privkey) == 64: # hex wif_key = PrivKeyToWIFCompress(privkey, self.net_type != 'mainnet') seckey = CBitcoinSecret(s=wif_key) elif len(privkey) == 52: #base58格式 seckey = CBitcoinSecret(s=privkey) else: raise Exception("invalid privkey") txin_script_pubkey = CScript([ OP_DUP, OP_HASH160, Hash160(seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG ]) sig_hash = SignatureHash(txin_script_pubkey, muttx, n, SIGHASH_ALL) sig = seckey.sign(sig_hash) + bytes([SIGHASH_ALL]) muttx.vin[n].scriptSig = CScript([sig, seckey.pub]) #TODO: 处理验签失败抛异常 VerifyScript(muttx.vin[n].scriptSig, txin_script_pubkey, muttx, n, (SCRIPT_VERIFY_P2SH, )) pass raw_tx_hex = b2x(muttx.serialize()) self.logger.info(f'raw_tx_hex is: {raw_tx_hex}') # local_tx_hash = muttx.GetTxid() # self.logger.info(f'local_tx_hash is: {b2x(local_tx_hash)}') #广播交易 assert self.ping( ) == True, 'bitcoind rpc is gone' #测试 bitcoind的 rpc服务是否还在 txid = self.send_raw_tx(raw_tx_hex=raw_tx_hex) self.logger.info(f'send_raw_tx txid is: {txid}') return txid
def sign_our_inputs(self) -> None: assert self.tx is not None for idx, _in in enumerate(self.inputs): privkey = _in['privkey'] if privkey and 'sig' not in _in: print('signing our input for tx', self.tx.serialize().hex()) inkey = privkey_expand(privkey) inkey_pub = coincurve.PublicKey.from_secret(inkey.secret) # Really horrid hack to produce a signature for the # multisig utxo in tests/helpers.py if privkey == '38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf': redeemscript = bytes.fromhex('51210253cdf835e328346a4f19de099cf3d42d4a7041e073cd4057a1c4fd7cdbb1228f2103ae903722f21f85e651b8f9b18fc854084fb90eeb76452bdcfd0cb43a16a382a221036c264d68a9727afdc75949f7d7fa71910ae9ae8001a1fbffa6f7ce000976597c21036429fa8a4ef0b2b1d5cb553e34eeb90a32ab19fae1f0024f332ab4f74283a7282103d4232f19ea85051e7b76bf5f01d03e17eea8751463dee36d71413a739de1a92755ae') else: address = P2WPKHBitcoinAddress.from_scriptPubKey(CScript([script.OP_0, Hash160(inkey_pub.format())])) redeemscript = address.to_redeemScript() sighash = script.SignatureHash(redeemscript, self.tx, idx, script.SIGHASH_ALL, amount=_in['sats'], sigversion=script.SIGVERSION_WITNESS_V0) sig = inkey.sign(sighash, hasher=None) + bytes([script.SIGHASH_ALL]) if privkey == '38204720bc4f9647fd58c6d0a4bd3a6dd2be16d8e4273c4d1bdd5774e8c51eaf': _in['sig'] = CTxInWitness(CScriptWitness([bytes([]), sig, redeemscript])) else: _in['sig'] = CTxInWitness(CScriptWitness([sig, inkey_pub.format()]))
# hex and converts it to bytes. txid = lx('7e195aa3de827814f172c362fcf838d92ba10e3f9fdd9c3ecaf79522b311b22d') vout = 0 # Create the txin structure, which includes the outpoint. The scriptSig # defaults to being empty. txin = CMutableTxIn(COutPoint(txid, vout)) # We also need the scriptPubKey of the output we're spending because # SignatureHash() replaces the transaction scriptSig's with it. # # Here we'll create that scriptPubKey from scratch using the pubkey that # corresponds to the secret key we generated above. txin_scriptPubKey = CScript( [OP_DUP, OP_HASH160, Hash160(seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG]) # Create the txout. This time we create the scriptPubKey from a Bitcoin # address. txout = CMutableTxOut( 0.001 * COIN, CBitcoinAddress('1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8').to_scriptPubKey()) # Create the unsigned transaction. tx = CMutableTransaction([txin], [txout]) # Calculate the signature hash for that transaction. sighash = SignatureHash(txin_scriptPubKey, tx, 0, SIGHASH_ALL) # Now sign it. We have to append the type of signature we want to the end, in # this case the usual SIGHASH_ALL.
for n, (_, _, redeemer) in enumerate(self.prevouts): txin = CMutableTxIn.from_txin(tx.vin[n]) txin.scriptSig = redeemer.sign_spend(unsigned_tx, n) tx.vin[n] = CTxIn.from_txin(txin) print(b2x(tx.serialize())) bitcoin.SelectParams('regtest') # parameters spend = CBitcoinSecret("cVFfsB2h1KHgPEWtpXrnZ5qjk18xw2o2fuxCTaf7BN2Z5PSvhq4M") refund = CBitcoinSecret("cRKSxo1yJKP1RwaHULWaumNYiyXiQu2tRGdTmUxzP1s4YeSM4ks1") sec = b'A pair of boiled eggs for lunch?' sechash = Hash160(sec) rev = x('4a120469b397556363c4e47f45d8f81b381f721af89baba372425f820ae7077c') revhash = Hash160(rev) # the two scripts: paying me or paying them (or refunding to me) htlcA = HTLCScript(spend.pub, refund.pub, sechash, 230, revhash, 'pay', 10) htlcB = HTLCScript(spend.pub, refund.pub, sechash, 230, revhash, 'refund', 10) # the six ways of resolving the scripts: spending, refunding or revoking, # for both types of script htlcAs = HTLCScript(spend.pub, refund.pub, sechash, 230, revhash, 'pay',
class HTLCScript(Script): _h_nil = Hash160(b"") _h_1 = Hash160(b"\x01") def __init__(self, paypubkey, refundpubkey, secret, timeout, revoke, revoke_side, delay, privkey=None, secret_pre=None, revoke_pre=None, secret_type='sha'): assert revoke_side in ["pay", "refund"] assert secret_type == 'sha' assert len(secret) == 20 assert secret_pre is None or len(secret_pre) == 32 assert len(revoke) == 20 if revoke_pre is not None: assert len(revoke_pre) == 32 assert Hash160(revoke_pre) == revoke assert secret_pre is None assert privkey is not None assert privkey.pub == (paypubkey if revoke_side == "pay" else refundpubkey) elif secret_pre is not None: assert privkey is not None assert privkey.pub == paypubkey assert Hash160(secret_pre) == secret elif privkey is not None: assert privkey.pub == refundpubkey if self._h_nil not in [revoke, secret]: self.timeout_pre = OP_FALSE elif self._h_1 not in [revoke, secret]: self.timeout_pre = OP_TRUE else: self.timeout_pre = OP_2 self.paypubkey = paypubkey self.refundpubkey = refundpubkey self.secret = secret self.timeout = timeout self.privkey = privkey self.secret_pre = secret_pre self.secret_type = secret_type self.delay = delay self.revoke_side = revoke_side self.revoke = revoke self.revoke_pre = revoke_pre @property def script(self): if self.revoke_side == "pay": # ((revoke|secret) & pay) | (csv & cltv & refund) return CScript([ OP_HASH160, OP_DUP, self.secret, OP_EQUAL, OP_SWAP, self.revoke, OP_EQUAL, OP_BOOLOR, OP_IF, self.paypubkey, OP_ELSE, self.delay, OP_CSV, self.timeout, OP_CLTV, OP_2DROP, self.refundpubkey, OP_ENDIF, OP_CHECKSIG ]) else: # (csv & secret & pay) | ((revoke | cltv) & refund) return CScript([ OP_HASH160, OP_DUP, self.secret, OP_EQUAL, OP_IF, self.delay, OP_CSV, OP_2DROP, self.paypubkey, OP_ELSE, self.revoke, OP_EQUAL, OP_NOTIF, self.timeout, OP_CLTV, OP_DROP, OP_ENDIF, self.refundpubkey, OP_ENDIF, OP_CHECKSIG ]) def mutate_spend(self, tx, vin): if self.revoke_pre is None: if self.secret_pre is not None: self.bump_locktime(tx, self.timeout) if self.revoke_side == "pay": if self.secret_pre is None: self.bump_delay(tx, vin, self.delay) else: if self.secret_pre is not None: self.bump_delay(tx, vin, self.delay) def sign_spend(self, tx, vin): assert self.privkey is not None txhash = mkhash(self.script, tx, vin, SIGHASH_ALL) sig = mksig(self.privkey, txhash, SIGHASH_ALL) if self.revoke_pre is not None: return CScript([sig, self.revoke_pre, self.script]) elif self.secret_pre is not None: return CScript([sig, self.secret_pre, self.script]) else: return CScript([sig, self.timeout_pre, self.script])
OP_CHECKSEQUENCEVERIFY = OP_NOP3 #unlike other op-codes, python-bitcoinlib doesn't define OP_CHECKSEQUENCEVERIFY, so I define it myself here SelectParams('testnet') # parameterize these privKeySender = str(sys.argv[1]) addressReceiver = str(sys.argv[2]) secret = CBitcoinSecret(privKeySender) # per instructor message in the COMP541-01-S-2019 Moodle classroom forum, use '0xc800' (200 blocks) for relative timelock amount # 0xc800 is calculated as little-endian hex of 200, plus '00' tacked on the end because it's necessary to add sign for values which are less than half a byte, otherwise interpreted as negative redeem_script = CScript([ 0xc800, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_DUP, OP_HASH160, Hash160(secret.pub), OP_EQUALVERIFY, OP_CHECKSIG ]) txin_scriptPubKey = redeem_script.to_p2sh_scriptPubKey() txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) print('Pay to:', str(txin_p2sh_address)) proxy = bitcoin.rpc.Proxy() # the below functionality is to be run from cmd line in between send_to_p2sh_timelock.py and this program # uncomment to run programmatically for testing purposes # transactionid = proxy.sendtoaddress(str(txin_p2sh_address), 0.0001*COIN) # print('transaction id: ' + b2lx(transactionid)) # find the set of UTXO transactions that have been sent to the script address txins = [] # inputs to the spending transaction we will create
def hash_of_secret(): return Hash160(alice_secret_x)
def _unsigned_tx( self, side: Side) -> Tuple[CMutableTransaction, List[Optional[HTLC]]]: """Create the commitment transaction. Returns it and a list of matching HTLCs for each output """ ocn = self.obscured_commit_num( self.keyset[self.opener].raw_payment_basepoint(), self.keyset[not self.opener].raw_payment_basepoint(), self.commitnum) # BOLT #3: # ## Commitment Transaction # ... # * txin count: 1 # * `txin[0]` outpoint: `txid` and `output_index` from `funding_created` message # * `txin[0]` sequence: upper 8 bits are 0x80, lower 24 bits are upper 24 bits of the obscured commitment number # * `txin[0]` script bytes: 0 # * `txin[0]` witness: `0 <signature_for_pubkey1> <signature_for_pubkey2>` txin = CTxIn(COutPoint(bytes.fromhex(self.funding.txid), self.funding.output_index), nSequence=0x80000000 | (ocn >> 24)) # txouts, with ctlv_timeouts (for htlc output tiebreak) and htlc txouts: List[Tuple[CTxOut, int, Optional[HTLC]]] = [] for htlc in self.untrimmed_htlcs(side): if htlc.owner == side: redeemscript, sats = self._offered_htlc_output(htlc, side) else: redeemscript, sats = self._received_htlc_output(htlc, side) print("*** Got htlc redeemscript {} / {}".format( redeemscript, redeemscript.hex())) txouts.append( (CTxOut(sats, CScript([script.OP_0, sha256(redeemscript).digest()])), htlc.cltv_expiry, htlc)) num_untrimmed_htlcs = len(txouts) fee = self._fee(num_untrimmed_htlcs) out_redeemscript, sats = self._to_local_output(fee, side) if sats >= self.dust_limit[side]: txouts.append((CTxOut( sats, CScript([script.OP_0, sha256(out_redeemscript).digest()])), 0, None)) # BOLT #3: # #### `to_remote` Output # # This output sends funds to the other peer and thus is a simple # P2WPKH to `remotepubkey`. amount_to_other = self.amounts[not side] // 1000 if not side == self.opener: amount_to_other -= fee if amount_to_other >= self.dust_limit[side]: txouts.append((CTxOut( amount_to_other, CScript([ script.OP_0, Hash160(self.to_remote_pubkey(side).format()) ])), 0, None)) # BOLT #3: # ## Transaction Input and Output Ordering # # Lexicographic ordering: see # [BIP69](https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki). # In the case of identical HTLC outputs, the outputs are ordered in # increasing `cltv_expiry` order. # First sort by cltv_expiry txouts.sort(key=lambda txout: txout[1]) # Now sort by BIP69 txouts.sort(key=lambda txout: txout[0].serialize()) # BOLT #3: # ## Commitment Transaction # # * version: 2 # * locktime: upper 8 bits are 0x20, lower 24 bits are the # lower 24 bits of the obscured commitment number return (CMutableTransaction(vin=[txin], vout=[txout[0] for txout in txouts], nVersion=2, nLockTime=0x20000000 | (ocn & 0x00FFFFFF)), [txout[2] for txout in txouts])
def build_fund_tx(self, redeem_script): if type(self.send_amount) != decimal.Decimal: raise Exception("Please only use decimal types for the amount.") if self.send_amount < decimal.Decimal( coinbend.config["minimum_trade"]): raise Exception("Amount is too small.") #Because every Satoshi counts. decimal.getcontext().prec = 50 #Create a change address. if "change" not in self.key_pairs: self.key_pairs["change"] = self.key_pair_from_address( self.jsonrpc[self.my].getnewaddress(), self.my) #Get wallet balance. balance = self.jsonrpc[self.my].getbalance() #Check we have enough. if balance < self.send_amount: raise Exception("Insufficent balance to cover fund.") #List unclaimed outputs. unspent_outputs = self.jsonrpc[self.my].listunspent() #Setup tx inputs. change_amount = decimal.Decimal("0") txins = [] total = decimal.Decimal("0") indexes = [] i = 0 for unspent_output in unspent_outputs: #Skip outputs that don't satisfy min confirmations. if unspent_output["confirmations"] < self.minimum_confirmations: i += 1 continue #Check scriptPubKey is pay to pub key hash. if self.jsonrpc[self.my].decodescript( unspent_output["scriptPubKey"])["type"] != "pubkeyhash": i += 1 continue #Record key pairs. if unspent_output["address"] not in self.key_pairs: self.key_pairs[ unspent_output["address"]] = self.key_pair_from_address( unspent_output["address"], self.my) #Build new txin. txid = lx(unspent_output["txid"]) vout = unspent_output["vout"] txin = CTxIn(COutPoint(txid, vout)) txins.append(txin) indexes.append(i) total += unspent_output["amount"] i += 1 if total > self.send_amount: break if total == self.send_amount: break #Insufficent funds. if total < self.send_amount: raise Exception("Not enough valid inputs to fund contract.") #Calculate change. change = total - self.send_amount #Build txouts. txouts = [] redeem_script_hash160 = self.hash160_script(redeem_script) p2sh_script_pub_key = CScript( [OP_HASH160, redeem_script_hash160["bin"], OP_EQUAL]) txouts.append( CTxOut( (self.send_amount - decimal.Decimal(coinbend.config["mining_fee"]["standard"])) * COIN, p2sh_script_pub_key)) if change > decimal.Decimal("0"): change_seckey = CBitcoinSecret( self.key_pairs["change"]["priv"]["wif"]) change_script_pub_key = CScript([ OP_DUP, OP_HASH160, Hash160(change_seckey.pub), OP_EQUALVERIFY, OP_CHECKSIG ]) txouts.append(CTxOut(change * COIN, change_script_pub_key)) #Build unsigned transaction. tx = CTransaction(txins, txouts) unsigned_tx_hex = b2x(tx.serialize()) #Sign transaction. signed_tx_hex = self.jsonrpc[self.my].signrawtransaction( unsigned_tx_hex)["hex"] return signed_tx_hex
def hash160_script(self, script): bin_hash = Hash160(script["bin"]) return { "hex": binascii.hexlify(bin_hash).decode("ascii"), "bin": bin_hash }