def sign_tx(self, tx): """Sign tx that has been created by MiniWallet in P2PK mode""" assert self._priv_key is not None (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) assert err is None tx.vin[0].scriptSig = CScript([ self._priv_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) ])
def sign_tx(self, tx, fixed_length=True): """Sign tx that has been created by MiniWallet in P2PK mode""" assert self._priv_key is not None (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) assert err is None # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability): # 65 bytes: high-R val (33 bytes) + low-S val (32 bytes) # with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes der_sig = b'' while not len(der_sig) == 71: der_sig = self._priv_key.sign_ecdsa(sighash) if not fixed_length: break tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))])
def prepare_tx_signed_with_sighash(self, address_type, sighash_rangeproof_aware, attach_issuance): # Create a tx that is signed with a specific version of the sighash # method. # If `sighash_rangeproof_aware` is # true, the sighash will contain the rangeproofs if SIGHASH_RANGEPROOF is set # false, the sighash will NOT contain the rangeproofs if SIGHASH_RANGEPROOF is set addr = self.nodes[1].getnewaddress("", address_type) assert len(self.nodes[1].getaddressinfo(addr)["confidential_key"]) > 0 self.nodes[0].sendtoaddress(addr, 1.0) self.nodes[0].generate(1) self.sync_all() utxo = self.nodes[1].listunspent(1, 1, [addr])[0] utxo_tx = FromHex(CTransaction(), self.nodes[1].getrawtransaction(utxo["txid"])) utxo_spk = CScript(hex_str_to_bytes(utxo["scriptPubKey"])) utxo_value = utxo_tx.vout[utxo["vout"]].nValue assert len(utxo["amountblinder"]) > 0 sink_addr = self.nodes[2].getnewaddress() unsigned_hex = self.nodes[1].createrawtransaction([{ "txid": utxo["txid"], "vout": utxo["vout"] }], [{ sink_addr: 0.9 }, { "fee": 0.1 }]) if attach_issuance: # Attach a blinded issuance unsigned_hex = self.nodes[1].rawissueasset( unsigned_hex, [{ "asset_amount": 100, "asset_address": self.nodes[1].getnewaddress(), "token_amount": 100, "token_address": self.nodes[1].getnewaddress(), "blind": True, # FIXME: if blind=False, `blindrawtranaction` fails. Should fix this in a future PR }])[0]["hex"] blinded_hex = self.nodes[1].blindrawtransaction(unsigned_hex) blinded_tx = FromHex(CTransaction(), blinded_hex) signed_hex = self.nodes[1].signrawtransactionwithwallet( blinded_hex)["hex"] signed_tx = FromHex(CTransaction(), signed_hex) # Make sure that the tx the node produced is always valid. test_accept = self.nodes[0].testmempoolaccept([signed_hex])[0] assert test_accept["allowed"], "not accepted: {}".format( test_accept["reject-reason"]) # Prepare the keypair we need to re-sign the tx. wif = self.nodes[1].dumpprivkey(addr) (b, v) = base58_to_byte(wif) privkey = ECKey() privkey.set(b[0:32], len(b) == 33) pubkey = privkey.get_pubkey() # Now we need to replace the signature with an equivalent one with the new sighash set, # which we do using the Python logic to detect any forking changes in the sighash format. hashtype = SIGHASH_ALL | SIGHASH_RANGEPROOF if address_type == "legacy": if sighash_rangeproof_aware: (sighash, _) = LegacySignatureHash(utxo_spk, blinded_tx, 0, hashtype) else: (sighash, _) = LegacySignatureHash(utxo_spk, blinded_tx, 0, hashtype, enable_sighash_rangeproof=False) signature = privkey.sign_ecdsa(sighash) + chr(hashtype).encode( 'latin-1') assert len(signature) <= 0xfc assert len(pubkey.get_bytes()) <= 0xfc signed_tx.vin[0].scriptSig = CScript( struct.pack("<B", len(signature)) + signature + struct.pack("<B", len(pubkey.get_bytes())) + pubkey.get_bytes()) elif address_type == "bech32" or address_type == "p2sh-segwit": assert signed_tx.wit.vtxinwit[0].scriptWitness.stack[ 1] == pubkey.get_bytes() pubkeyhash = hash160(pubkey.get_bytes()) script = get_p2pkh_script(pubkeyhash) if sighash_rangeproof_aware: sighash = SegwitV0SignatureHash(script, blinded_tx, 0, hashtype, utxo_value) else: sighash = SegwitV0SignatureHash( script, blinded_tx, 0, hashtype, utxo_value, enable_sighash_rangeproof=False) signature = privkey.sign_ecdsa(sighash) + chr(hashtype).encode( 'latin-1') signed_tx.wit.vtxinwit[0].scriptWitness.stack[0] = signature else: assert False # Make sure that the tx we manually signed is valid signed_hex = signed_tx.serialize_with_witness().hex() test_accept = self.nodes[0].testmempoolaccept([signed_hex])[0] if sighash_rangeproof_aware: assert test_accept["allowed"], "not accepted: {}".format( test_accept["reject-reason"]) else: assert not test_accept["allowed"], "tx was accepted" if sighash_rangeproof_aware: signed_hex = self.nodes[1].signrawtransactionwithwallet( blinded_hex, [], "ALL|RANGEPROOF")["hex"] signed_tx = FromHex(CTransaction(), signed_hex) # Make sure that the tx that the node signed is valid test_accept = self.nodes[0].testmempoolaccept([signed_hex])[0] assert test_accept["allowed"], "not accepted: {}".format( test_accept["reject-reason"]) # Try re-signing with node 0, which should have no effect since the transaction was already complete signed_hex = self.nodes[0].signrawtransactionwithwallet( signed_hex)["hex"] test_accept = self.nodes[0].testmempoolaccept([signed_hex])[0] assert test_accept["allowed"], "not accepted: {}".format( test_accept["reject-reason"]) # Try signing using the PSBT interface psbt_hex = self.nodes[0].converttopsbt(unsigned_hex) signed_psbt = self.nodes[1].walletprocesspsbt( psbt_hex, True, "ALL|RANGEPROOF") extracted_tx = self.nodes[0].finalizepsbt(signed_psbt["psbt"]) assert extracted_tx["complete"] test_accept = self.nodes[0].testmempoolaccept( [extracted_tx["hex"]])[0] assert test_accept["allowed"], "not accepted: {}".format( test_accept["reject-reason"]) else: signed_tx.rehash() return signed_tx
def prepare_tx_signed_with_sighash(self, address_type, sighash_rangeproof_aware): # Create a tx that is signed with a specific version of the sighash # method. # If `sighash_rangeproof_aware` is # true, the sighash will contain the rangeproofs if SIGHASH_RANGEPROOF is set # false, the sighash will NOT contain the rangeproofs if SIGHASH_RANGEPROOF is set addr = self.nodes[1].getnewaddress("", address_type) assert len(self.nodes[1].getaddressinfo(addr)["confidential_key"]) > 0 self.nodes[0].sendtoaddress(addr, 1.0) self.nodes[0].generate(1) self.sync_all() utxo = self.nodes[1].listunspent(1, 1, [addr])[0] utxo_tx = FromHex(CTransaction(), self.nodes[1].getrawtransaction(utxo["txid"])) utxo_spk = CScript(hex_str_to_bytes(utxo["scriptPubKey"])) utxo_value = utxo_tx.vout[utxo["vout"]].nValue assert len(utxo["amountblinder"]) > 0 sink_addr = self.nodes[2].getnewaddress() unsigned_hex = self.nodes[1].createrawtransaction([{ "txid": utxo["txid"], "vout": utxo["vout"] }], { sink_addr: 0.9, "fee": 0.1 }) blinded_hex = self.nodes[1].blindrawtransaction(unsigned_hex) blinded_tx = FromHex(CTransaction(), blinded_hex) signed_hex = self.nodes[1].signrawtransactionwithwallet( blinded_hex)["hex"] signed_tx = FromHex(CTransaction(), signed_hex) # Make sure that the tx the node produced is always valid. test_accept = self.nodes[0].testmempoolaccept([signed_hex])[0] assert test_accept["allowed"], "not accepted: {}".format( test_accept["reject-reason"]) # Prepare the keypair we need to re-sign the tx. wif = self.nodes[1].dumpprivkey(addr) (b, v) = base58_to_byte(wif) privkey = ECKey() privkey.set(b[0:32], len(b) == 33) pubkey = privkey.get_pubkey() # Now we need to replace the signature with an equivalent one with the new sighash set. hashtype = SIGHASH_ALL | SIGHASH_RANGEPROOF if address_type == "legacy": if sighash_rangeproof_aware: (sighash, _) = LegacySignatureHash(utxo_spk, blinded_tx, 0, hashtype) else: (sighash, _) = LegacySignatureHash(utxo_spk, blinded_tx, 0, hashtype, enable_sighash_rangeproof=False) signature = privkey.sign_ecdsa(sighash) + chr(hashtype).encode( 'latin-1') assert len(signature) <= 0xfc assert len(pubkey.get_bytes()) <= 0xfc signed_tx.vin[0].scriptSig = CScript( struct.pack("<B", len(signature)) + signature + struct.pack("<B", len(pubkey.get_bytes())) + pubkey.get_bytes()) elif address_type == "bech32" or address_type == "p2sh-segwit": assert signed_tx.wit.vtxinwit[0].scriptWitness.stack[ 1] == pubkey.get_bytes() pubkeyhash = hash160(pubkey.get_bytes()) script = get_p2pkh_script(pubkeyhash) if sighash_rangeproof_aware: sighash = SegwitV0SignatureHash(script, blinded_tx, 0, hashtype, utxo_value) else: sighash = SegwitV0SignatureHash( script, blinded_tx, 0, hashtype, utxo_value, enable_sighash_rangeproof=False) signature = privkey.sign_ecdsa(sighash) + chr(hashtype).encode( 'latin-1') signed_tx.wit.vtxinwit[0].scriptWitness.stack[0] = signature else: assert False signed_tx.rehash() return signed_tx