def test_segwit_valid_txs(setup_segwit): with open("test/tx_segwit_valid.json", "r") as f: json_data = f.read() valid_txs = json.loads(json_data) for j in valid_txs: if len(j) < 2: continue deserialized_tx = btc.CMutableTransaction.deserialize(hextobin(j[1])) print(pformat(deserialized_tx)) assert deserialized_tx.serialize() == hextobin(j[1])
def render_POST(self, request): """ An individual proposal may be submitted in base64, with key appended after newline separator in hex. """ log.debug("The server got this POST request: ") # unfortunately the twisted Request object is not # easily serialized: log.debug(request) log.debug(request.method) log.debug(request.uri) log.debug(request.args) sender_parameters = request.args log.debug(request.path) # defer logging of raw request content: proposals = request.content if not isinstance(proposals, BytesIO): return self.return_error(request, "Invalid request format", "invalid-request-format") proposals = proposals.read() # for now, only allowing proposals of form "base64ciphertext,hexkey", #newline separated: proposals = proposals.split(b"\n") log.debug("Client send proposal list of length: " + str(len(proposals))) accepted_proposals = [] for proposal in proposals: if len(proposal) == 0: continue try: encryptedtx, key, nonce = proposal.split(b",") bin_key = hextobin(key.decode('utf-8')) bin_nonce = hextobin(nonce.decode('utf-8')) base64.b64decode(encryptedtx) except: log.warn("This proposal was not accepted: " + proposal.decode("utf-8")) # give up immediately in case of format error: return self.return_error(request, "Invalid request format", "invalid-request-format") if not verify_pow(proposal, nbits=self.nbits, truncate=32): return self.return_error(request, "Insufficient PoW", "insufficient proof of work") accepted_proposals.append((key, encryptedtx)) # the proposals are valid format-wise; add them to the database for p in accepted_proposals: # note we will ignore errors here and continue; # warning will be shown in logs from called fn. self.add_proposal(p) content = "{} proposals accepted".format(len(accepted_proposals)) request.setHeader(b"content-length", ("%d" % len(content)).encode("ascii")) return content.encode("ascii")
def deserialize_revelation(cls, ser_rev, separator='|'): """ Reads the over-the-wire format as used in Joinmarket communication protocol. """ ser_list = ser_rev.split(separator) if len(ser_list) != 5: raise PoDLEError("Failed to deserialize, wrong format") utxostr, P, P2, s, e = ser_list success, utxo = utxostr_to_utxo(utxostr) assert success, "invalid utxo format in PoDLE." return {'utxo': utxo, 'P': hextobin(P), 'P2': hextobin(P2), 'sig': hextobin(s), 'e': hextobin(e)}
def _add_unspent_txo(self, utxo, height): """ Add a UTXO as returned by rpc's listunspent call to the wallet. Note that these are returned as little endian outpoint txids, so are converted. params: utxo: single utxo dict as returned by listunspent current_blockheight: blockheight as integer, used to set the block in which a confirmed utxo is included. """ txid = hextobin(utxo['txid']) script = hextobin(utxo['scriptPubKey']) value = int(Decimal(str(utxo['amount'])) * Decimal('1e8')) self.add_utxo(txid, int(utxo['vout']), script, value, height)
def test_ecdh(): """Using private key test vectors from Bitcoin Core. 1. Import a set of private keys from the json file. 2. Calculate the corresponding public keys. 3. Do ECDH on the cartesian product (x, Y), with x private and Y public keys, for all combinations. 4. Compare the result from CoinCurve with the manual multiplication xY following by hash (sha256). Note that sha256(xY) is the default hashing function used for ECDH in libsecp256k1. Since there are about 20 private keys in the json file, this creates around 400 test cases (note xX is still valid). """ with open(os.path.join(testdir, "base58_keys_valid.json"), "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) extracted_privkeys = [] for a in valid_keys_list: key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: c, k = btc.read_privkey(hextobin(hex_key)) extracted_privkeys.append(k) extracted_pubkeys = [btc.privkey_to_pubkey(x) for x in extracted_privkeys] for p in extracted_privkeys: for P in extracted_pubkeys: c, k = btc.read_privkey(p) shared_secret = btc.ecdh(k, P) assert len(shared_secret) == 32 # try recreating the shared secret manually: pre_secret = btc.multiply(p, P) derived_secret = hashlib.sha256(pre_secret).digest() assert derived_secret == shared_secret # test some important failure cases; null key, overflow case privkeys_invalid = [ b'\x00' * 32, hextobin( 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141') ] for p in privkeys_invalid: with pytest.raises(Exception) as e_info: shared_secret = btc.ecdh(p, extracted_pubkeys[0]) pubkeys_invalid = [b'0xff' + extracted_pubkeys[0][1:], b'0x00' * 12] for p in extracted_privkeys: with pytest.raises(Exception) as e_info: shared_secret = btc.ecdh(p, pubkeys_invalid[0]) with pytest.raises(Exception) as e_info: shared_secret = btc.ecdh(p, pubkeys_invalid[1])
def test_ecies(): """Using private key test vectors from Bitcoin Core. 1. Import a set of private keys from the json file. 2. Calculate the corresponding public keys. 3. Do ECDH on the cartesian product (x, Y), with x private and Y public keys, for all combinations. 4. Compare the result from CoinCurve with the manual multiplication xY following by hash (sha256). Note that sha256(xY) is the default hashing function used for ECDH in libsecp256k1. Since there are about 20 private keys in the json file, this creates around 400 test cases (note xX is still valid). """ with open(os.path.join(testdir, "base58_keys_valid.json"), "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) print("got valid keys list") extracted_privkeys = [] for a in valid_keys_list: key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: c, k = btc.read_privkey(hextobin(hex_key)) extracted_privkeys.append(k) extracted_pubkeys = [btc.privkey_to_pubkey(x) for x in extracted_privkeys] for (priv, pub) in zip(extracted_privkeys, extracted_pubkeys): test_message = base64.b64encode(os.urandom(15) * 20) assert btc.ecies_decrypt(priv, btc.ecies_encrypt(test_message, pub)) == test_message
def on_JM_REQUEST_MSGSIG_VERIFY(self, msg, fullmsg, sig, pubkey, nick, hashlen, max_encoded, hostid): pubkey_bin = hextobin(pubkey) verif_result = True if not btc.ecdsa_verify(str(msg), sig, pubkey_bin): # workaround for hostid, which sometimes is lowercase-only for some IRC connections if not btc.ecdsa_verify(str(msg[:-len(hostid)] + hostid.lower()), sig, pubkey_bin): jlog.debug("nick signature verification failed, ignoring: " + str(nick)) verif_result = False #check that nick matches hash of pubkey nick_pkh_raw = hashlib.sha256( pubkey.encode("ascii")).digest()[:hashlen] nick_stripped = nick[2:2 + max_encoded] #strip right padding nick_unpadded = ''.join([x for x in nick_stripped if x != 'O']) if not nick_unpadded == btc.base58.encode(nick_pkh_raw): jlog.debug("Nick hash check failed, expected: " + str(nick_unpadded) + ", got: " + str(btc.base58.encode(nick_pkh_raw))) verif_result = False d = self.callRemote(commands.JMMsgSignatureVerify, verif_result=verif_result, nick=nick, fullmsg=fullmsg, hostid=hostid) self.defaultCallbacks(d) return {'accepted': True}
def on_JM_TX_RECEIVED(self, nick, txhex, offer): offer = json.loads(offer) retval = self.client.on_tx_received(nick, txhex, offer) if not retval[0]: jlog.info("Maker refuses to continue on receipt of tx") else: sigs = retval[1] self.finalized_offers[nick] = offer tx = btc.CMutableTransaction.deserialize(hextobin(txhex)) self.finalized_offers[nick]["txd"] = tx txid = tx.GetTxid()[::-1] # we index the callback by the out-set of the transaction, # because the txid is not known until all scriptSigs collected # (hence this is required for Makers, but not Takers). # For more info see WalletService.transaction_monitor(): txinfo = tuple((x.scriptPubKey, x.nValue) for x in tx.vout) self.client.wallet_service.register_callbacks( [self.unconfirm_callback], txinfo, "unconfirmed") self.client.wallet_service.register_callbacks( [self.confirm_callback], txinfo, "confirmed") task.deferLater( reactor, float(jm_single().config.getint("TIMEOUT", "unconfirm_timeout_sec")), self.client.wallet_service.check_callback_called, txinfo, self.unconfirm_callback, "unconfirmed", "transaction with outputs: " + str(txinfo) + " not broadcast.") d = self.callRemote(commands.JMTXSigs, nick=nick, sigs=json.dumps(sigs)) self.defaultCallbacks(d) return {"accepted": True}
def setUp(self): self.wss_url = "ws://127.0.0.1:" + str(self.wss_port) self.wss_factory = JmwalletdWebSocketServerFactory(self.wss_url) self.wss_factory.protocol = JmwalletdWebSocketServerProtocol self.wss_factory.valid_token = encoded_token self.listeningport = listenWS(self.wss_factory, contextFactory=None) self.test_tx = CTransaction.deserialize(hextobin(test_tx_hex_1))
def setUp(self): load_test_config() self.clean_out_wallet_files() jm_single().bc_interface.tick_forward_chain_interval = 5 jm_single().bc_interface.simulate_blocks() # a client connnection object which is often but not always # instantiated: self.client_connector = None # start the daemon; note we are using tcp connections # to avoid storing certs in the test env. # TODO change that. self.daemon = JMWalletDaemonT(self.dport, self.wss_port, tls=False) self.daemon.auth_disabled = False # because we sync and start the wallet service manually here # (and don't use wallet files yet), we won't have set a wallet name, # so we set it here: self.daemon.wallet_name = self.get_wallet_file_name(1) r, s = self.daemon.startService() self.listener_rpc = r self.listener_ws = s wallet_structures = [self.wallet_structure] * 2 # note: to test fidelity bond wallets we should add the argument # `wallet_cls=SegwitWalletFidelityBonds` here, but it slows the # test down from 9 seconds to 1 minute 40s, which is too slow # to be acceptable. TODO: add a test with FB by speeding up # the sync for test, by some means or other. self.daemon.services["wallet"] = make_wallets_to_list( make_wallets(1, wallet_structures=[wallet_structures[0]], mean_amt=self.mean_amt, wallet_cls=SegwitWalletFidelityBonds))[0] jm_single().bc_interface.tickchain() sync_wallets([self.daemon.services["wallet"]]) # dummy tx example to force a notification event: self.test_tx = CTransaction.deserialize(hextobin(test_tx_hex_1))
def get_transactions_in_block(block): """ `block` is hex output from RPC `getblock`. Return: yields the block's transactions, type CBitcoinTransaction """ block = hextobin(block) # Skipping the header transaction_data = block[80:] # Decoding the number of transactions, offset is the size of # the varint (1 to 9 bytes) n_transactions, offset = decode_varint(transaction_data) for i in range(n_transactions): # This 'strat' of reading in small chunks optimistically is taken from: # https://github.com/alecalve/python-bitcoin-blockchain-parser/blob/7a9e15c236b10d2a6dff5e696801c0641af72628/blockchain_parser/utils.py # Try from 1024 (1KiB) -> 1073741824 (1GiB) slice widths for j in range(0, 20): try: offset_e = offset + (1024 * 2 ** j) transaction = CBitcoinTransaction.deserialize( transaction_data[offset:offset_e], allow_padding=True) yield transaction break except: continue # Skipping to the next transaction offset += len(transaction.serialize())
def test_auth_pub_not_found(setup_taker): taker = get_taker([(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)]) orderbook = copy.deepcopy(t_orderbook) res = taker.initialize(orderbook, []) taker.orderbook = copy.deepcopy( t_chosen_orders) #total_cjfee unaffected, all same maker_response = copy.deepcopy(t_maker_response) utxos = [ utxostr_to_utxo(x)[1] for x in [ "03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6:1", "498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3:0", "3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c:1" ] ] fake_query_results = [{ 'value': 200000000, 'address': "mrKTGvFfYUEqk52qPKUroumZJcpjHLQ6pn", 'script': hextobin('76a914767c956efe6092a775fea39a06d1cac9aae956d788ac'), 'utxo': utxos[i], 'confirms': 20 } for i in range(3)] jm_single().bc_interface.insert_fake_query_results(fake_query_results) res = taker.receive_utxos(maker_response) assert not res[0] assert res[1] == "Not enough counterparties responded to fill, giving up" jm_single().bc_interface.insert_fake_query_results(None)
def test_hr_psbt(setup_psbt_wallet): bitcoin.select_chain_params("bitcoin") for k, v in hr_test_vectors.items(): print( PSBTWalletMixin.human_readable_psbt( bitcoin.PartiallySignedTransaction.from_binary(hextobin(v)))) bitcoin.select_chain_params("bitcoin/regtest")
def test_coinjoin_mixdepth_wrap_taker(monkeypatch, tmpdir, setup_cj): def raise_exit(i): raise Exception("sys.exit called") monkeypatch.setattr(sys, 'exit', raise_exit) set_commitment_file(str(tmpdir.join('commitments.json'))) MAKER_NUM = 3 wallet_services = make_wallets_to_list( make_wallets(MAKER_NUM + 1, wallet_structures=[[4, 0, 0, 0, 0]] * MAKER_NUM + [[0, 0, 0, 0, 3]], mean_amt=1)) for wallet_service in wallet_services: assert wallet_service.max_mixdepth == 4 jm_single().bc_interface.tickchain() jm_single().bc_interface.tickchain() sync_wallets(wallet_services) cj_fee = 2000 makers = [ YieldGeneratorBasic( wallet_services[i], [0, cj_fee, 0, absoffer_type_map[SegwitWallet], 10**7]) for i in range(MAKER_NUM) ] create_orders(makers) orderbook = create_orderbook(makers) assert len(orderbook) == MAKER_NUM cj_amount = int(1.1 * 10**8) # mixdepth, amount, counterparties, dest_addr, waittime, rounding schedule = [(4, cj_amount, MAKER_NUM, 'INTERNAL', 0, NO_ROUNDING)] taker = create_taker(wallet_services[-1], schedule, monkeypatch) active_orders, maker_data = init_coinjoin(taker, makers, orderbook, cj_amount) txdata = taker.receive_utxos(maker_data) assert txdata[0], "taker.receive_utxos error" taker_final_result = do_tx_signing(taker, makers, active_orders, txdata) assert taker_final_result is not False tx = btc.CMutableTransaction.deserialize(hextobin(txdata[2])) wallet_service = wallet_services[-1] # TODO change for new tx monitoring: wallet_service.remove_old_utxos(tx) wallet_service.add_new_utxos(tx) balances = wallet_service.get_balance_by_mixdepth() assert balances[0] == cj_amount # <= because of tx fee assert balances[4] <= 3 * 10**8 - cj_amount - (cj_fee * MAKER_NUM)
def fund_wallet_addr(wallet, addr, value_btc=1): # special case, grab_coins returns hex from rpc: txin_id = hextobin(jm_single().bc_interface.grab_coins(addr, value_btc)) txinfo = jm_single().bc_interface.get_transaction(txin_id) txin = btc.CMutableTransaction.deserialize(btc.x(txinfo["hex"])) utxo_in = wallet.add_new_utxos(txin, 1) assert len(utxo_in) == 1 return list(utxo_in.keys())[0]
def get_deser_from_gettransaction(self, rpcretval): """Get full transaction deserialization from a call to `gettransaction` """ if not "hex" in rpcretval: log.info("Malformed gettransaction output") return None return btc.CMutableTransaction.deserialize(hextobin(rpcretval["hex"]))
def test_get_utxo_info(): load_test_config() # this test tests mainnet keys, so temporarily switch network select_chain_params("bitcoin") jm_single().config.set("BLOCKCHAIN", "network", "mainnet") dbci = DummyBlockchainInterface() privkey = "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi" #to verify use from_wif_privkey and privkey_to_address iaddr = "bc1q6tvmnmetj8vfz98vuetpvtuplqtj4uvvwjgxxc" fakeutxo = "aa" * 32 + ":08" success, fakeutxo_bin = utxostr_to_utxo(fakeutxo) assert success fake_query_results = [{ 'value': 200000000, 'script': BTC_P2WPKH.address_to_script(iaddr), 'utxo': fakeutxo_bin, 'confirms': 20 }] dbci.insert_fake_query_results(fake_query_results) jm_single().bc_interface = dbci u, priv = get_utxo_info(fakeutxo + "," + privkey) assert u == fakeutxo assert priv == privkey #invalid format with pytest.raises(Exception): u, priv = get_utxo_info(fakeutxo + privkey) #invalid index fu2 = "ab" * 32 + ":-1" with pytest.raises(Exception): u, priv = get_utxo_info(fu2 + "," + privkey) #invalid privkey p2 = privkey[:-1] + 'j' with pytest.raises(Exception): u, priv = get_utxo_info(fakeutxo + "," + p2) utxodatas = [(fakeutxo_bin, privkey)] retval = validate_utxo_data(utxodatas, False) assert retval #try to retrieve retval = validate_utxo_data(utxodatas, True) assert retval[0] == (fakeutxo_bin, 200000000) fake_query_results[0]['script'] = hextobin( "76a91479b000887626b294a914501a4cd226b58b23598388ac") dbci.insert_fake_query_results(fake_query_results) #validate should fail for wrong address retval = validate_utxo_data(utxodatas, False) assert not retval #remove fake query result and trigger not found dbci.fake_query_results = None dbci.setQUSFail(True) retval = validate_utxo_data(utxodatas, False) assert not retval dbci.setQUSFail(False) select_chain_params("bitcoin/regtest") jm_single().config.set("BLOCKCHAIN", "network", "regtest")
def test_pushtx_errors(setup_wallets): """Ensure pushtx fails return False """ badtx = b"\xaa\xaa" assert not jm_single().bc_interface.pushtx(badtx) #Break the authenticated jsonrpc and try again jm_single().bc_interface.jsonRpc.port = 18333 assert not jm_single().bc_interface.pushtx(hextobin(t_raw_signed_tx)) #rebuild a valid jsonrpc inside the bci load_test_config()
def on_push_tx(self, nick, txhex): """Broadcast unquestioningly, except checking hex format. """ try: dummy = hextobin(txhex) except: return d = self.callRemote(JMTXBroadcast, txhex=txhex) self.defaultCallbacks(d)
def _add_unspent_txo(self, utxo, height): """ Add a UTXO as returned by rpc's listunspent call to the wallet. Note that these are returned as little endian outpoint txids, so are converted. params: utxo: single utxo dict as returned by listunspent current_blockheight: blockheight as integer, used to set the block in which a confirmed utxo is included. """ txid = hextobin(utxo['txid']) script = hextobin(utxo['scriptPubKey']) value = int(Decimal(str(utxo['amount'])) * Decimal('1e8')) self.add_utxo(txid, int(utxo['vout']), script, value, height) # if we start up with unconfirmed outputs, they must be # put into the transaction monitor state, so we can recognize # when they transition to confirmed. if height is None: txd = self.bci.get_deser_from_gettransaction( self.bci.get_transaction(txid)) self.active_txs[utxo['txid']] = txd self.processed_txids.add(utxo['txid'])
def get_all_transactions(self): """ Get all transactions (spending or receiving) that are currently recorded by our blockchain interface as relating to this wallet, as a list. """ res = [] processed_txids = set() for r in self.bci._yield_transactions(): txid = r["txid"] if txid not in processed_txids: tx = self.bci.get_transaction(hextobin(txid)) res.append(self.bci.get_deser_from_gettransaction(tx)) processed_txids.add(txid) return res
def on_JM_TX_BROADCAST(self, txhex): """ Makers have no issue broadcasting anything, so only need to prevent crashes. Note in particular we don't check the return value, since the transaction being accepted or not is not our (maker)'s concern. """ try: txbin = hextobin(txhex) jm_single().bc_interface.pushtx(txbin) except: jlog.info("We received an invalid transaction broadcast " "request: " + txhex) return {"accepted": True}
def query_utxo_set(self, txouts, includeconf=False): if self.qusfail: #simulate failure to find the utxo return [None] if self.fake_query_results: result = [] for x in self.fake_query_results: for y in txouts: if y == x['utxo']: result.append(x) return result result = [] #external maker utxos known_outs = {"03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6:1": "03a2d1cbe977b1feaf8d0d5cc28c686859563d1520b28018be0c2661cf1ebe4857", "498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3:0": "02b4b749d54e96b04066b0803e372a43d6ffa16e75a001ae0ed4b235674ab286be", "3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c:1": "023bcbafb4f68455e0d1d117c178b0e82a84e66414f0987453d78da034b299c3a9"} known_outs = dictchanger(known_outs) #our wallet utxos, faked, for podle tests: utxos are doctored (leading 'f'), #and the lists are (amt, age) wallet_outs = {'f34b635ed8891f16c4ec5b8236ae86164783903e8e8bb47fa9ef2ca31f3c2d7a:0': [10000000, 2], 'f780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75:1': [20000000, 6], 'fe574db96a4d43a99786b3ea653cda9e4388f377848f489332577e018380cff1:0': [50000000, 3], 'fd9711a2ef340750db21efb761f5f7d665d94b312332dc354e252c77e9c48349:0': [50000000, 6]} wallet_outs = dictchanger(wallet_outs) if includeconf and set(txouts).issubset(set(wallet_outs)): #includeconf used as a trigger for a podle check; #here we simulate a variety of amount/age returns results = [] for to in txouts: results.append({'value': wallet_outs[to][0], 'confirms': wallet_outs[to][1]}) return results if txouts[0] in known_outs: scr = BTC_P2SH_P2WPKH.pubkey_to_script(known_outs[txouts[0]]) addr = btc.CCoinAddress.from_scriptPubKey(scr) return [{'value': 200000000, 'address': addr, 'script': scr, 'confirms': 20}] for t in txouts: result_dict = {'value': 10000000000, 'address': "mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ", 'script': hextobin('76a91479b000887626b294a914501a4cd226b58b23598388ac')} if includeconf: result_dict['confirms'] = 20 result.append(result_dict) return result
def restart_wait(txid): """ Returns true only if the transaction txid is seen in the wallet, and confirmed (it must be an in-wallet transaction since it always spends coins from the wallet). """ res = jm_single().bc_interface.get_transaction(hextobin(txid)) if not res: return False if res["confirmations"] == 0: return False if res["confirmations"] < 0: log.warn("Tx: " + txid + " has a conflict, abandoning.") sys.exit(EXIT_SUCCESS) else: log.debug("Tx: " + str(txid) + " has " + str(res["confirmations"]) + " confirmations.") return True
def test_valid_bip341_scriptpubkeys_addresses(): with ChainParams("bitcoin"): with open(os.path.join(testdir, "bip341_wallet_test_vectors.json"), "r") as f: json_data = json.loads(f.read()) for x in json_data["scriptPubKey"]: sPK = hextobin(x["expected"]["scriptPubKey"]) addr = x["expected"]["bip350Address"] res, message = validate_address(addr) assert res, message print("address {} was valid bech32m".format(addr)) # test this specific conversion because this is how # our human readable outputs work: assert str(CCoinAddress.from_scriptPubKey( btc.CScript(sPK))) == addr print("and it converts correctly from scriptPubKey: {}".format( btc.CScript(sPK)))
def query_utxo_set(self, txout, includeconf=False, includeunconf=False): """If txout is either (a) a single utxo in (txidbin, n) form, or a list of the same, returns, as a list for each txout item, the result of gettxout from the bitcoind rpc for those utxos; if any utxo is invalid, None is returned. includeconf: if this is True, the current number of confirmations of the prescribed utxo is included in the returned result dict. includeunconf: if True, utxos which currently have zero confirmations are included in the result set. If the utxo is of a non-standard type such that there is no address, the address field in the dict is None. """ if not isinstance(txout, list): txout = [txout] result = [] for txo in txout: txo_hex = bintohex(txo[0]) if len(txo_hex) != 64: log.warn("Invalid utxo format, ignoring: {}".format(txo)) result.append(None) continue try: txo_idx = int(txo[1]) except ValueError: log.warn("Invalid utxo format, ignoring: {}".format(txo)) result.append(None) continue ret = self.rpc('gettxout', [txo_hex, txo_idx, includeunconf]) if ret is None: result.append(None) else: if ret['scriptPubKey'].get('addresses'): address = ret['scriptPubKey']['addresses'][0] else: address = None result_dict = { 'value': int(Decimal(str(ret['value'])) * Decimal('1e8')), 'address': address, 'script': hextobin(ret['scriptPubKey']['hex']) } if includeconf: result_dict['confirms'] = int(ret['confirmations']) result.append(result_dict) return result
def on_privmsg(self, nick, message): """handles the case when a private message is received""" #Aberrant short messages should be handled by subclasses #in _privmsg, but this constitutes a sanity check. Note that #messages which use an encrypted_command but present no #ciphertext will be rejected with the ValueError on decryption. #Other ill formatted messages will be caught in the try block. if len(message) < 2: return if message[0] != COMMAND_PREFIX: log.debug('message not a cmd') return cmd_string = message[1:].split(' ')[0] if cmd_string not in plaintext_commands + encrypted_commands: log.debug('cmd not in cmd_list, line="' + message + '"') return badsigmsg = "Sig not properly appended to privmsg, ignoring" #Verify nick ownership try: pub, sig = message[1:].split(' ')[-2:] except Exception: log.debug(badsigmsg) return #reconstruct original message without cmd rawmessage = ' '.join(message[1:].split(' ')[1:-2]) # can happen if not enough fields for command, (stuff), pub, sig: if len(rawmessage) == 0: log.debug(badsigmsg) return # Sanitising signature before attempting to verify: # Note that the sig itself can be any garbage, because `ecdsa_verify` # swallows any fail and returns False; but the pubkey is assumed # to be hex-encoded, and the signature base64 encoded, so check early: try: dummypub = hextobin(pub) dummysig = base64.b64decode(sig) except Exception: log.debug(badsigmsg) return self.daemon.request_signature_verify(rawmessage + str(self.hostid), message, sig, pub, nick, NICK_HASH_LENGTH, NICK_MAX_ENCODED, str(self.hostid))
def test_ecies(): """Tests encryption and decryption of random messages using the ECIES module. TODO these tests are very minimal. """ with open(os.path.join(testdir,"base58_keys_valid.json"), "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) print("got valid keys list") extracted_privkeys = [] for a in valid_keys_list: key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: c, k = btc.read_privkey(hextobin(hex_key)) extracted_privkeys.append(k) extracted_pubkeys = [btc.privkey_to_pubkey(x) for x in extracted_privkeys] for (priv, pub) in zip(extracted_privkeys, extracted_pubkeys): test_message = base64.b64encode(os.urandom(15)*20) assert btc.ecies_decrypt(priv, btc.ecies_encrypt(test_message, pub)) == test_message
def read_from_podle_file(): """ Returns used commitment list and external commitments dict struct currently stored in PODLE_COMMIT_FILE. """ if os.path.isfile(PODLE_COMMIT_FILE): with open(PODLE_COMMIT_FILE, "rb") as f: try: c = json.loads(f.read().decode('utf-8')) except ValueError: #pragma: no cover #Exit conditions cannot be included in tests. jmprint("the file: " + PODLE_COMMIT_FILE + " is not valid json.", "error") sys.exit(EXIT_FAILURE) if 'used' not in c.keys() or 'external' not in c.keys(): raise PoDLEError("Incorrectly formatted file: " + PODLE_COMMIT_FILE) used = [hextobin(x) for x in c["used"]] external = external_dict_from_file(c["external"]) return (used, external) return ([], {})
def query_utxo_set(self, txout, includeconfs=False, include_mempool=True): """If txout is either (a) a single utxo in (txidbin, n) form, or a list of the same, returns, as a list for each txout item, the result of gettxout from the bitcoind rpc for those utxos; if any utxo is invalid, None is returned. includeconfs: if this is True, the current number of confirmations of the prescribed utxo is included in the returned result dict. include_mempool: if True, the contents of the mempool are included; this *both* means that utxos that are spent in in-mempool transactions are *not* returned, *and* means that utxos that are created in the mempool but have zero confirmations *are* returned. If the utxo is of a non-standard type such that there is no address, the address field in the dict is None. """ if not isinstance(txout, list): txout = [txout] result = [] for txo in txout: txo_hex = bintohex(txo[0]) if len(txo_hex) != 64: log.warn("Invalid utxo format, ignoring: {}".format(txo)) result.append(None) continue try: txo_idx = int(txo[1]) except ValueError: log.warn("Invalid utxo format, ignoring: {}".format(txo)) result.append(None) continue ret = self._rpc('gettxout', [txo_hex, txo_idx, include_mempool]) if ret is None: result.append(None) else: result_dict = { 'value': int(Decimal(str(ret['value'])) * Decimal('1e8')), 'script': hextobin(ret['scriptPubKey']['hex']) } if includeconfs: result_dict['confirms'] = int(ret['confirmations']) result.append(result_dict) return result