def test_version_bytes(self): xprv_headers_b58 = { 'standard': 'tprv', 'p2wpkh-p2sh': 'uprv', 'p2wsh-p2sh': 'Uprv', 'p2wpkh': 'vprv', 'p2wsh': 'Vprv', } xpub_headers_b58 = { 'standard': 'tpub', 'p2wpkh-p2sh': 'upub', 'p2wsh-p2sh': 'Upub', 'p2wpkh': 'vpub', 'p2wsh': 'Vpub', } for xtype, xkey_header_bytes in constants.net.XPRV_HEADERS.items(): xkey_header_bytes = bfh("%08x" % xkey_header_bytes) xkey_bytes = xkey_header_bytes + bytes([0] * 74) xkey_b58 = EncodeBase58Check(xkey_bytes) self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) xkey_bytes = xkey_header_bytes + bytes([255] * 74) xkey_b58 = EncodeBase58Check(xkey_bytes) self.assertTrue(xkey_b58.startswith(xprv_headers_b58[xtype])) for xtype, xkey_header_bytes in constants.net.XPUB_HEADERS.items(): xkey_header_bytes = bfh("%08x" % xkey_header_bytes) xkey_bytes = xkey_header_bytes + bytes([0] * 74) xkey_b58 = EncodeBase58Check(xkey_bytes) self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype])) xkey_bytes = xkey_header_bytes + bytes([255] * 74) xkey_b58 = EncodeBase58Check(xkey_bytes) self.assertTrue(xkey_b58.startswith(xpub_headers_b58[xtype]))
def tx_inputs(self, tx, for_sig=False): inputs = [] for txin in tx.inputs(): txinputtype = self.types.TxInputType() if txin['type'] == 'coinbase': prev_hash = b"\x00"*32 prev_index = 0xffffffff # signed int -1 else: if for_sig: x_pubkeys = txin['x_pubkeys'] if len(x_pubkeys) == 1: x_pubkey = x_pubkeys[0] xpub, s = parse_xpubkey(x_pubkey) xpub_n = self.client_class.expand_path(self.xpub_path[xpub]) txinputtype._extend_address_n(xpub_n + s) txinputtype.script_type = self.get_safet_input_script_type(txin['type']) else: def f(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) return self._make_node_path(xpub, s) pubkeys = list(map(f, x_pubkeys)) multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=list(map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures'))), m=txin.get('num_sig'), ) script_type = self.get_safet_input_script_type(txin['type']) txinputtype = self.types.TxInputType( script_type=script_type, multisig=multisig ) # find which key is mine for x_pubkey in x_pubkeys: if is_xpubkey(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) if xpub in self.xpub_path: xpub_n = self.client_class.expand_path(self.xpub_path[xpub]) txinputtype._extend_address_n(xpub_n + s) break prev_hash = unhexlify(txin['prevout_hash']) prev_index = txin['prevout_n'] if 'value' in txin: txinputtype.amount = txin['value'] txinputtype.prev_hash = prev_hash txinputtype.prev_index = prev_index if txin.get('scriptSig') is not None: script_sig = bfh(txin['scriptSig']) txinputtype.script_sig = script_sig txinputtype.sequence = txin.get('sequence', 0xffffffff - 1) inputs.append(txinputtype) return inputs
def parse_script(self, x): script = '' for word in x.split(): if word[0:3] == 'OP_': opcode_int = opcodes[word] assert opcode_int < 256 # opcode is single-byte script += bitcoin.int_to_hex(opcode_int) else: bfh(word) # to test it is hex data script += push_script(word) return script
def parse_script(self, x): from electrum.transaction import opcodes, push_script script = '' for word in x.split(): if word[0:3] == 'OP_': assert word in opcodes.lookup opcode_int = opcodes.lookup[word] assert opcode_int < 256 # opcode is single-byte script += bitcoin.int_to_hex(opcode_int) else: bfh(word) # to test it is hex data script += push_script(word) return script
def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if wallet.has_password(): password = window.password_dialog('An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.') if not password: return else: password = None if not window.question(_("An encrypted transaction was retrieved from cosigning pool.\nDo you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bh2u(bitcoin.deserialize_xprv(xprv)[-1]) EC = bitcoin.EC_KEY(bfh(k)) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message(str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def f(x_pubkey): if is_xpubkey(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) else: xpub = xpub_from_pubkey(0, bfh(x_pubkey)) s = [] return self._make_node_path(xpub, s)
def get_noise_map(cls, versioned_seed: VersionedSeed) -> Dict[Tuple[int, int], int]: """Returns a map from (x,y) coordinate to pixel value 0/1, to be used as rawnoise.""" w, h = cls.SIZE version = versioned_seed.version hex_seed = versioned_seed.seed checksum = versioned_seed.checksum noise_map = {} if version == '0': random.seed(int(hex_seed, 16)) for x in range(w): for y in range(h): noise_map[(x, y)] = random.randint(0, 1) elif version == '1': prng_seed = bfh(hex_seed + version + checksum) drbg = DRBG(prng_seed) num_noise_bytes = 1929 # ~ w*h noise_array = bin(int.from_bytes(drbg.generate(num_noise_bytes), 'big'))[2:] # there's an approx 1/1024 chance that the generated number is 'too small' # and we would get IndexError below. easiest backwards compat fix: noise_array += '0' * (w * h - len(noise_array)) i = 0 for x in range(w): for y in range(h): noise_map[(x, y)] = int(noise_array[i]) i += 1 else: raise Exception(f"unexpected revealer version: {version}") return noise_map
def xfp_from_xpub(xpub): # sometime we need to BIP32 fingerprint value: 4 bytes of ripemd(sha256(pubkey)) # UNTESTED kk = bfh(Xpub.get_pubkey_from_xpub(xpub, [])) assert len(kk) == 33 xfp, = unpack('<I', hash_160(kk)[0:4]) return xfp
def show_qr(self): text = bfh(str(self.tx)) text = base_encode(text, base=43) try: self.main_window.show_qrcode(text, 'Transaction', parent=self) except Exception as e: self.show_message(str(e))
def test_base43(self): tx_hex = "020000000001021cd0e96f9ca202e017ca3465e3c13373c0df3a4cdd91c1fd02ea42a1a65d2a410000000000fdffffff757da7cf8322e5063785e2d8ada74702d2648fa2add2d533ba83c52eb110df690200000000fdffffff02d07e010000000000160014b544c86eaf95e3bb3b6d2cabb12ab40fc59cad9ca086010000000000232102ce0d066fbfcf150a5a1bbc4f312cd2eb080e8d8a47e5f2ce1a63b23215e54fb5ac02483045022100a9856bf10a950810abceeabc9a86e6ba533e130686e3d7863971b9377e7c658a0220288a69ef2b958a7c2ecfa376841d4a13817ed24fa9a0e0a6b9cb48e6439794c701210324e291735f83ff8de47301b12034950b80fa4724926a34d67e413d8ff8817c53024830450221008f885978f7af746679200ed55fe2e86c1303620824721f95cc41eb7965a3dfcf02207872082ac4a3c433d41a203e6d685a459e70e551904904711626ac899238c20a0121023d4c9deae1aacf3f822dd97a28deaec7d4e4ff97be746d124a63d20e582f5b290a971600" tx_bytes = bfh(tx_hex) tx_base43 = base_encode(tx_bytes, 43) self.assertEqual("3E2DH7.J3PKVZJ3RCOXQVS3Y./6-WE.75DDU0K58-0N1FRL565N8ZH-DG1Z.1IGWTE5HK8F7PWH5P8+V3XGZZ6GQBPHNDE+RD8CAQVV1/6PQEMJIZTGPMIJ93B8P$QX+Y2R:TGT9QW8S89U4N2.+FUT8VG+34USI/N/JJ3CE*KLSW:REE8T5Y*9:U6515JIUR$6TODLYHSDE3B5DAF:5TF7V*VAL3G40WBOM0DO2+CFKTTM$G-SO:8U0EW:M8V:4*R9ZDX$B1IRBP9PLMDK8H801PNTFB4$HL1+/U3F61P$4N:UAO88:N5D+J:HI4YR8IM:3A7K1YZ9VMRC/47$6GGW5JEL1N690TDQ4XW+TWHD:V.1.630QK*JN/.EITVU80YS3.8LWKO:2STLWZAVHUXFHQ..NZ0:.J/FTZM.KYDXIE1VBY7/:PHZMQ$.JZQ2.XT32440X/HM+UY/7QP4I+HTD9.DUSY-8R6HDR-B8/PF2NP7I2-MRW9VPW3U9.S0LQ.*221F8KVMD5ANJXZJ8WV4UFZ4R.$-NXVE+-FAL:WFERGU+WHJTHAP", tx_base43) self.assertEqual(tx_bytes, base_decode(tx_base43, None, 43))
def f(x_pubkey): if is_xpubkey(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) else: xpub = xpub_from_pubkey(0, bfh(x_pubkey)) s = [] node = self.ckd_public.deserialize(xpub) return self.types.HDNodePathType(node=node, address_n=s)
def test_base58(self): data_hex = '0cd394bef396200774544c58a5be0189f3ceb6a41c8da023b099ce547dd4d8071ed6ed647259fba8c26382edbf5165dfd2404e7a8885d88437db16947a116e451a5d1325e3fd075f9d370120d2ab537af69f32e74fc0ba53aaaa637752964b3ac95cfea7' data_bytes = bfh(data_hex) data_base58 = base_encode(data_bytes, 58) self.assertEqual("VuvZ2K5UEcXCVcogny7NH4Evd9UfeYipsTdWuU4jLDhyaESijKtrGWZTFzVZJPjaoC9jFBs3SFtarhDhQhAxkXosUD8PmUb5UXW1tafcoPiCp8jHy7Fe2CUPXAbYuMvAyrkocbe6", data_base58) self.assertEqual(data_bytes, base_decode(data_base58, None, 58))
def test_base58check(self): data_hex = '0cd394bef396200774544c58a5be0189f3ceb6a41c8da023b099ce547dd4d8071ed6ed647259fba8c26382edbf5165dfd2404e7a8885d88437db16947a116e451a5d1325e3fd075f9d370120d2ab537af69f32e74fc0ba53aaaa637752964b3ac95cfea7' data_bytes = bfh(data_hex) data_base58check = EncodeBase58Check(data_bytes) self.assertEqual("4GCCJsjHqFbHxWbFBvRg35cSeNLHKeNqkXqFHW87zRmz6iP1dJU9Tk2KHZkoKj45jzVsSV4ZbQ8GpPwko6V3Z7cRfux3zJhUw7TZB6Kpa8Vdya8cMuUtL5Ry3CLtMetaY42u52X7Ey6MAH", data_base58check) self.assertEqual(data_bytes, DecodeBase58Check(data_base58check))
def sign_transaction(self, keystore, tx, prev_tx, xpub_path): prev_tx = { bfh(txhash): self.electrum_tx_to_txtype(tx, xpub_path) for txhash, tx in prev_tx.items() } client = self.get_client(keystore) inputs = self.tx_inputs(tx, xpub_path, True) outputs = self.tx_outputs(keystore.get_derivation(), tx) details = SignTx(lock_time=tx.locktime, version=tx.version) signatures, _ = client.sign_tx(self.get_coin_name(), inputs, outputs, details=details, prev_txes=prev_tx) signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def write_kv(ktype, val, key=b''): # serialize helper: write w/ size and key byte out_fd.write(my_var_int(1 + len(key))) out_fd.write(bytes([ktype]) + key) if isinstance(val, str): val = bfh(val) out_fd.write(my_var_int(len(val))) out_fd.write(val)
def trezor_validate_op_return_output_and_get_data(output: TxOutput) -> bytes: if output.type != TYPE_SCRIPT: raise Exception("Unexpected output type: {}".format(output.type)) script = bfh(output.address) if not (script[0] == opcodes.OP_RETURN and script[1] == len(script) - 2 and script[1] <= 75): raise UserFacingException(_("Only OP_RETURN scripts, with one constant push, are supported.")) if output.value != 0: raise UserFacingException(_("Amount for OP_RETURN output must be zero.")) return script[2:]
def show_qr(self): text = bfh(str(self.tx)) text = base_encode(text, base=43) try: self.main_window.show_qrcode(text, 'Transaction', parent=self) except qrcode.exceptions.DataOverflowError: self.show_error(_('Failed to display QR code.') + '\n' + _('Transaction is too large in size.')) except Exception as e: self.show_error(_('Failed to display QR code.') + '\n' + str(e))
def electrum_tx_to_txtype(self, tx): t = self.types.TransactionType() d = deserialize(tx.raw) t.version = d['version'] t.lock_time = d['lockTime'] inputs = self.tx_inputs(tx) t.inputs.extend(inputs) for vout in d['outputs']: o = t.bin_outputs.add() o.amount = vout['value'] o.script_pubkey = bfh(vout['scriptPubKey']) return t
def do_send(self, tx): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii') try: server.put(_hash, message) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message("Failed to send transaction to cosigning pool.") return window.show_message("Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it.")
def tx_inputs(self, tx, xpub_path, for_sig=False): inputs = [] for txin in tx.inputs(): txinputtype = TxInputType() if txin['type'] == 'coinbase': prev_hash = b"\x00"*32 prev_index = 0xffffffff # signed int -1 else: if for_sig: x_pubkeys = txin['x_pubkeys'] xpubs = [parse_xpubkey(x) for x in x_pubkeys] multisig = self._make_multisig(txin.get('num_sig'), xpubs, txin.get('signatures')) script_type = self.get_trezor_input_script_type(txin['type']) txinputtype = TxInputType( script_type=script_type, multisig=multisig) # find which key is mine for xpub, deriv in xpubs: if xpub in xpub_path: xpub_n = parse_path(xpub_path[xpub]) txinputtype.address_n = xpub_n + deriv break prev_hash = bfh(txin['prevout_hash']) prev_index = txin['prevout_n'] if 'value' in txin: txinputtype.amount = txin['value'] txinputtype.prev_hash = prev_hash txinputtype.prev_index = prev_index if txin.get('scriptSig') is not None: script_sig = bfh(txin['scriptSig']) txinputtype.script_sig = script_sig txinputtype.sequence = txin.get('sequence', 0xffffffff - 1) inputs.append(txinputtype) return inputs
def test_sign_transaction(self): eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d')) sig1 = eckey1.sign_transaction(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94')) self.assertEqual(bfh('3045022100902a288b98392254cd23c0e9a49ac6d7920f171b8249a48e484b998f1874a2010220723d844826828f092cf400cb210c4fa0b8cd1b9d1a7f21590e78e022ff6476b9'), sig1) eckey2 = ecc.ECPrivkey(bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23')) sig2 = eckey2.sign_transaction(bfh('642a2e66332f507c92bda910158dfe46fc10afbf72218764899d3af99a043fac')) self.assertEqual(bfh('30440220618513f4cfc87dde798ce5febae7634c23e7b9254a1eabf486be820f6a7c2c4702204fef459393a2b931f949e63ced06888f35e286e446dc46feb24b5b5f81c6ed52'), sig2)
def _do_test_bip32(self, seed: str, sequence): node = BIP32Node.from_rootseed(bfh(seed), xtype='standard') xprv, xpub = node.to_xprv(), node.to_xpub() self.assertEqual("m/", sequence[0:2]) sequence = sequence[2:] for n in sequence.split('/'): if n[-1] != "'": xpub2 = BIP32Node.from_xkey(xpub).subkey_at_public_derivation(n).to_xpub() node = BIP32Node.from_xkey(xprv).subkey_at_private_derivation(n) xprv, xpub = node.to_xprv(), node.to_xpub() if n[-1] != "'": self.assertEqual(xpub, xpub2) return xpub, xprv
def electrum_tx_to_txtype(self, tx, xpub_path): t = TransactionType() if tx is None: # probably for segwit input and we don't need this prev txn return t d = deserialize(tx.raw) t.version = d['version'] t.lock_time = d['lockTime'] t.inputs = self.tx_inputs(tx, xpub_path) t.bin_outputs = [ TxOutputBinType(amount=vout['value'], script_pubkey=bfh(vout['scriptPubKey'])) for vout in d['outputs'] ] return t
def deserialize_with_auxpow(data_hex: str, **kwargs): """Deserializes a block header given as hex string This makes sure that the data is always deserialised as full block header with AuxPoW. The keyword-arguments expect_trailing_data and start_position can be set and will be passed on to deserialize_full_header.""" # We pass a height beyond the last checkpoint, because # deserialize_full_header expects checkpointed headers to be truncated # by ElectrumX (i.e. not contain an AuxPoW). return blockchain.deserialize_full_header( bfh(data_hex), constants.net.max_checkpoint() + 1, **kwargs)
def test_encode_decode_msg__init(self): # "init" is interesting because it has TLVs optionally self.assertEqual( bfh("00100000000220c2"), encode_msg( "init", gflen=0, flen=2, features=(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT | LnFeatures.GOSSIP_QUERIES_OPT | LnFeatures.GOSSIP_QUERIES_REQ | LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT), )) self.assertEqual( bfh("00100000000220c2"), encode_msg("init", gflen=0, flen=2, features=bfh("20c2"))) self.assertEqual( bfh("00100000000220c2012043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000" ), encode_msg( "init", gflen=0, flen=2, features=(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT | LnFeatures.GOSSIP_QUERIES_OPT | LnFeatures.GOSSIP_QUERIES_REQ | LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT), init_tlvs={ 'networks': { 'chains': b'CI\x7f\xd7\xf8&\x95q\x08\xf4\xa3\x0f\xd9\xce\xc3\xae\xbay\x97 \x84\xe9\x0e\xad\x01\xea3\t\x00\x00\x00\x00' } })) self.assertEqual(('init', { 'gflen': 2, 'globalfeatures': b'"\x00', 'flen': 3, 'features': b'\x02\xa2\xa1', 'init_tlvs': {} }), decode_msg(bfh("001000022200000302a2a1"))) self.assertEqual( ('init', { 'gflen': 2, 'globalfeatures': b'"\x00', 'flen': 3, 'features': b'\x02\xaa\xa2', 'init_tlvs': { 'networks': { 'chains': b'CI\x7f\xd7\xf8&\x95q\x08\xf4\xa3\x0f\xd9\xce\xc3\xae\xbay\x97 \x84\xe9\x0e\xad\x01\xea3\t\x00\x00\x00\x00' } } }), decode_msg( bfh("001000022200000302aaa2012043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000" )))
def electrum_tx_to_txtype(self, tx): t = self.types.TransactionType() if tx is None: # probably for segwit input and we don't need this prev txn return t d = deserialize(tx.raw) t.version = d['version'] t.lock_time = d['lockTime'] inputs = self.tx_inputs(tx) t._extend_inputs(inputs) for vout in d['outputs']: o = t._add_bin_outputs() o.amount = vout['value'] o.script_pubkey = bfh(vout['scriptPubKey']) return t
def test_should_reject_very_long_merkle_branch(self): header_bytes = bfh(namecoin_header_37174) # We can't pass the real height because it's below a checkpoint, and # the deserializer expects ElectrumX to strip checkpointed AuxPoW. header = blockchain.deserialize_header( header_bytes, constants.net.max_checkpoint() + 1) header['auxpow']['chain_merkle_branch'] = list( [32 * '00' for i in range(32)]) with self.assertRaises(auxpow.AuxPoWChainMerkleTooLongError): blockchain.Blockchain.verify_header(header, namecoin_prev_hash_37174, namecoin_target_37174)
def test_encode_decode_msg__ints_can_be_passed_as_bytes(self): self.assertEqual(bfh(frombtc("01020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000000d43100006f00025e6ed0830100009000000000000000c8000001f400000023000000003b9aca00")), encode_msg( "channel_update", short_channel_id=ShortChannelID.from_components(54321, 111, 2), channel_flags=b'\x00', message_flags=b'\x01', cltv_expiry_delta=int.to_bytes(144, length=2, byteorder="big", signed=False), htlc_minimum_msat=int.to_bytes(200, length=8, byteorder="big", signed=False), htlc_maximum_msat=int.to_bytes(1_000_000_000, length=8, byteorder="big", signed=False), fee_base_msat=int.to_bytes(500, length=4, byteorder="big", signed=False), fee_proportional_millionths=int.to_bytes(35, length=4, byteorder="big", signed=False), chain_hash=constants.net.rev_genesis_bytes(), timestamp=int.to_bytes(1584320643, length=4, byteorder="big", signed=False), ))
def _do_test_bip32(self, seed, sequence): xprv, xpub = bip32_root(bfh(seed), 'standard') self.assertEqual("m/", sequence[0:2]) path = 'm' sequence = sequence[2:] for n in sequence.split('/'): child_path = path + '/' + n if n[-1] != "'": xpub2 = bip32_public_derivation(xpub, path, child_path) xprv, xpub = bip32_private_derivation(xprv, path, child_path) if n[-1] != "'": self.assertEqual(xpub, xpub2) path = child_path return xpub, xprv
def _make_multisig(self, m, xpubs, signatures=None): if len(xpubs) == 1: return None pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] if signatures is None: signatures = [b''] * len(pubkeys) elif len(signatures) != len(pubkeys): raise RuntimeError('Mismatched number of signatures') else: signatures = [bfh(x)[:-1] if x else b'' for x in signatures] return MultisigRedeemScriptType(pubkeys=pubkeys, signatures=signatures, m=m)
def _do_test_bip32(self, seed: str, sequence): node = BIP32Node.from_rootseed(bfh(seed), xtype='standard') xprv, xpub = node.to_xprv(), node.to_xpub() self.assertEqual("m/", sequence[0:2]) sequence = sequence[2:] for n in sequence.split('/'): if n[-1] != "'": xpub2 = BIP32Node.from_xkey(xpub).subkey_at_public_derivation( n).to_xpub() node = BIP32Node.from_xkey(xprv).subkey_at_private_derivation(n) xprv, xpub = node.to_xprv(), node.to_xpub() if n[-1] != "'": self.assertEqual(xpub, xpub2) return xpub, xprv
def do_send(self, tx): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue raw_tx_bytes = bfh(str(tx)) public_key = ecc.ECPubkey(K) message = public_key.encrypt_message(raw_tx_bytes).decode('ascii') try: server.put(_hash, message) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_error(_("Failed to send transaction to cosigning pool") + ':\n' + str(e)) return window.show_message(_("Your transaction was sent to the cosigning pool.") + '\n' + _("Open your cosigner wallet to retrieve it."))
def test_deserialize_auxpow_header_explicit_coinbase(self): header = self.deserialize_with_auxpow(namecoin_header_37174) header_auxpow = header['auxpow'] self.assertEqual(constants.net.AUXPOW_CHAIN_ID, header_auxpow['chain_id']) coinbase_tx = header_auxpow['parent_coinbase_tx'] expected_coinbase_txid = '8a3164be45a621f85318647d425fe9f45837b8e42ec4fdd902d7f64daf61ff4a' observed_coinbase_txid = auxpow.fast_txid(coinbase_tx) self.assertEqual(expected_coinbase_txid, observed_coinbase_txid) self.assertEqual(header_auxpow['coinbase_merkle_branch'], [ "f8f27314022a5165ae122642babb28dd44191dd36f99dad80b4f16b75197dde0", "c8a9dc420e17dee7b04bc0174c7a37ed9e5bc3f0ea0fdfe0b5d24bfc19ecedb0", "0ce9c5b98e212527e4aa7b9298435dc4e8f4dfc4dc63b7c89c06300637c33620", "3b6d0c4122a5b047cb879a440461839f0446f6bd451f01c6f0b14b6624e84136", "458500be38a68b215112df5e52d9c08fdd52034fb2005ce15d2a42be28e436cb", ]) coinbase_merkle_index = header_auxpow['coinbase_merkle_index'] self.assertEqual(0, coinbase_merkle_index) self.assertEqual(header_auxpow['chain_merkle_branch'], [ "000000000000000000000000000000000000000000000000000000000000000a", "65bd8eb2c7e3a3646507977e8659e5396b197f197fbb51e7158927a263798302", "5f961bb13289d705abb28376a01f7097535c95f87b9e719b9ec39d8eb20d72e9", "7cb5fdcc41120d6135a40a6753bddc0c9b675ba2936d2e0cd78cdcb02e6beb50", ]) chain_merkle_index = header_auxpow['chain_merkle_index'] self.assertEqual(11, chain_merkle_index) expected_parent_header = blockchain.deserialize_pure_header( bfh('0100000055a7bc918827dbe7d8027781d803f4b418589b7b9fc03e718a03000000000000625a3d6dc4dfb0ab25f450cd202ff3bdb074f2edde1ddb4af5217e10c9dbafb9639a0a4fd7690d1a25aeaa97' ), None) expected_parent_hash = blockchain.hash_header(expected_parent_header) observed_parent_hash = blockchain.hash_header( header_auxpow['parent_header']) self.assertEqual(expected_parent_hash, observed_parent_hash) expected_parent_merkle_root = expected_parent_header['merkle_root'] observed_parent_merkle_root = header_auxpow['parent_header'][ 'merkle_root'] self.assertEqual(expected_parent_merkle_root, observed_parent_merkle_root)
def test_encode_decode_msg__missing_mandatory_field_gets_set_to_zeroes(self): # "channel_update": "signature" missing -> gets set to zeroes self.assertEqual(bfh(frombtc("01020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000000d43100006f00025e6ed0830100009000000000000000c8000001f400000023000000003b9aca00")), encode_msg( "channel_update", short_channel_id=ShortChannelID.from_components(54321, 111, 2), channel_flags=b'\x00', message_flags=b'\x01', cltv_expiry_delta=144, htlc_minimum_msat=200, htlc_maximum_msat=1_000_000_000, fee_base_msat=500, fee_proportional_millionths=35, chain_hash=constants.net.rev_genesis_bytes(), timestamp=1584320643, ))
def _make_multisig(self, m, xpubs, signatures=None): if len(xpubs) == 1: return None pubkeys = [self._make_node_path(xpub, deriv) for xpub, deriv in xpubs] if signatures is None: signatures = [b''] * len(pubkeys) elif len(signatures) != len(pubkeys): raise RuntimeError('Mismatched number of signatures') else: signatures = [bfh(x)[:-1] if x else b'' for x in signatures] return MultisigRedeemScriptType( pubkeys=pubkeys, signatures=signatures, m=m)
def do_send(self, tx): for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii') try: server.put(_hash, message) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message( "Failed to send transaction to cosigning pool.") return window.show_message( "Your transaction was sent to the cosigning pool.\nOpen your cosigner wallet to retrieve it." )
def test_scrypt(self): #0200000011f1fe21e0b66dc214be46366465cb95d29830e31ddd225a11349a836a993bf7b5db36b3e5593d039779bff204d132b65ee029a2e499ebeb5a4b19cbe862eee2b623cc5276676c1c000e1c60 header = { 'block_height': 12095, 'nonce': 1612451328, 'timestamp': 1389110198, 'version': 2, 'prev_block_hash': 'f73b996a839a34115a22dd1de33098d295cb65643646be14c26db6e021fef111', 'merkle_root': 'e2ee62e8cb194b5aebeb99e4a229e05eb632d104f2bf7997033d59e5b336dbb5', 'bits': 476866422 } powhash = rev_hex(bh2u(scryptGetHash(bfh(serialize_header(header))))) self.assertEqual( powhash, '00000000335c88172421df73a1c1f22f4d7c23d8ef34c78d728c4eff3ba24a34')
def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.' )) return elif wallet.has_keystore_encryption(): password = window.password_dialog( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('Please enter your password to decrypt it.')) if not password: return else: password = None if not window.question( _("An encrypted transaction was retrieved from cosigning pool." ) + '\n' + _("Do you want to open it now?")): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bh2u(bitcoin.deserialize_xprv(xprv)[-1]) EC = bitcoin.EC_KEY(bfh(k)) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message(str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def test_decode_onion_error(self): orf = OnionRoutingFailure.from_bytes( bfh("400f0000000017d2d8b0001d9458")) self.assertEqual(('incorrect_or_unknown_payment_details', { 'htlc_msat': 399694000, 'height': 1938520 }), OnionWireSerializer.decode_msg(orf.to_bytes())) self.assertEqual({ 'htlc_msat': 399694000, 'height': 1938520 }, orf.decode_data()) orf2 = OnionRoutingFailure(26399, bytes.fromhex("0000000017d2d8b0001d9458")) with self.assertRaises(UnknownMsgType): OnionWireSerializer.decode_msg(orf2.to_bytes()) self.assertEqual(None, orf2.decode_data())
def check_deserialization(self, fcn, hex_data): """ Runs the deserialisation function fcn on the data given as hex, starting from position 80 (after the pure header). This also verifies the expected error if we reach EOF, and returns the parsed dict. """ data = bfh(hex_data) start = blockchain.PURE_HEADER_SIZE # A too-short auxpow raises an InvalidHeader exception rather than # our powdata one. Just check for any error here. with self.assertRaises(Exception): fcn(data[:-1], expect_trailing_data=True, start_position=start) res, _ = fcn(data, start_position=start) return res
def test_get_address_from_output_script(self): # the inverse of this test is in test_bitcoin: test_address_to_script addr_from_script = lambda script: transaction.get_address_from_output_script( bfh(script)) ADDR = transaction.TYPE_ADDRESS # bech32 native segwit # test vectors from BIP-0173 self.assertEqual( (ADDR, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), addr_from_script('0014751e76e8199196d454941c45d1b3a323f1433bd6')) self.assertEqual( (ADDR, 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx' ), addr_from_script( '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6' )) self.assertEqual((ADDR, 'bc1sw50qa3jx3s'), addr_from_script('6002751e')) self.assertEqual( (ADDR, 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), addr_from_script('5210751e76e8199196d454941c45d1b3a323')) # base58 p2pkh self.assertEqual( (ADDR, '14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), addr_from_script( '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac')) self.assertEqual( (ADDR, '1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), addr_from_script( '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac')) self.assertEqual( (ADDR, '18u8VTYhogvwek9rUQRtHKn66Sf6a2RV5w'), addr_from_script( '76a91456a4c36cd1fdb71a493fec9941b69b4a7cec90ea88ac')) # base58 p2sh self.assertEqual( (ADDR, '35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), addr_from_script('a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487')) self.assertEqual( (ADDR, '3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), addr_from_script('a914f47c8954e421031ad04ecd8e7752c9479206b9d387'))
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx): prev_tx = { bfh(txhash): self.electrum_tx_to_txtype(tx) for txhash, tx in prev_tx.items() } client = self.get_client(keystore) inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore) signatures, _ = client.sign_tx( self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version, amount_unit=self.get_trezor_amount_unit(), prev_txes=prev_tx) signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def test_should_reject_coinbase_root_too_late(self): header = self.deserialize_with_auxpow(header_without_mm) input_script = header['powdata']['auxpow'][ 'parent_coinbase_tx'].inputs()[0].script_sig padded_script = bfh('00') * ( auxpow.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4) padded_script += input_script header['powdata']['auxpow']['parent_coinbase_tx']._inputs[ 0].script_sig = padded_script self.clear_coinbase_outputs(header['powdata']['auxpow']) with self.assertRaises(auxpow.AuxPoWCoinbaseRootTooLate): blockchain.Blockchain.verify_header(header, prev_hash_without_mm, target_without_mm)
def test_decode_onion_error(self): # test vector from bolt-04 payment_path_pubkeys = [ bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'), bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'), bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'), bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'), bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'), ] session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141') error_packet_for_node_0 = bfh('9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d') decoded_error, index_of_sender = _decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key) self.assertEqual(bfh('4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), decoded_error) self.assertEqual(4, index_of_sender) failure_msg, index_of_sender = decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key) self.assertEqual(4, index_of_sender) self.assertEqual(OnionFailureCode.TEMPORARY_NODE_FAILURE, failure_msg.code) self.assertEqual(b'', failure_msg.data)
def show_address(self, wallet, address, keystore: 'Coldcard_KeyStore' = None): if keystore is None: keystore = wallet.get_keystore() if not self.show_address_helper(wallet, address, keystore): return txin_type = wallet.get_txin_type(address) # Standard_Wallet => not multisig, must be bip32 if type(wallet) is Standard_Wallet: sequence = wallet.get_address_index(address) keystore.show_address(sequence, txin_type) elif type(wallet) is Multisig_Wallet: assert isinstance( wallet, Multisig_Wallet) # only here for type-hints in IDE # More involved for P2SH/P2WSH addresses: need M, and all public keys, and their # derivation paths. Must construct script, and track fingerprints+paths for # all those keys pubkey_deriv_info = wallet.get_public_keys_with_deriv_info(address) pubkey_hexes = sorted([pk.hex() for pk in list(pubkey_deriv_info)]) xfp_paths = [] for pubkey_hex in pubkey_hexes: pubkey = bytes.fromhex(pubkey_hex) ks, der_suffix = pubkey_deriv_info[pubkey] fp_bytes, der_full = ks.get_fp_and_derivation_to_be_used_in_partial_tx( der_suffix, only_der_suffix=False) xfp_int = xfp_int_from_xfp_bytes(fp_bytes) xfp_paths.append([xfp_int] + list(der_full)) script = bfh(wallet.pubkeys_to_scriptcode(pubkey_hexes)) keystore.show_p2sh_address(wallet.m, script, xfp_paths, txin_type) else: keystore.handler.show_error( _('This function is only available for standard wallets when using {}.' ).format(self.device)) return
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx): prev_tx = { bfh(txhash): self.electrum_tx_to_txtype(tx) for txhash, tx in prev_tx.items() } client = self.get_client(keystore) inputs = self.tx_inputs(tx, for_sig=True, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore) details = SignTx(version=tx.version, overwintered=tx.overwintered, version_group_id=tx.versionGroupId, lock_time=tx.locktime, expiry=tx.expiryHeight) signatures, _ = client.sign_tx(self.get_coin_name(), inputs, outputs, details=details, prev_txes=prev_tx) signatures = [(bh2u(x) + '01') for x in signatures] tx.update_signatures(signatures)
def clear_coinbase_outputs(auxpow_header: dict, fix_merkle_root=True) -> None: """Clears the auxpow coinbase outputs Set the outputs of the auxpow coinbase to an empty list. This is necessary when the coinbase has been modified and needs to be re-serialised, since present outputs are invalid due to the fast_tx_deserialize optimisation.""" auxpow_header['parent_coinbase_tx']._outputs = [] # Clear the cached raw serialization auxpow_header['parent_coinbase_tx'].invalidate_ser_cache() # Re-serialize. Note that our AuxPoW library won't do this for us, # because it optimizes via fast_txid. auxpow_header['parent_coinbase_tx']._cached_network_ser_bytes = bfh(auxpow_header['parent_coinbase_tx'].serialize_to_network(force_legacy=True)) # Correct the coinbase Merkle root. if fix_merkle_root: update_merkle_root_to_match_coinbase(auxpow_header)
def do_send(self, tx): def on_success(result): window.show_message(_("Your transaction was sent to the cosigning pool.") + '\n' + _("Open your cosigner wallet to retrieve it.")) def on_failure(exc_info): e = exc_info[1] try: traceback.print_exception(*exc_info) except OSError: pass window.show_error(_("Failed to send transaction to cosigning pool") + ':\n' + str(e)) for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue # construct message raw_tx_bytes = bfh(str(tx)) public_key = ecc.ECPubkey(K) message = public_key.encrypt_message(raw_tx_bytes).decode('ascii') # send message task = lambda: server.put(_hash, message) msg = _('Sending transaction to cosigning pool...') WaitingDialog(window, msg, task, on_success, on_failure)
def show_address(self, wallet, address, keystore=None): if keystore is None: keystore = wallet.get_keystore() if not self.show_address_helper(wallet, address, keystore): return txin_type = wallet.get_txin_type(address) # Standard_Wallet => not multisig, must be bip32 if type(wallet) is Standard_Wallet: sequence = wallet.get_address_index(address) keystore.show_address(sequence, txin_type) elif type(wallet) is Multisig_Wallet: # More involved for P2SH/P2WSH addresses: need M, and all public keys, and their # derivation paths. Must construct script, and track fingerprints+paths for # all those keys pubkeys = wallet.get_public_keys(address) xfps = [] for xp, ks in zip(wallet.get_master_public_keys(), wallet.get_keystores()): path = "%s/%d/%d" % (getattr(ks, 'derivation', 'm'), *wallet.get_address_index(address)) # need master XFP for each co-signers ks_xfp = xfp_for_keystore(ks) xfps.append(unpacked_xfp_path(ks_xfp, path)) # put into BIP45 (sorted) order pkx = list(sorted(zip(pubkeys, xfps))) script = bfh(multisig_script([pk for pk,xfp in pkx], wallet.m)) keystore.show_p2sh_address(wallet.m, script, [xfp for pk,xfp in pkx], txin_type) else: keystore.handler.show_error(_('This function is only available for standard wallets when using {}.').format(self.device)) return
def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if wallet.has_keystore_encryption(): password = window.password_dialog( 'An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.' ) if not password: return else: password = None if not window.question( _("An encrypted transaction was retrieved from cosigning pool.\nDo you want to open it now?" )): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bh2u(bitcoin.deserialize_xprv(xprv)[-1]) EC = bitcoin.EC_KEY(bfh(k)) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message(str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def test_sign_transaction(self): eckey1 = ecc.ECPrivkey( bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d' )) sig1 = eckey1.sign_transaction( bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94' )) self.assertEqual( bfh('3045022100902a288b98392254cd23c0e9a49ac6d7920f171b8249a48e484b998f1874a2010220723d844826828f092cf400cb210c4fa0b8cd1b9d1a7f21590e78e022ff6476b9' ), sig1) eckey2 = ecc.ECPrivkey( bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23' )) sig2 = eckey2.sign_transaction( bfh('642a2e66332f507c92bda910158dfe46fc10afbf72218764899d3af99a043fac' )) self.assertEqual( bfh('30440220618513f4cfc87dde798ce5febae7634c23e7b9254a1eabf486be820f6a7c2c4702204fef459393a2b931f949e63ced06888f35e286e446dc46feb24b5b5f81c6ed52' ), sig2)
def test_add_number_to_script(self): # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#numbers self.assertEqual(add_number_to_script(0), bytes([opcodes.OP_0])) self.assertEqual(add_number_to_script(7), bytes([opcodes.OP_7])) self.assertEqual(add_number_to_script(16), bytes([opcodes.OP_16])) self.assertEqual(add_number_to_script(-1), bytes([opcodes.OP_1NEGATE])) self.assertEqual(add_number_to_script(-127), bfh('01ff')) self.assertEqual(add_number_to_script(-2), bfh('0182')) self.assertEqual(add_number_to_script(17), bfh('0111')) self.assertEqual(add_number_to_script(127), bfh('017f')) self.assertEqual(add_number_to_script(-32767), bfh('02ffff')) self.assertEqual(add_number_to_script(-128), bfh('028080')) self.assertEqual(add_number_to_script(128), bfh('028000')) self.assertEqual(add_number_to_script(32767), bfh('02ff7f')) self.assertEqual(add_number_to_script(-8388607), bfh('03ffffff')) self.assertEqual(add_number_to_script(-32768), bfh('03008080')) self.assertEqual(add_number_to_script(32768), bfh('03008000')) self.assertEqual(add_number_to_script(8388607), bfh('03ffff7f')) self.assertEqual(add_number_to_script(-2147483647), bfh('04ffffffff')) self.assertEqual(add_number_to_script(-8388608 ), bfh('0400008080')) self.assertEqual(add_number_to_script(8388608), bfh('0400008000')) self.assertEqual(add_number_to_script(2147483647), bfh('04ffffff7f'))
def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() inputs = [] inputsPaths = [] pubKeys = [] chipInputs = [] redeemScripts = [] signatures = [] preparedTrustedInputs = [] changePath = "" changeAmount = None output = None outputAmount = None p2shTransaction = False segwitTransaction = False pin = "" self.get_client() # prompt for the PIN before displaying the dialog if necessary # Fetch inputs of the transaction to sign derivations = self.get_tx_derivations(tx) for txin in tx.inputs(): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] in ['p2sh']: p2shTransaction = True if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']: if not self.get_client_electrum().supports_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True if txin['type'] in ['p2wpkh', 'p2wsh']: if not self.get_client_electrum().supports_native_segwit(): self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT) segwitTransaction = True pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin) for i, x_pubkey in enumerate(x_pubkeys): if x_pubkey in derivations: signingPos = i s = derivations.get(x_pubkey) hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1]) break else: self.give_error("No matching x_key for sign_transaction") # should never happen redeemScript = Transaction.get_preimage_script(txin) txin_prev_tx = txin.get('prev_tx') if txin_prev_tx is None and not Transaction.is_segwit_input(txin): raise Exception(_('Offline signing with {} is not supported for legacy inputs.').format(self.device)) txin_prev_tx_raw = txin_prev_tx.raw if txin_prev_tx else None inputs.append([txin_prev_tx_raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos, txin.get('sequence', 0xffffffff - 1), txin.get('value')]) inputsPaths.append(hwAddress) pubKeys.append(pubkeys) # Sanity check if p2shTransaction: for txin in tx.inputs(): if txin['type'] != 'p2sh': self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen txOutput = var_int(len(tx.outputs())) for txout in tx.outputs(): output_type, addr, amount = txout txOutput += int_to_hex(amount, 8) script = tx.pay_script(output_type, addr) txOutput += var_int(len(script)//2) txOutput += script txOutput = bfh(txOutput) # Recognize outputs - only one output and one change is authorized if not p2shTransaction: if not self.get_client_electrum().supports_multi_output(): if len(tx.outputs()) > 2: self.give_error("Transaction with more than 2 outputs not supported") for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if (info is not None) and len(tx.outputs()) > 1 \ and info[0][0] == 1: # "is on 'change' branch" index, xpubs, m = info changePath = self.get_derivation()[2:] + "/%d/%d"%index changeAmount = amount else: output = address outputAmount = amount self.handler.show_message(_("Confirm Transaction on your Ledger device...")) try: # Get trusted inputs from the original transactions for utxo in inputs: sequence = int_to_hex(utxo[5], 4) if segwitTransaction: tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) tmp += bfh(int_to_hex(utxo[6], 8)) # txin['value'] chipInputs.append({'value' : tmp, 'witness' : True, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) elif not p2shTransaction: txtmp = bitcoinTransaction(bfh(utxo[0])) trustedInput = self.get_client().getTrustedInput(txtmp, utxo[1]) trustedInput['sequence'] = sequence chipInputs.append(trustedInput) redeemScripts.append(txtmp.outputs[utxo[1]].script) else: tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) chipInputs.append({'value' : tmp, 'sequence' : sequence}) redeemScripts.append(bfh(utxo[2])) # Sign all inputs firstTransaction = True inputIndex = 0 rawTx = tx.serialize() self.get_client().enableAlternate2fa(False) if segwitTransaction: self.get_client().startUntrustedTransaction(True, inputIndex, chipInputs, redeemScripts[inputIndex]) if changePath: # we don't set meaningful outputAddress, amount and fees # as we only care about the alternateEncoding==True branch outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) else: outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin if not pin: raise UserWarning() if pin != 'paired': self.handler.show_message(_("Confirmed. Signing Transaction...")) while inputIndex < len(inputs): singleInput = [ chipInputs[inputIndex] ] self.get_client().startUntrustedTransaction(False, 0, singleInput, redeemScripts[inputIndex]) inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 else: while inputIndex < len(inputs): self.get_client().startUntrustedTransaction(firstTransaction, inputIndex, chipInputs, redeemScripts[inputIndex]) if changePath: # we don't set meaningful outputAddress, amount and fees # as we only care about the alternateEncoding==True branch outputData = self.get_client().finalizeInput(b'', 0, 0, changePath, bfh(rawTx)) else: outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput if firstTransaction: transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() pin = self.handler.get_auth( outputData ) # does the authenticate dialog and returns pin if not pin: raise UserWarning() if pin != 'paired': self.handler.show_message(_("Confirmed. Signing Transaction...")) else: # Sign input with the provided PIN inputSignature = self.get_client().untrustedHashSign(inputsPaths[inputIndex], pin, lockTime=tx.locktime) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 if pin != 'paired': firstTransaction = False except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BTChipException as e: if e.sw == 0x6985: # cancelled by user return elif e.sw == 0x6982: raise # pin lock. decorator will catch it else: traceback.print_exc(file=sys.stderr) self.give_error(e, True) except BaseException as e: traceback.print_exc(file=sys.stdout) self.give_error(e, True) finally: self.handler.finished() for i, txin in enumerate(tx.inputs()): signingPos = inputs[i][4] Transaction.add_signature_to_txin(txin, signingPos, bh2u(signatures[i])) tx.raw = tx.serialize()
def tx_inputs(self, tx, for_sig=False, script_gen=SCRIPT_GEN_LEGACY): inputs = [] for txin in tx.inputs(): txinputtype = self.types.TxInputType() if txin['type'] == 'coinbase': prev_hash = "\0" * 32 prev_index = 0xffffffff # signed int -1 else: if for_sig: x_pubkeys = txin['x_pubkeys'] if len(x_pubkeys) == 1: x_pubkey = x_pubkeys[0] xpub, s = parse_xpubkey(x_pubkey) xpub_n = self.client_class.expand_path( self.xpub_path[xpub]) txinputtype._extend_address_n(xpub_n + s) if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: txinputtype.script_type = self.types.InputScriptType.SPENDWITNESS elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: txinputtype.script_type = self.types.InputScriptType.SPENDP2SHWITNESS else: txinputtype.script_type = self.types.InputScriptType.SPENDADDRESS else: def f(x_pubkey): if is_xpubkey(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) else: xpub = xpub_from_pubkey(0, bfh(x_pubkey)) s = [] node = self.ckd_public.deserialize(xpub) return self.types.HDNodePathType(node=node, address_n=s) pubkeys = list(map(f, x_pubkeys)) multisig = self.types.MultisigRedeemScriptType( pubkeys=pubkeys, signatures=list( map(lambda x: bfh(x)[:-1] if x else b'', txin.get('signatures'))), m=txin.get('num_sig'), ) if script_gen == SCRIPT_GEN_NATIVE_SEGWIT: script_type = self.types.InputScriptType.SPENDWITNESS elif script_gen == SCRIPT_GEN_P2SH_SEGWIT: script_type = self.types.InputScriptType.SPENDP2SHWITNESS else: script_type = self.types.InputScriptType.SPENDMULTISIG txinputtype = self.types.TxInputType( script_type=script_type, multisig=multisig) # find which key is mine for x_pubkey in x_pubkeys: if is_xpubkey(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) if xpub in self.xpub_path: xpub_n = self.client_class.expand_path( self.xpub_path[xpub]) txinputtype._extend_address_n(xpub_n + s) break prev_hash = unhexlify(txin['prevout_hash']) prev_index = txin['prevout_n'] if 'value' in txin: txinputtype.amount = txin['value'] txinputtype.prev_hash = prev_hash txinputtype.prev_index = prev_index if 'scriptSig' in txin: script_sig = bfh(txin['scriptSig']) txinputtype.script_sig = script_sig txinputtype.sequence = txin.get('sequence', 0xffffffff - 1) inputs.append(txinputtype) return inputs
def test_push_script(self): # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#push-operators self.assertEqual(push_script(''), bh2u(bytes([opcodes.OP_0]))) self.assertEqual(push_script('07'), bh2u(bytes([opcodes.OP_7]))) self.assertEqual(push_script('10'), bh2u(bytes([opcodes.OP_16]))) self.assertEqual(push_script('81'), bh2u(bytes([opcodes.OP_1NEGATE]))) self.assertEqual(push_script('11'), '0111') self.assertEqual(push_script(75 * '42'), '4b' + 75 * '42') self.assertEqual(push_script(76 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42'))) self.assertEqual(push_script(100 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42'))) self.assertEqual(push_script(255 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42'))) self.assertEqual(push_script(256 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42'))) self.assertEqual(push_script(520 * '42'), bh2u(bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42')))