electrum-personal-server

Maximally lightweight electrum server for a single user
git clone https://git.parazyd.org/electrum-personal-server
Log | Files | Refs | README

commit 86f51b7d87f8c39956fb3478cb0ed2609f5cc96b
parent 314443ac47f77ce90490744ecef5071581ffe2c1
Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date:   Fri,  9 Mar 2018 00:19:21 +0000

added support for bech32 and multisig master public keys, and more tests

Diffstat:
Mbitcoin/deterministic.py | 21++++++++++++++++++---
Mconfig.cfg_sample | 5++++-
Mdeterministicwallet.py | 338++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mserver.py | 3++-
4 files changed, 289 insertions(+), 78 deletions(-)

diff --git a/bitcoin/deterministic.py b/bitcoin/deterministic.py @@ -5,11 +5,26 @@ from binascii import hexlify # Below code ASSUMES binary inputs and compressed pubkeys MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' -MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' +#MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' TESTNET_PRIVATE = b'\x04\x35\x83\x94' -TESTNET_PUBLIC = b'\x04\x35\x87\xCF' +#TESTNET_PUBLIC = b'\x04\x35\x87\xCF' PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE] -PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC] +#PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC] + +#updated for electrum's bip32 version bytes +#only public keys because electrum personal server only needs them +#https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst +PUBLIC = [ b'\x04\x88\xb2\x1e', #mainnet p2pkh or p2sh xpub + b'\x04\x9d\x7c\xb2', #mainnet p2wpkh-p2sh ypub + b'\x02\x95\xb4\x3f', #mainnet p2wsh-p2sh Ypub + b'\x04\xb2\x47\x46', #mainnet p2wpkh zpub + b'\x02\xaa\x7e\xd3', #mainnet p2wsh Zpub + b'\x04\x35\x87\xcf', #testnet p2pkh or p2sh tpub + b'\x04\x4a\x52\x62', #testnet p2wpkh-p2sh upub + b'\x02\x42\x85\xef', #testnet p2wsh-p2sh Upub + b'\x04\x5f\x1c\xf6', #testnet p2wpkh vpub + b'\x02\x57\x54\x83' #testnet p2wsh Vpub + ] # BIP32 child key derivation diff --git a/config.cfg_sample b/config.cfg_sample @@ -7,6 +7,9 @@ #any_key_works = xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF +# Multisig wallets use format `required-signatures [list of master pub keys]` +#multisig_wallet = 2 xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF xpub661MyMwAqRbcFseXCwRdRVkhVuzEiskg4QUp5XpUdNf2uGXvQmnD4zcofZ1MN6Fo8PjqQ5cemJQ39f7RTwDVVputHMFjPUn8VRp2pJQMgEF + [bip39-master-public-keys] ## Add master public keys in the bip39 standard to this section ## Most hardware wallet keys go here @@ -16,7 +19,7 @@ ## Add addresses to this section #addr = 1DuqpoeTB9zLvVCXQG53VbMxvMkijk494n -# A space or comma separated list is accepted +# A space separated list is accepted #my_test_addresses = 3Hh7QujVLqz11tiQsnUE5CSL16WEHBmiyR 1PXRLo1FQoZyF1Jhnz4qbG5x8Bo3pFpybz bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq [bitcoin-rpc] diff --git a/deterministicwallet.py b/deterministicwallet.py @@ -2,22 +2,43 @@ import bitcoin as btc import util +#the wallet types are here +#https://github.com/spesmilo/electrum/blob/3.0.6/RELEASE-NOTES +#and +#https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst + def parse_electrum_master_public_key(keydata, gaplimit): - if keydata[:4] == "xpub": + if keydata[:4] in ("xpub", "tpub"): return SingleSigP2PKHWallet(keydata, gaplimit) + elif keydata[:4] in ("zpub", "vpub"): + return SingleSigP2WPKHWallet(keydata, gaplimit) + elif keydata.find(" ") != -1: #multiple keys = multisig + chunks = keydata.split(" ") + try: + m = int(chunks[0]) + except ValueError: + raise ValueError("Unable to parse m in multisig key data: " + + chunks[0]) + pubkeys = chunks[1:] + if not all([pubkeys[0][:4] == pub[:4] for pub in pubkeys[1:]]): + raise ValueError("inconsistent bip32 pubkey types") + if pubkeys[0][:4] in ("xpub", "tpub"): + return MultisigP2SHWallet(m, pubkeys, gaplimit) + if pubkeys[0][:4] in("Zpub", "Vpub"): + return MultisigP2WSHWallet(m, pubkeys, gaplimit) else: - raise RuntimeError("Unrecognized electrum mpk format: " + keydata[:4]) - -#the wallet types are here -#https://github.com/spesmilo/electrum/blob/3.0.6/RELEASE-NOTES + raise ValueError("Unrecognized electrum mpk format: " + keydata[:4]) class DeterministicWallet(object): def __init__(self, gaplimit): self.gaplimit = gaplimit + self.next_index = [0, 0] + self.scriptpubkey_index = {} def get_new_scriptpubkeys(self, change, count): """Returns newly-generated addresses from this deterministic wallet""" - pass + return self.get_scriptpubkeys(change, self.next_index[change], + count) def get_scriptpubkeys(self, change, from_index, count): """Returns addresses from this deterministic wallet""" @@ -25,45 +46,9 @@ class DeterministicWallet(object): #called in check_for_new_txes() when a new tx of ours arrives #to see if we need to import more addresses - def have_scriptpubkeys_overrun_gaplimit(self, scripts): + def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys): """Return None if they havent, or how many addresses to import if they have""" - pass - - def rewind_one(self, change): - """Go back one pubkey in a branch""" - pass - -class SingleSigP2PKHWallet(DeterministicWallet): - def __init__(self, mpk, gaplimit): - super(SingleSigP2PKHWallet, self).__init__(gaplimit) - self.branches = (btc.bip32_ckd(mpk, 0), btc.bip32_ckd(mpk, 1)) - self.next_index = [0, 0] - self.scriptpubkey_index = {} - - def pubkey_to_scriptpubkey(self, pubkey): - pkh = util.bh2u(util.hash_160(util.bfh(pubkey))) - #op_dup op_hash_160 length hash160 op_equalverify op_checksig - return "76a914" + pkh + "88ac" - #for p2sh its "a9" + hash160 + "87" #op_hash_160 op_equal - - def get_new_scriptpubkeys(self, change, count): - return self.get_scriptpubkeys(change, self.next_index[change], - count) - - def get_scriptpubkeys(self, change, from_index, count): - #m/change/i - result = [] - for index in range(from_index, from_index + count): - pubkey = btc.bip32_extract_key(btc.bip32_ckd(self.branches[change], - index)) - scriptpubkey = self.pubkey_to_scriptpubkey(pubkey) - self.scriptpubkey_index[scriptpubkey] = (change, index) - result.append(scriptpubkey) - self.next_index[change] = max(self.next_index[change], from_index+count) - return result - - def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys): result = {} for spk in scriptpubkeys: if spk not in self.scriptpubkey_index: @@ -84,42 +69,249 @@ class SingleSigP2PKHWallet(DeterministicWallet): return None def rewind_one(self, change): + """Go back one pubkey in a branch""" self.next_index[change] -= 1 -''' -recv -76a914b1847c763c9a9b12631ab42335751c1bf843880c88ac -76a914d8b6b932e892fad5132ea888111adac2171c5af588ac -76a914e44b19ef74814f977ae4e2823dd0a0b33480472a88ac -change -76a914d2c2905ca383a5b8f94818cb7903498061a6286688ac -76a914e7b4ddb7cede132e84ba807defc092cf52e005b888ac -76a91433bdb046a1d373728d7844df89aa24f788443a4588ac -''' +class SingleSigWallet(DeterministicWallet): + def __init__(self, mpk, gaplimit): + super(SingleSigWallet, self).__init__(gaplimit) + self.branches = (btc.bip32_ckd(mpk, 0), btc.bip32_ckd(mpk, 1)) + #m/change/i -#need test vectors for each kind of detwallet + def pubkey_to_scriptpubkey(self, pubkey): + raise RuntimeError() + def get_scriptpubkeys(self, change, from_index, count): + result = [] + for index in range(from_index, from_index + count): + pubkey = btc.bip32_extract_key(btc.bip32_ckd(self.branches[change], + index)) + scriptpubkey = self.pubkey_to_scriptpubkey(pubkey) + self.scriptpubkey_index[scriptpubkey] = (change, index) + result.append(scriptpubkey) + self.next_index[change] = max(self.next_index[change], from_index+count) + return result + +class SingleSigP2PKHWallet(SingleSigWallet): + def pubkey_to_scriptpubkey(self, pubkey): + pkh = util.bh2u(util.hash_160(util.bfh(pubkey))) + #op_dup op_hash_160 length hash160 op_equalverify op_checksig + return "76a914" + pkh + "88ac" + +class SingleSigP2WPKHWallet(SingleSigWallet): + def pubkey_to_scriptpubkey(self, pubkey): + pkh = util.bh2u(util.hash_160(util.bfh(pubkey))) + #witness-version length hash160 + #witness version is always 0, length is always 0x14 + return "0014" + pkh + +class MultisigWallet(DeterministicWallet): + def __init__(self, m, mpk_list, gaplimit): + super(MultisigWallet, self).__init__(gaplimit) + self.m = m + self.pubkey_branches = [(btc.bip32_ckd(mpk, 0), btc.bip32_ckd(mpk, 1)) + for mpk in mpk_list] + #derivation path for pubkeys is m/change/index + + def redeem_script_to_scriptpubkey(self, redeem_script): + raise RuntimeError() + + def get_scriptpubkeys(self, change, from_index, count): + result = [] + for index in range(from_index, from_index + count): + pubkeys = [btc.bip32_extract_key(btc.bip32_ckd(branch[change], + index)) for branch in self.pubkey_branches] + pubkeys = sorted(pubkeys) + redeemScript = "" + redeemScript += "%x"%(0x50 + self.m) #op_m + for p in pubkeys: + redeemScript += "21" #length + redeemScript += p + redeemScript += "%x"%(0x50 + len(pubkeys)) #op_n + redeemScript += "ae" # op_checkmultisig + scriptpubkey = self.redeem_script_to_scriptpubkey(redeemScript) + self.scriptpubkey_index[scriptpubkey] = (change, index) + result.append(scriptpubkey) + self.next_index[change] = max(self.next_index[change], from_index+count) + return result + +class MultisigP2SHWallet(MultisigWallet): + def redeem_script_to_scriptpubkey(self, redeem_script): + sh = util.bh2u(util.hash_160(util.bfh(redeem_script))) + return "a914" + sh + "87" + #op_hash160 length hash160 op_equal + +class MultisigP2WSHWallet(MultisigWallet): + def redeem_script_to_scriptpubkey(self, redeem_script): + sh = util.bh2u(util.sha256(util.bfh(redeem_script))) + return "0020" + sh + #witness-version length sha256 + #witness version is always 0, length is always 0x20 + +electrum_keydata_test_vectors = [ + #p2pkh wallet + ("xpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" + + "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #pubkey + ["76a914b1847c763c9a9b12631ab42335751c1bf843880c88ac" #recv scriptpubkeys + ,"76a914d8b6b932e892fad5132ea888111adac2171c5af588ac" + ,"76a914e44b19ef74814f977ae4e2823dd0a0b33480472a88ac"], + ["76a914d2c2905ca383a5b8f94818cb7903498061a6286688ac" #change scriptpubkeys + ,"76a914e7b4ddb7cede132e84ba807defc092cf52e005b888ac" + ,"76a91433bdb046a1d373728d7844df89aa24f788443a4588ac"]) + , #p2wpkh wallet + ("zpub6mr7wBKy3oJn89TCiXUAPBWpTTTx58BgEjPLzDNf5kMThvd6xchrobPTsJ5mP" + + "w3NJ7zRhckN8cv4FhQBfwurZzNE5uTW5C5PYqNTkRAnTkP", #pubkey + ['00142b82c61a7a48b7b10801f0eb247af46821bd33f5' #recv scriptpubkeys + ,'0014073dc6bcbb18d6468c5996bdeba926f6805b74b1' + ,'001400fa0b5cb21e8d442a7bd61af3d558a62be0c9aa'], + ['00144f4a0655a4b586be1e08d97a2f55125120b84c69' #change scriptpubkeys + ,'0014ef7967a7a56c23bbc9f317e612c93a5e23d25ffe' + ,'0014ad768a11730bf54d10c72184d53239de0f310bc9']) + ,#p2sh 2of2 multisig wallet + ("2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + + "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" + + "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" + + "T74sszGaxaf64o5s", #m=2, 2 pubkeys, n=len(pubkeys) + ['a914fe30a46a4e1b41f9bb758448fd84ee4628c103e187' #recv + ,'a914dad5dd605871560ae5d219cd6275e6ad19bc6b9987' + ,'a914471e158e2db190acdd8c76ed6d2ade102fe1e8ac87' + ,'a914013449715a32f21d1a8a2b95a01b40eb41ada16f87' + ,'a914ae3dd25567fb7c2f87be41220dd14025ca68b0e087' + ,'a91462b90344947b610c4eadb7dd460fee3f32fefe7687' + ,'a914d4388c7d5771ebf26b6e650c42e60e4cf7d4c5a187' + ,'a914e4f0832e56591d01b71c72b9a3777dc8f9d9a92e87' + ,'a914a5d5accd96d27403c7663b92fdb57299d7a871eb87' + ,'a914f8f2c6ef2d80f972e4d8b418a15337a3c38af37f87' + ,'a914a2bd2f67fac7c24e609b574ccc8cfaa2f90ebf8c87' + ,'a914a56298a7decde1d18306f55d9305577c3fce690187' + ,'a91430f2f83238ac29125a539055fa59efc86a73a23987' + ,'a914263b4585d0735c5065987922af359d5eabeb880d87' + ,'a91455d9d47113fb8b37705bdf6d4107d438afd63e4687' + ,'a914970d754163b8957b73f4e8baaf23dea5f6e3db2287' + ,'a914facbc921203a9ffd751cc246a884918beaac21b687' + ,'a914fc7556833eca1e0f84c6d7acb875e645f7ed4e9687' + ,'a914bbfe6a032d633f113b5d605e3a97cc08a47cc87d87' + ,'a91403d733c4ca337b5fa1de95970ba6f898a9d36c4887' + ,'a9148af27dc7c950e17c11e164065e672cd60ae3d48d87' + ,'a914c026aa45377f2a4a62136bac1d3350c318fee5c587' + ,'a9146337f59e3ea55e73725c9f2fc52a5ca5d68c361687'], + ['a914aeaebf9d567ab8a6813e89668e16f40bf419408e87' #change + ,'a914f2a6264dd3975297fa2a5a8e17321299a44f76d987' + ,'a9142067a6c47958090a645137cc0898c0c7bbc69b5387' + ,'a914210840f77ea5b7eb11cb55e5d719a93b7746fb9387' + ,'a914163db6b8ca00362be63a26502c5f7bf64787506b87' + ,'a91479b2c527594059c056e5367965ae92bbcf63512187']) + ,#p2sh 2of3 multisig wallet + ("2 tpubD6NzVbkrYhZ4WwaMJ3od4hANxdMVpb63Du3ERq1xjtowxVJEcTbGH2rFd9TFXxw" + + "KJRKDn9vQjDPxFeaku6BHW6wHn2KPF1ijS4LwgwQFJ3B tpubD6NzVbkrYhZ4Wjv4ZRPD6" + + "MNdiLmfvXztbKuuatkqHjukU3S6GXhmKnbAF5eU9bR2Nryiq8v67emUUSM1VUrAx5wcZ19" + + "AsaGg3ZLmjbbwLXr tpubD6NzVbkrYhZ4Xxa2fEp7YsbnFnwuQNaogijbiX42Deqd4NiAD" + + "tqNU6AXCU2d2kPFWBpAGG7K3HAKYwUfZBPgTLkfQp2dDg9SLVnkgYPgEXN", + ['a914167c95beb25b984ace517d4346e6cdbf1381793687', #recv addrs + 'a914378bbda1ba7a713de18c3ba3c366f42212bfb45087', + 'a9142a5c9881c70906180f37dd02d8c830e9b6328d4a87', + 'a914ffe0832375b72ee5307bfa502896ba28cc470ee987', + 'a9147607d40e039fbea57d9c04e48b198c9fcf3356c187', + 'a9148d9582ad4cf0581c6e0697e4cba6a12e66ca1a0087', + 'a914d153a743b315ba19690823119019e16e3762104d87', + 'a914b4accc89e48610043e70371153fd8cb5a3eef34287', + 'a91406febca615e3631253fd75a1d819436e1d046e0487', + 'a914b863cbb888c6b28291cb87a2390539e28be37a9587', + 'a914ec39094e393184d2c352a29b9d7a3caddaccb6cf87', + 'a914da4faa4babbdf611caf511d287133f06c1c3244a87', + 'a9146e64561d0c5e2e9159ecff65db02e04b3277402487', + 'a914377d66386972492192ae827fb2208596af0941d187', + 'a914448d364ff2374449e57df13db33a40f5b099997c87', + 'a914f24b875d2cb99e0b138ab0e6dd65027932b3c6e787', + 'a914aa4bcee53406b1ef6c83852e3844e38a3a9d9f3087', + 'a9145e5ec40fdab54be0d6e21107bc38c39df97e37fc87', + 'a9141de4d402c82f4e9b0e6b792b331232a5405ebd3f87', + 'a9148873ee280e51f9c64d257dd6dedc8712fd652cc687'], + ['a9142cc87d7562a85029a57cc37026e12dab72223db287', #change + 'a91499f4aee0b274f0b3ab48549a2c58cd667a62c0cb87', + 'a91497a89cd5ada3a766a1275f8151e9256fcf537f6c87', + 'a9147ffc9f3a3b60635ea1783243274f4d07ab617cb487', + 'a9143423113ab913d86fd47e55488a0c559e18b457b987', + 'a914a28a3773a37c52ff6fd7dff497d0eaf80a46febb87']) + , #p2wsh 1of2 multisig wallet + ("1 Vpub5fAqpSRkLmvXwqbuR61MaKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHp" + + "dvrg2LHHXkKxSXBHNWNpZz9b1VqADjmcCs3arSoxN3F3r Vpub5fvEo4MUpbVs9sZqr45" + + "zmRVEsTcQ49MA9m3MLht3XzdZvS9eMXLLu1H6TL1j2SMnykHqXNzG5ycMyQmFDvEE5B32" + + "sP8TmRe6wW8HjBgMssh", + #recv scriptpubkeys + ['002031fbaa839e96fc1abaf3453b9f770e0ccfe2d8e3e990bb381fdcb7db4722986a', + '0020820ae739b36f4feb1c299ced201db383bbcf1634e0071e489b385f43c2323761', + '0020eff05f4d14aa1968a7142b1009aa57a6208fb01b212f8b8f7df63645d26a1292', + '002049c6e17979dca380ffb66295d27f609bea2879d4f0b590c96c70ff12260a8721', + '002002bf2430fc7ebc6fb27da1cb80e52702edcc62a29f65c997e5c924dcd98411bd', + '0020c7a58dcf9633453ba12860b57c14af67d87d022be5c52bf6be7a6abdc295c6e0', + '0020136696059a5e932c72f4f0a05fa7f52faf9b54f1b7694e15acce710e6cc9e89d', + '0020c372e880227f35c2ee35d0724bf05cea95e74dcb3e6aa67ff15f561a29c0645d', + '002095c705590e2b84996fa44bff64179b26669e53bbd58d76bb6bbb5c5498a981ce', + '00207217754dae083c3c365c7e1ce3ad889ca2bd88e4f809cec66b9987adc390aa26', + '0020bee30906450e099357cc96a1f472c1ef70089cd4a0cba96749adfe1c9a2f9e87', + '0020b1838b3d5a386ad6c90eeae9a27a9b812e32ce06376f261dea89e405bc8209d9', + '0020231a3d05886efff601f0702d4c8450dfcce8d6a4bd90f17f7ff76f5c25c632de', + '002071220f3941b5f65aca90e464db4291cd5ea63f37fa858fd5b66d5019f0dbab0f', + '0020fc3c7db9f0e773f9f9c725d4286ddcc88db9575c45b2441d458018150eb4ef10', + '00209f037bfc98dee2fc0d3cca54df09b2d20e92a0133fa381a4dd74c49e4d0a89f5', + '0020c9060d0554ba2ca92048e1772e806d796ba41f10bf6aee2653a9eba96b05c944', + '0020a7cb1dd2730dba564f414ed8d9312370ff89c34df1441b83125cb4d97a96005a', + '00209fddc9b4e070b887dec034ed74f15f62d075a3ac8cf6eb95a88c635e0207534c', + '0020c48f9c50958ab8e386a8bd3888076f31d12e5cf011ff46cc83c6fadfe6d47d20', + '0020a659f4621dca404571917e73dedb26b6d7c49a07dacbf15890760ac0583d3267'], + #change scriptpubkeys + ['002030213b5d3b6988b86aa13a9eaca08e718d51f32dc130c70981abb0102173c791', + '002027bd198f9783a58e9bc4d3fdbd1c75cc74154905cce1d23c7bd3e051695418fe', + '0020c1fd2cdebf120d3b1dc990dfdaca62382ff9525beeb6a79a908ddecb40e2162c', + '00207a3e478266e5fe49fe22e3d8f04d3adda3b6a0835806a0db1f77b84d0ba7f79c', + '002059e66462023ecd54e20d4dce286795e7d5823af511989736edc0c7a844e249f5', + '0020bd8077906dd367d6d107d960397e46db2daba5793249f1f032d8d7e12e6f193c']) +] + +electrum_bad_keydata_test_vectors = [ + "zpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2VJV7usvsFLZz" + + "2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz", #bad checksum + "a tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + + "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ tpubD6NzVbkrYhZ4WjgNYq2nF" + + "TbiSLW2SZAzs4g5JHLqwQ3AmR3tCWpqsZJJEoZuP5HAEBNxgYQhtWMezszoaeTCg6FWGQB" + + "T74sszGaxaf64o5s", #unparsable m number + "2 tpubD6NzVbkrYhZ4YVMVzC7wZeRfz3bhqcHvV8M3UiULCfzFtLtp5nwvi6LnBQegrkx" + + "YGPkSzXUEvcPEHcKdda8W1YShVBkhFBGkLxjSQ1Nx3cJ Vpub5fAqpSRkLmvXwqbuR61M" + + "aKMSwj5z5xUBwanaz3qnJ5MgaBDpFSLUvKTiNK9zHpdvrg2LHHXkKxSXBHNWNpZz9b1Vq" + + "ADjmcCs3arSoxN3F3r" #inconsistent magic +] def test(): - xpub = ("xpub661MyMwAqRbcGVQTLtBFzc3ENvyZHoUEhWRdGwoqLZaf5wXP9VcDY2V" + - "JV7usvsFLZz2RUTVhCVXYXc3S8zpLyAFbDFcfrpUiwLoE9VWH2yz") - wal = parse_electrum_master_public_key(xpub) - initial_count = 15 - gaplimit = 5 - spks = wal.get_scriptpubkeys(0, 0, initial_count) - #for test, generate 15, check that the last 5 lead to gap limit overrun - for i in range(initial_count - gaplimit): - ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[i]], gaplimit) - assert ret == None - for i in range(gaplimit): - index = i + initial_count - gaplimit - ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[index]], gaplimit) - assert ret != None and ret[0] == i+1 - last_index_add = 3 - last_index = initial_count - gaplimit + last_index_add - ret = wal.have_scriptpubkeys_overrun_gaplimit(spks[2:last_index], gaplimit) - assert ret[0] == last_index_add - print("Test passed successfully") + for keydata, recv_spks, change_spks in electrum_keydata_test_vectors: + initial_count = 15 + gaplimit = 5 + wal = parse_electrum_master_public_key(keydata, gaplimit) + spks = wal.get_scriptpubkeys(0, 0, initial_count) + #for test, generate 15, check that the last 5 lead to gap limit overrun + for i in range(initial_count - gaplimit): + ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[i]]) + assert ret == None + for i in range(gaplimit): + index = i + initial_count - gaplimit + ret = wal.have_scriptpubkeys_overrun_gaplimit([spks[index]]) + assert ret != None and ret[0] == i+1 + last_index_add = 3 + last_index = initial_count - gaplimit + last_index_add + ret = wal.have_scriptpubkeys_overrun_gaplimit(spks[2:last_index]) + assert ret[0] == last_index_add + assert wal.get_scriptpubkeys(0, 0, len(recv_spks)) == recv_spks + assert wal.get_scriptpubkeys(1, 0, len(change_spks)) == change_spks + for keydata in electrum_bad_keydata_test_vectors: + try: + parse_electrum_master_public_key(keydata, 5) + raised_error = False + except (ValueError, Exception): + raised_error = True + assert raised_error + print("All tests passed successfully") if __name__ == "__main__": test() + pass + diff --git a/server.py b/server.py @@ -209,6 +209,7 @@ def handle_query(sock, line, rpc, address_history, deterministic_wallets): send_response(sock, query, []) #no peers to report else: log("*** BUG! Not handling method: " + method + " query=" + str(query)) + #TODO just send back the same query will result = [] def get_block_header(rpc, blockhash): rpc_head = rpc.call("getblockheader", [blockhash]) @@ -594,7 +595,7 @@ def get_scriptpubkeys_to_monitor(rpc, config): watch_only_addresses = [] for key in config.options("watch-only-addresses"): watch_only_addresses.extend(config.get("watch-only-addresses", - key).replace(' ', ',').split(',')) + key).split(' ')) watch_only_addresses = set(watch_only_addresses) watch_only_addresses_to_import = [] if not watch_only_addresses.issubset(imported_addresses):