def p2sh_address(cls, addr_fmt, witdeem_script): # Multisig and general P2SH support # - witdeem => witness script for segwit, or redeem script otherwise # - redeem script can be generated from witness script if needed. # - this function needs a witdeem script to be provided, not simple to make # - more verification needed to prove it's change/included address (NOT HERE) # - reference: <https://bitcoincore.org/en/segwit_wallet_dev/> # - returns: str(address) assert addr_fmt & AFC_SCRIPT, 'for p2sh only' assert witdeem_script, "need witness/redeem script" if addr_fmt & AFC_SEGWIT: digest = tcc.sha256(witdeem_script).digest() else: digest = hash160(witdeem_script) if addr_fmt & AFC_BECH32: # bech32 encoded segwit p2sh addr = tcc.codecs.bech32_encode(cls.bech32_hrp, 0, digest) elif addr_fmt == AF_P2WSH_P2SH: # segwit p2wsh encoded as classic P2SH addr = tcc.codecs.b58_encode(cls.b58_script + hash160(b'\x00\x20' + digest)) else: # P2SH classic addr = tcc.codecs.b58_encode(cls.b58_script + digest) return addr
def address(cls, node, addr_fmt): # return a human-readable, properly formatted address if addr_fmt == AF_CLASSIC: # olde fashioned P2PKH assert len(cls.b58_addr) == 1 return node.address(cls.b58_addr[0]) if addr_fmt & AFC_SCRIPT: # use p2sh_address() instead. raise ValueError(hex(addr_fmt)) # so must be P2PKH, fetch it. assert addr_fmt & AFC_PUBKEY raw = node.address_raw() assert len(raw) == 20 if addr_fmt & AFC_BECH32: # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) # see bip-141, "P2WPKH nested in BIP16 P2SH" section assert addr_fmt == AF_P2WPKH_P2SH assert len(cls.b58_script) == 1 digest = hash160(b'\x00\x14' + raw) return tcc.codecs.b58_encode(cls.b58_script + digest)
def address(cls, node, addr_fmt): # return a spending address if addr_fmt == AF_CLASSIC: # olde fashioned P2PKH assert len(cls.b58_addr) == 1 return node.address(cls.b58_addr[0]) if addr_fmt & AFC_SCRIPT: # TODO: no multisig support yet (wrapped segwit doesn't count) # - we'd need more info than we have here anyway raise ValueError(hex(addr_fmt)) # so must be P2PKH, fetch it. assert addr_fmt & AFC_PUBKEY raw = node.address_raw() assert len(raw) == 20 if addr_fmt & AFC_BECH32: # bech32 encoded segwit p2pkh return tcc.codecs.bech32_encode(cls.bech32_hrp, 0, raw) # see bip-141, "P2WPKH nested in BIP16 P2SH" section assert addr_fmt == AF_P2WPKH_P2SH assert len(cls.b58_script) == 1 digest = hash160(b'\x00\x14' + raw) return tcc.codecs.b58_encode(cls.b58_script + digest)
def determine_my_signing_key(self, utxo): # See what it takes to sign this particular input # - type of script # - which pubkey needed # - scriptSig value addr_type, addr_or_pubkey, self.is_segwit = utxo.get_address() which_key = None self.is_multisig = False self.is_p2sh = False self.amount = utxo.nValue if addr_type == 'p2sh': # multisig input self.is_p2sh = True # we must have the redeem script already (else fail) if not self.redeem_script: raise AssertionError("missing redeem script for in #%d" % self.my_index) redeem_script = self.get(self.redeem_script) self.scriptSig = ser_string(redeem_script) # new cheat: psbt creator probably telling us exactly what key # to use, by providing exactly one. This is ideal for p2sh wrapped p2pkh if len(self.subpaths) == 1: which_key, = self.subpaths.keys() else: # messy P2SH multisig guessing? ws = self.get(self.witness_script ) if self.witness_script else redeem_script for pubkey in self.subpaths: if pubkey in ws: # limitations: # - we could be holding multiple legs of the P2SH # - text match like this could be fooled w/ crafting which_key = pubkey break if not self.is_segwit and \ len(redeem_script) == 22 and \ redeem_script[0] == 0 and redeem_script[1] == 20: # it's actually segwit p2pkh inside p2sh addr_type = 'p2wpkh-p2sh' addr = redeem_script[2:22] self.is_segwit = True else: # multiple keys involved, we probably can't do the finalize step self.is_multisig = True elif addr_type == 'p2pkh': # input is hash160 of a single public key self.scriptSig = utxo.scriptPubKey addr = addr_or_pubkey for pubkey in self.subpaths: if hash160(pubkey) == addr: which_key = pubkey break elif addr_type == 'p2pk': # input is single public key (less common) self.scriptSig = utxo.scriptPubKey assert len(addr_or_pubkey) == 33 if addr_or_pubkey in self.subpaths: which_key = addr_or_pubkey else: # we don't know how to "solve" this type of input pass if not which_key: print( "no key: input #%d: type=%s segwit=%d a_or_pk=%s scriptPubKey=%s" % (self.my_index, addr_type, self.is_segwit, b2a_hex(addr_or_pubkey), b2a_hex(utxo.scriptPubKey))) self.required_key = which_key if self.is_segwit: if ('pkh' in addr_type): # This comment from <https://bitcoincore.org/en/segwit_wallet_dev/>: # # Please note that for a P2SH-P2WPKH, the scriptCode is always 26 # bytes including the leading size byte, as 0x1976a914{20-byte keyhash}88ac, # NOT the redeemScript nor scriptPubKey # # Also need this scriptCode for native segwit p2pkh # assert not self.is_multisig self.scriptCode = b'\x19\x76\xa9\x14' + addr + b'\x88\xac' elif not self.scriptCode: # Segwit P2SH segwit. We need the script! if not self.witness_script: raise AssertionError('Need witness script for input #%d' % self.my_index) self.scriptCode = self.get(self.witness_script)
def validate(self, out_idx, txo, my_xfp): # do things make sense? assert self.my_index == out_idx # We might be a change output, because the PSBT # creator has given a key path. However, we must be # **very** careful and validate this fully. # - no output info is needed, in general, so # any output info provided better be right, or fail # - full key derivation and validation elsewhere, but critical. # - we raise a fraud alarm, since these are not innocent errors # self.is_change = False if not self.subpaths: return ours = self.parse_subpaths(my_xfp, first_known=True) # - must be exactly one of our keys here (extras ignored, not-ours ignored) # - not considered fraud because other signers looking at PSBT may have them if ours == None: return expect_pubkey = ours[0] # - must match expected address for this output, coming from unsigned txn addr_type, addr_or_pubkey, is_segwit = txo.get_address() if addr_type == 'p2pk': # output is public key (not a hash, much less common) assert len(addr_or_pubkey) == 33 if addr_or_pubkey != expect_pubkey: raise FraudulentChangeOutput( "Output#%d: P2PK change output is fraudulent" % self.my_index) self.is_change = ours return expect_pkh = hash160(expect_pubkey) pkh = None if addr_type == 'p2sh': # multisig output # we must have the redeem script already (else fail) if not self.redeem_script: # perhaps an omission, so let's not call fraud on it raise AssertionError("Missing redeem script for output #%d" % self.my_index) redeem_script = self.get(self.redeem_script) if not is_segwit and \ len(redeem_script) == 22 and \ redeem_script[0] == 0 and redeem_script[1] == 20: # it's actually segwit p2pkh inside p2sh pkh = redeem_script[2:22] else: # multiple keys involved, not supported # TODO multisig support raise AssertionError( "Not ready for multisig/p2wsh change outputs") elif addr_type == 'p2pkh': # input is hash160 of a single public key assert len(addr_or_pubkey) == 20 pkh = addr_or_pubkey else: # we don't know how to "solve" this type of input return if pkh != expect_pkh: raise FraudulentChangeOutput( "Output#%d: P2PKH change output is fraudulent" % self.my_index) self.is_change = ours
async def doit(self, *a, have_key=None): # make the wallet. from main import dis try: from chains import current_chain import tcc from serializations import hash160 from stash import blank_object if not have_key: # get some random bytes await ux_dramatic_pause("Picking key...", 2) privkey = tcc.secp256k1.generate_secret() else: # caller must range check this already: 0 < privkey < order privkey = have_key # calculate corresponding public key value pubkey = tcc.secp256k1.publickey(privkey, True) # always compressed style dis.fullscreen("Rendering...") # make payment address digest = hash160(pubkey) ch = current_chain() if self.is_segwit: addr = tcc.codecs.bech32_encode(ch.bech32_hrp, 0, digest) else: addr = tcc.codecs.b58_encode(ch.b58_addr + digest) wif = tcc.codecs.b58_encode(ch.b58_privkey + privkey + b'\x01') if self.can_do_qr(): with imported('uqr') as uqr: # make the QR's now, since it's slow is_alnum = self.is_segwit qr_addr = uqr.make( addr if not is_alnum else addr.upper(), min_version=4, max_version=4, encoding=(uqr.Mode_ALPHANUMERIC if is_alnum else 0)) qr_wif = uqr.make(wif, min_version=4, max_version=4, encoding=uqr.Mode_BYTE) else: qr_addr = None qr_wif = None # Use address as filename. clearly will be unique, but perhaps a bit # awkward to work with. basename = addr dis.fullscreen("Saving...") with CardSlot() as card: fname, nice_txt = card.pick_filename( basename + ('-note.txt' if self.template_fn else '.txt')) with open(fname, 'wt') as fp: self.make_txt(fp, addr, wif, privkey, qr_addr, qr_wif) if self.template_fn: fname, nice_pdf = card.pick_filename(basename + '.pdf') with open(fname, 'wb') as fp: self.make_pdf(fp, addr, wif, qr_addr, qr_wif) else: nice_pdf = '' # Half-hearted attempt to cleanup secrets-contaminated memory # - better would be force user to reboot # - and yet, we just output the WIF to SDCard anyway blank_object(privkey) blank_object(wif) del qr_wif except CardMissingError: await needs_microsd() return except Exception as e: await ux_show_story('Failed to write!\n\n\n' + str(e)) return await ux_show_story('Done! Created file(s):\n\n%s\n\n%s' % (nice_txt, nice_pdf))
def sign_tx(self, tx): # Create a transaction with all scriptsigs blanekd out blank_tx = CTransaction(tx.tx) for txin in blank_tx.vin: txin.scriptSig = b"" # Get the master key fingerprint master_fp = get_xpub_fingerprint( json.loads(self.get_pubkey_at_path('m/0'))['xpub']) # create sighashes sighash_tuples = [] for txin, psbt_in, i_num in zip(blank_tx.vin, tx.inputs, range(len(blank_tx.vin))): sighash = b"" pubkeys = [] if psbt_in.non_witness_utxo: utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n] # Check if P2SH if utxo.is_p2sh(): # Look up redeemscript redeemscript = tx.redeem_scripts[utxo.scriptPubKey[2:22]] # Add to blank_tx txin.scriptSig = redeemscript # Find which pubkeys to sign with for this input for pubkey in tx.hd_keypaths.keys(): if pubkey in redeemscript: pubkeys.append(pubkey) # Check if P2PKH elif utxo.is_p2pkh() or utxo.is_p2pk(): txin.scriptSig = psbt_in.non_witness_utxo.vout[ txin.prevout.n].scriptPubKey # Find which pubkeys to sign with for this input for pubkey in tx.hd_keypaths.keys(): if utxo.is_p2pk and pubkey in utxo.scriptPubKey: pubkeys.append(pubkey) if utxo.is_p2pkh and hash160( pubkey) in utxo.scriptPubKey: pubkeys.append(pubkey) # We don't know what this is, skip it else: continue # Serialize and add sighash ALL ser_tx = blank_tx.serialize_without_witness() ser_tx += b"\x01\x00\x00\x00" print(binascii.hexlify(ser_tx)) # Hash it sighash += hash256(ser_tx) print(binascii.hexlify(sighash)) txin.scriptSig = b"" elif psbt_in.witness_utxo: # Calculate hashPrevouts and hashSequence prevouts_preimage = b"" sequence_preimage = b"" for inputs in blank_tx.vin: prevouts_preimage += inputs.prevout.serialize() sequence_preimage += struct.pack("<I", inputs.nSequence) hashPrevouts = hash256(prevouts_preimage) hashSequence = hash256(sequence_preimage) # Calculate hashOutputs outputs_preimage = b"" for output in blank_tx.vout: outputs_preimage += output.serialize() hashOutputs = hash256(outputs_preimage) # Get the scriptCode scriptCode = b"" witness_program = b"" if psbt_in.witness_utxo.is_p2sh(): # Look up redeemscript redeemscript = tx.redeem_scripts[ psbt_in.witness_utxo.scriptPubKey[2:22]] witness_program += redeemscript else: witness_program += psbt_in.witness_utxo.scriptPubKey # Check if witness_program is script hash if len(witness_program) == 34 and witness_program[ 0] == OP_0 and witness_program[1] == 0x20: # look up witnessscript and set as scriptCode witnessscript = tx.witness_scripts[redeemscript[2:]] scriptCode += witnessscript else: scriptCode += b"\x19\x76\xa9\x14" scriptCode += redeemscript[2:] scriptCode += b"\x88\xac" # Find pubkeys to sign with in the scriptCode # Find which pubkeys to sign with for this input for pubkey in tx.hd_keypaths.keys(): if hash160(pubkey) in scriptCode or pubkey in scriptCode: pubkeys.append(pubkey) # Make sighash preimage preimage = b"" preimage += struct.pack("<i", blank_tx.nVersion) preimage += hashPrevouts preimage += hashSequence preimage += txin.prevout.serialize() preimage += scriptCode preimage += psbt_in.witness_utxo.nValue preimage += txin.nSequence preimage += hashOutputs preimage += struct.pack("<I", tx.tx.nLockTime) preimage += b"\x01\x00\x00\x00" # hash it sighash += hash256(preimage) # Figure out which keypath thing is for this input for pubkey in pubkeys: keypath = tx.hd_keypaths[pubkey] print(master_fp) print(keypath[0]) if master_fp == keypath[0]: # Add the keypath strings keypath_str = 'm/' for index in keypath[1:]: keypath_str += str(index) + "/" # Create tuples and add to List tup = (binascii.hexlify(sighash), keypath_str, i_num, pubkey) sighash_tuples.append(tup) # Sign the sighashes to_send = '{"sign":{"data":[' for tup in sighash_tuples: to_send += '{"hash":"' to_send += tup[0] to_send += '","keypath":"' to_send += tup[1] to_send += '"},' to_send = to_send[:-1] to_send += ']}}' print(to_send) reply = send_encrypt(to_send, self.password, self.device) print(reply) if 'error' in reply: return print( "Touch the device for 3 seconds to sign. Touch briefly to cancel") reply = send_encrypt(to_send, self.password, self.device) print(reply) if 'error' in reply: return # Extract sigs sigs = [] for item in reply['sign']: sigs.append(binascii.unhexlify(item['sig'])) # Make sigs der der_sigs = [] for sig in sigs: der_sigs.append(ser_sig_der(sig[0:32], sig[32:64])) # add sigs to tx for tup, sig in zip(sighash_tuples, der_sigs): tx.inputs[tup[2]].partial_sigs[tup[3]] = sig # For each input, finalize only p2pkh and p2pk for txin, psbt_in in zip(tx.tx.vin, tx.inputs): if psbt_in.non_witness_utxo: utxo = psbt_in.non_witness_utxo.vout[txin.prevout.n] if utxo.is_p2pkh: txin.scriptSig = struct.pack( "B", len(psbt_in.partial_sigs.values()[0]) ) + psbt_in.partial_sigs.values()[0] + struct.pack( "B", len(psbt_in.partial_sigs.keys() [0])) + psbt_in.partial_sigs.keys()[0] elif utxo.is_p2pk: txin.scriptSig = truct.pack( "B", len(psbt_in.partial_sigs.values() [0])) + psbt_in.partial_sigs.values()[0] psbt_in.set_null() # Extract sigs sigs = [] for item in reply['sign']: sigs.append(binascii.unhexlify(item['sig'])) # Make sigs der der_sigs = [] for sig in sigs: der_sigs.append(ser_sig_der(sig[0:32], sig[32:64])) # add sigs to tx for tup, sig in zip(sighash_tuples, der_sigs): tx.inputs[tup[2]].partial_sigs[tup[3]] = sig return tx.serialize()