def test_random_access(self): p = BIP32Path("m/4h/5h/1/4") self.assertEqual(p[0], 4 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(p[1], 5 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(p[2], 1) self.assertEqual(p[3], 4) p = BIP32Path([0xFFFFFFFF - n for n in range(255)]) self.assertEqual(p[254], 0xFFFFFF01)
def test_from_list(self): with self.assertRaises(ValueError): BIP32Path([-1]) with self.assertRaises(ValueError): BIP32Path([0xFFFFFFFF + 1]) # more than 32bit with self.assertRaises(ValueError): # only apostrophe and "h" markers are allowed BIP32Path([0xFFFFFFFF, 0, 0x80000000], hardened_marker='b') with self.assertRaises(ValueError): # too long path BIP32Path([0 for _ in range(256)]) self.assertEqual(str(BIP32Path([0])), "m/0") self.assertEqual(str(BIP32Path([])), "m") self.assertEqual( str(BIP32Path([0xFFFFFFFF, 0x80000001, 1, 0x80000002])), "m/2147483647'/1'/1/2'") self.assertEqual( str(BIP32Path([0xFFFFFFFF, 0x80000001, 1, 2], hardened_marker='h')), "m/2147483647h/1h/1/2") self.assertEqual( str( BIP32Path([n + BIP32_HARDENED_KEY_OFFSET for n in range(128)] + [n for n in range(127)])), 'm/' + '/'.join("%u'" % n for n in range(128)) + '/' + '/'.join("%u" % n for n in range(127)))
def test_from_BIP32Path(self) -> None: p = BIP32Path("m/4h/5h/1/4") self.assertEqual(str(BIP32Path(p)), "m/4h/5h/1/4") self.assertEqual(str(BIP32Path(p, hardened_marker="'")), "m/4'/5'/1/4") p = BIP32Path("m/4'/5'/1/4") self.assertEqual(str(BIP32Path(p)), "m/4'/5'/1/4") self.assertEqual(str(BIP32Path(p, hardened_marker='h')), "m/4h/5h/1/4") p = BIP32Path("4'/5'/1/4") self.assertEqual(str(BIP32Path(p)), "4'/5'/1/4") self.assertEqual(str(BIP32Path(p, hardened_marker='h')), "4h/5h/1/4")
def assemble_psbt_change_output(self, psbt: PartiallySignedTransaction): change_output = psbt.outputs[1] last_change_address = self.watch_only_wallet.last_change_address address_derivation_info = PSBT_KeyDerivationInfo( self.watch_only_wallet.master_fingerprint, BIP32Path(last_change_address.key_path)) change_output.derivation_map[ last_change_address.pub_key] = address_derivation_info
def test_path_as_list(self) -> None: self.assertEqual(list(BIP32Path('m/0')), [0]) self.assertEqual(list(BIP32Path('0')), [0]) self.assertEqual( list(BIP32Path("m/4h/5/1h")), [4 + BIP32_HARDENED_KEY_OFFSET, 5, 1 + BIP32_HARDENED_KEY_OFFSET]) self.assertEqual(list(BIP32Path("m/0'/2147483647'/1/10")), [BIP32_HARDENED_KEY_OFFSET, 0xFFFFFFFF, 1, 10]) self.assertEqual( list( BIP32Path('m/' + '/'.join("%u'" % n for n in range(128)) + '/' + '/'.join("%u" % (BIP32_HARDENED_KEY_OFFSET - n - 1) for n in range(127)))), [n + BIP32_HARDENED_KEY_OFFSET for n in range(128)] + [BIP32_HARDENED_KEY_OFFSET - n - 1 for n in range(127)])
def T(base_xpub, expected_child, path): xpub = CBitcoinExtPubKey(base_xpub) for child_num in path: xpub = xpub.derive(child_num) self.assertEqual(str(CBitcoinExtPubKey.from_bytes(xpub)), expected_child) xpub = CBitcoinExtPubKey(base_xpub).derive_path( str(BIP32Path(path))) self.assertEqual(str(CBitcoinExtPubKey.from_bytes(xpub)), expected_child)
def test_standard_bip32_vectors(self): for vector in BIP32_TEST_VECTORS: _, seed = vector[0] base_key = CBitcoinExtKey.from_seed(x(seed)) key = base_key path = [] for xpub, xpriv, child_num in vector[1:]: self.assertEqual(xpub, str(key.neuter())) self.assertEqual(xpriv, str(key)) key = key.derive(child_num) path.append(child_num) key_from_path = base_key.derive_path(str(BIP32Path(path))) self.assertEqual(key, key_from_path)
def T(base_xpub: str, expected_child: str, path: List[int]) -> None: xpub = CBitcoinExtPubKey(base_xpub) self.assertEqual(xpub.parent_fp, b'\x00\x00\x00\x00') for child_num in path: parent_fp = xpub.fingerprint xpub = xpub.derive(child_num) self.assertEqual(xpub.parent_fp, parent_fp) self.assertEqual(str(CBitcoinExtPubKey.from_bytes(xpub)), expected_child) xpub = CBitcoinExtPubKey(base_xpub).derive_path( str(BIP32Path(path))) self.assertEqual(str(CBitcoinExtPubKey.from_bytes(xpub)), expected_child)
def test_random_access(self) -> None: p = BIP32Path("m/4h/5h/1/4") self.assertEqual(p[0], 4 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(p[1], 5 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(p[2], 1) self.assertEqual(p[3], 4) p = BIP32Path([0xFFFFFFFF - n for n in range(255)]) self.assertEqual(p[254], 0xFFFFFF01) pt = BIP32PathTemplate("m/4h/5h/[1-2]/[4,7]") self.assertEqual(pt[0][0][0], 4 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(pt[0][0][1], 4 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(pt[1][0][0], 5 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(pt[1][0][1], 5 + BIP32_HARDENED_KEY_OFFSET) self.assertEqual(pt[2][0][0], 1) self.assertEqual(pt[2][0][1], 2) self.assertEqual(pt[3][0][0], 4) self.assertEqual(pt[3][0][1], 4) self.assertEqual(pt[3][1][0], 7) self.assertEqual(pt[3][1][1], 7) pt = BIP32PathTemplate([[(0xFFFFFFFF - n, 0xFFFFFFFF - n)] for n in range(255)]) self.assertEqual(pt[254][0][0], 0xFFFFFF01) self.assertEqual(pt[254][0][1], 0xFFFFFF01)
def assembe_psbt_inputs(self, psbt: PartiallySignedTransaction, utxos: List[Utxo]): for i, psbt_input in enumerate(psbt.inputs): utxo = utxos[i] address = self.watch_only_wallet.get_address(utxo.address) address_derivation_info = PSBT_KeyDerivationInfo( self.watch_only_wallet.master_fingerprint, BIP32Path(address.key_path)) psbt_input.derivation_map[ address.pub_key] = address_derivation_info if utxo.is_witness_utxo: psbt_input.set_utxo(utxo.tx_out, psbt.unsigned_tx) else: transaction = self.blockchain_client.get_transaction( utxo.tx_hash) psbt_input.set_utxo(transaction, psbt.unsigned_tx)
def test_standard_bip32_vectors(self) -> None: for vector in BIP32_TEST_VECTORS: _, seed, _ = vector[0] base_key = CBitcoinExtKey.from_seed(x(seed)) self.assertEqual(base_key.parent_fp, b'\x00\x00\x00\x00') key = base_key path = [] for xpub, xpriv, child_num in vector[1:]: self.assertEqual(xpub, str(key.neuter())) self.assertEqual(xpriv, str(key)) parent_fp = key.fingerprint key = key.derive(child_num) self.assertEqual(key.parent_fp, parent_fp) path.append(child_num) key_from_path = base_key.derive_path(str(BIP32Path(path))) self.assertEqual(key, key_from_path)
except ValueError: pass else: print("ERROR: specified key does not appear to be valid") sys.exit(-1) path_str = sys.argv[1] if not path_str.startswith('m'): # Allow users to omit path prefix if path_str == '': path_str = 'm' else: path_str = 'm/' + path_str.lstrip('/') path = BIP32Path(path_str) if len(path) == 0: # NOTE: xkey.derive_path() method will raise ValueError # on empty path, to guard against bugs: # if there is nothing to derive, why call derive_path() ? print('ERROR: nothing to derive, path is empty.') sys.exit(-1) for n in path: print("child number: 0x{:08x}".format(n)) xkey = xkey.derive(n) if isinstance(xkey, CBitcoinExtKey): print("xpriv:", xkey) # Note:
def test(self) -> None: xpriv1 = CCoinExtKey( 'xprv9s21ZrQH143K4TFwadu5VoGfAChTWXUw49YyTWE8SRqC9ZC9AQpHspzgbAcScTmC4MURiMT7pmCbci5oKbWijJmARiUeRiLXYehCtsoVdYf' ) xpriv2 = CCoinExtKey( 'xprv9uZ4jKNZFfGEQTTunEuy2cLQMckzuy5saCmiKuxYJgHX5pGFCx3KQ8mTkSfuLNaWGNQ9LKCg5YzUihxoQv493ErnkcaS3q1udx9X8WZbwZc' ) priv1 = CCoinKey( 'L27zAtDgjDC34sG5ZSey1wvdZ9JyZsNnvZEwbbZYWUYXXQtgri5R') xpub1 = CCoinExtPubKey( 'xpub69b6hm71WMe1PGpgUmaDPkbxYoTzpmswX8KGeinv7SPRcKT22RdMM4416kqtEUuXqXCAi7oGx7tHwCRTd3JHatE3WX1Zms6Lgj5mrbFyuro' ) xpub1.assign_derivation_info( KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0'))) pub1 = CPubKey( x('03b0fe9cfc88fed9fcecf9dcb7bb5c90dd1a4500f4cfc5c854ffc8e54d639d6bc5' )) kstore = KeyStore( external_privkey_lookup=(lambda key_id, dinfo: priv1 if key_id == priv1.pub.key_id else None), external_pubkey_lookup=(lambda key_id, dinfo: pub1 if key_id == pub1.key_id else None)) self.assertEqual(kstore.get_privkey(priv1.pub.key_id), priv1) self.assertEqual(kstore.get_pubkey(pub1.key_id), pub1) self.assertEqual(kstore.get_pubkey(priv1.pub.key_id), priv1.pub) kstore = KeyStore(xpriv1, priv1, xpub1, pub1, require_path_templates=False) self.assertEqual(kstore.get_privkey(priv1.pub.key_id), priv1) self.assertEqual(kstore.get_pubkey(priv1.pub.key_id), priv1.pub) self.assertEqual(kstore.get_pubkey(pub1.key_id), pub1) # check that no-derivation lookup for (priv, pub) of extended keys # does not return anything if derivation is not supplied, # but returns pubkey when empty path is supplied self.assertEqual(kstore.get_privkey(xpriv1.pub.key_id), None) self.assertEqual( kstore.get_privkey( xpriv1.pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m"))), xpriv1.priv) self.assertEqual(kstore.get_pubkey(xpriv1.pub.key_id), None) self.assertEqual( kstore.get_pubkey( xpriv1.pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m"))), xpriv1.pub) # can't find xpub1's pub without derivation self.assertEqual(kstore.get_pubkey(xpub1.pub.key_id), None) # can find with correct derivation info supplied self.assertEqual( kstore.get_pubkey( xpub1.pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path("m/0"))), xpub1.pub) # but not with incorrect derivation info self.assertEqual( kstore.get_pubkey( xpub1.pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path("m"))), None) # check longer derivations self.assertEqual( kstore.get_privkey(xpriv1.derive_path("0'/1'/2'").pub.key_id), None) self.assertEqual( kstore.get_privkey( xpriv1.derive_path("0'/1'/2'").pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m/0'/1'/2'"))), xpriv1.derive_path("0'/1'/2'").priv) self.assertEqual( kstore.get_pubkey( xpriv1.derive_path("0'/1'/2'").pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m/0'/1'/2'"))), xpriv1.derive_path("0'/1'/2'").pub) self.assertEqual( kstore.get_pubkey( xpub1.derive_path("0/1/2").pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0/0/1/2'))), xpub1.derive_path("0/1/2").pub) path = BIP32Path("0'/1'/2'") derived_xpub = xpriv2.derive_path(path).neuter() derived_pub = derived_xpub.derive_path('3/4/5').pub self.assertEqual(kstore.get_pubkey(derived_pub.key_id), None) kstore.add_key(derived_xpub) self.assertEqual( kstore.get_pubkey( derived_pub.key_id, KeyDerivationInfo(xpriv2.parent_fp, BIP32Path("m/0/0'/1'/2'/3/4/5"))), derived_pub) kstore.add_key(xpriv2) derived_pub = xpriv2.derive_path('3h/4h/5h').pub self.assertEqual( kstore.get_pubkey( derived_pub.key_id, KeyDerivationInfo(xpriv2.parent_fp, BIP32Path("m/0/3'/4'/5'"))), derived_pub) derived_priv = xpriv2.derive_path('3h/4h/5h').priv self.assertEqual( kstore.get_privkey( derived_priv.pub.key_id, KeyDerivationInfo(xpriv2.parent_fp, BIP32Path("m/0/3'/4'/5'"))), derived_priv) # check that .remove_key() works kstore.remove_key(xpriv1) kstore.remove_key(xpub1) kstore.remove_key(priv1) kstore.remove_key(pub1) self.assertEqual(kstore.get_privkey(priv1.pub.key_id), None) self.assertEqual(kstore.get_pubkey(pub1.key_id), None) self.assertEqual( kstore.get_privkey( xpriv1.derive_path("0'/1'/2'").pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path("m/0'/1'/2'"))), None) self.assertEqual( kstore.get_pubkey(xpub1.derive_path("0/1/2").pub.key_id), None)
def test_path_template_enforcement(self) -> None: xpriv1 = CCoinExtKey( 'xprv9s21ZrQH143K4TFwadu5VoGfAChTWXUw49YyTWE8SRqC9ZC9AQpHspzgbAcScTmC4MURiMT7pmCbci5oKbWijJmARiUeRiLXYehCtsoVdYf' ) xpriv2 = CCoinExtKey( 'xprv9s21ZrQH143K3QgBvK4tkeHuvuWc6KETTTcgGQ4NmW7g16AtCPV4hZpujiimpLM9ivFPgsMdNNVuVUnDwChutxczNKYHzP1Mo5HuqG7CNYv' ) assert xpriv2.derivation_info assert len(xpriv2.derivation_info.path) == 0 priv1 = CCoinKey( 'L27zAtDgjDC34sG5ZSey1wvdZ9JyZsNnvZEwbbZYWUYXXQtgri5R') xpub1 = CCoinExtPubKey( 'xpub69b6hm71WMe1PGpgUmaDPkbxYoTzpmswX8KGeinv7SPRcKT22RdMM4416kqtEUuXqXCAi7oGx7tHwCRTd3JHatE3WX1Zms6Lgj5mrbFyuro' ) xpub2 = xpriv2.derive(333).neuter() xpub1.assign_derivation_info( KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0'))) pub1 = CPubKey( x('03b0fe9cfc88fed9fcecf9dcb7bb5c90dd1a4500f4cfc5c854ffc8e54d639d6bc5' )) xpub3 = xpub1.derive(0) xpub3.assign_derivation_info( KeyDerivationInfo(x('abcdef10'), BIP32Path('m/0/0'))) # No error when require_path_templates is not set KeyStore(xpriv1, xpriv2, priv1, xpub1, pub1, require_path_templates=False) with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): KeyStore((priv1, BIP32PathTemplate(''))) # type: ignore with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): KeyStore((pub1, [BIP32PathTemplate('')])) # type: ignore with self.assertRaisesRegex(ValueError, 'path templates must be specified'): KeyStore(xpriv1) with self.assertRaisesRegex(ValueError, 'path templates must be specified'): KeyStore(xpub1) # same but via add_key ks = KeyStore() with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): ks.add_key((priv1, BIP32PathTemplate(''))) # type: ignore with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): ks.add_key((pub1, [BIP32PathTemplate('')])) # type: ignore with self.assertRaisesRegex(ValueError, 'path templates list is empty'): ks.add_key((pub1, [])) # type: ignore with self.assertRaisesRegex(ValueError, 'only make sense for extended keys'): ks.add_key((pub1, '')) # type: ignore with self.assertRaisesRegex(ValueError, 'index template format is not valid'): ks.add_key((xpub1, 'abc')) # type: ignore with self.assertRaisesRegex(TypeError, 'is expected to be an instance of '): ks.add_key((xpub1, [10])) # type: ignore with self.assertRaisesRegex(ValueError, 'path templates must be specified'): ks.add_key(xpriv1) with self.assertRaisesRegex(ValueError, 'path templates must be specified'): ks.add_key(xpub1) # No error when path templates are specified for extended keys ks = KeyStore( (xpriv1, BIP32PathTemplate('m')), (xpriv2, 'm/[44,49,84]h/0h/0h/[0-1]/*'), (xpub1, ''), # '' same as BIP32PathTemplate('') (xpub2, ['0/1', 'm/333/3/33']), (xpub3, BIP32PathTemplate('m/0/0/1')), priv1, pub1) self.assertEqual(ks.get_privkey(priv1.pub.key_id), priv1) self.assertEqual(ks.get_pubkey(pub1.key_id), pub1) # still can find non-extended priv even if derivation info is # specified, because there's exact match. self.assertEqual( ks.get_privkey(priv1.pub.key_id, KeyDerivationInfo(xpriv1.parent_fp, BIP32Path("m"))), priv1) self.assertEqual(ks.get_pubkey(pub1.key_id), pub1) # can't find without derivation specified self.assertEqual(ks.get_privkey(xpriv1.pub.key_id), None) # but can find with derivation specified self.assertEqual( ks.get_privkey( xpriv1.pub.key_id, KeyDerivationInfo(xpriv1.fingerprint, BIP32Path('m'))), xpriv1.priv) # can't find without derivation specified self.assertEqual(ks.get_pubkey(xpub1.pub.key_id), None) # can find with derivation specified self.assertEqual( ks.get_pubkey(xpub1.pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0'))), xpub1.pub) # exception when derivation goes beyond template with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub1.derive(1).pub.key_id, KeyDerivationInfo(xpub1.parent_fp, BIP32Path('m/0/1'))) # success when template allows self.assertEqual( ks.get_pubkey( xpub3.derive(1).pub.key_id, KeyDerivationInfo(x('abcdef10'), BIP32Path('m/0/0/1'))), xpub3.derive(1).pub) # fails when template not allows with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub3.derive(2).pub.key_id, KeyDerivationInfo(x('abcdef10'), BIP32Path('m/0/0/2'))) long_path = BIP32Path( "m/43435/646/5677/5892/58885/2774/9943/75532/8888") with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path(long_path).pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, long_path)) with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path("44'/0'/0'/3/25").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/3/25'))) with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path("44'/0'/0'/0/1'").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/0/1h'))) self.assertEqual( ks.get_privkey( xpriv2.derive_path("44'/0'/0'/1/25").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/1/25'))), xpriv2.derive_path("44'/0'/0'/1/25").priv) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub2.derive_path('0').pub.key_id, KeyDerivationInfo(xpub2.parent_fp, BIP32Path('m/333/0'))) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub2.derive_path('3/34').pub.key_id, KeyDerivationInfo(xpub2.parent_fp, BIP32Path('m/333/3/34'))) self.assertEqual( ks.get_pubkey( xpub2.derive_path('3/33').pub.key_id, KeyDerivationInfo(xpub2.parent_fp, BIP32Path('m/333/3/33'))), xpub2.derive_path('3/33').pub) xpub49 = xpriv2.derive_path("m/49'/0'/0'/0").neuter() with self.assertRaisesRegex(ValueError, 'must specify full path'): ks = KeyStore( xpriv2, xpub49, default_path_template='[44,49,84]h/0h/0h/[0-1]/[0-50000]') ks = KeyStore( xpriv2, xpub49, default_path_template='m/[44,49,84]h/0h/0h/[0-1]/[0-50000]') with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path(long_path).pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, long_path)) with self.assertRaises(BIP32PathTemplateViolation): ks.get_privkey( xpriv2.derive_path("44'/0'/0'/1/50001").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/1/50001'))) self.assertEqual( ks.get_privkey( xpriv2.derive_path("44'/0'/0'/1/25").pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/44h/0h/0h/1/25'))), xpriv2.derive_path("44'/0'/0'/1/25").priv) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub49.derive_path('50001').pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/49h/0h/0h/0/50001'))) with self.assertRaises(BIP32PathTemplateViolation): ks.get_pubkey( xpub49.derive_path('50000/3').pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/49h/0h/0h/0/50000/3'))) self.assertEqual( ks.get_pubkey( xpub49.derive_path('50000').pub.key_id, KeyDerivationInfo(xpriv2.fingerprint, BIP32Path('m/49h/0h/0h/0/50000'))), xpub49.derive_path('50000').pub)
def test_BIP32PathTemplate_match_path(self) -> None: t_partial = BIP32PathTemplate("4'/5'/1/[3,4,5-50]") t_full = BIP32PathTemplate("m/4'/5'/1/[3,4,5-50]") for v in [3, 4] + list(range(5, 50)): self.assertTrue(t_partial.match_path(BIP32Path(f"4'/5'/1/{v}"))) for v in [3, 4] + list(range(5, 50)): self.assertTrue(t_full.match_path(BIP32Path(f"m/4'/5'/1/{v}"))) self.assertFalse(t_full.match_path(BIP32Path("4'/5'/1/3"))) self.assertFalse(t_partial.match_path(BIP32Path("m/4'/5'/1/3"))) self.assertFalse(t_full.match_path(BIP32Path("m/4'/5'/1"))) self.assertFalse(t_partial.match_path(BIP32Path("4'/5'/1"))) self.assertFalse(t_full.match_path(BIP32Path("m/4'/5'/1/3/1"))) self.assertFalse(t_partial.match_path(BIP32Path("4'/5'/1/3/1"))) self.assertTrue( BIP32PathTemplate("m/4'/5'/1/[3,4,5-50]/*").match_path( BIP32Path("m/4'/5'/1/3/1"))) self.assertTrue( BIP32PathTemplate("4h/5h/1h/[3,4,5-50]h/*h").match_path( BIP32Path("4'/5'/1'/3'/323452'"))) self.assertFalse( BIP32PathTemplate("4h/5h/1h/[3,4,5-50]h/*h").match_path( BIP32Path("4'/5'/1'/3/323452'"))) self.assertFalse( BIP32PathTemplate("4h/5h/1h/[3,4,5-50]h/*h").match_path( BIP32Path("4'/5'/1'/3'/323452"))) self.assertTrue( BIP32PathTemplate("*h").match_path(BIP32Path("323452h"))) self.assertTrue( BIP32PathTemplate("[0-100,200-300]h").match_path(BIP32Path("99h"))) self.assertTrue( BIP32PathTemplate("[0-100,200-300]h").match_path( BIP32Path("299h"))) self.assertFalse( BIP32PathTemplate("[0-100,200-300]h").match_path( BIP32Path("199h"))) self.assertTrue(BIP32PathTemplate("m").match_path(BIP32Path("m"))) self.assertTrue(BIP32PathTemplate("").match_path(BIP32Path(""))) self.assertFalse(BIP32PathTemplate("m").match_path(BIP32Path(""))) self.assertFalse(BIP32PathTemplate("").match_path(BIP32Path("m"))) self.assertTrue( BIP32PathTemplate('/'.join(str(v) for v in range(255))).match_path( BIP32Path('/'.join(str(v) for v in range(255)))))
def bip_84_keypath(): BIP_84_KEYPATH = "84'" return BIP32Path(BIP_84_KEYPATH)
def external_chain_keypath(): EXTERNAL_CHAIN_KEYPATH = "0" return BIP32Path(EXTERNAL_CHAIN_KEYPATH)
def test_path_from_list(self) -> None: with self.assertRaisesRegex(ValueError, 'cannot be negative'): BIP32Path([-1]) with self.assertRaisesRegex(ValueError, 'derivation index cannot be'): BIP32Path([0xFFFFFFFF + 1]) # more than 32bit with self.assertRaisesRegex(ValueError, 'unsupported hardened_marker'): # only apostrophe and "h" markers are allowed BIP32Path([0xFFFFFFFF, 0, 0x80000000], hardened_marker='b') with self.assertRaisesRegex( ValueError, 'derivation path longer than 255 elements'): # too long path BIP32Path([0 for _ in range(256)]) self.assertEqual(str(BIP32Path([0], is_partial=False)), "m/0") self.assertEqual(str(BIP32Path(BIP32Path([0], is_partial=False))), "m/0") self.assertEqual(str(BIP32Path(BIP32Path([0], is_partial=True))), "0") self.assertEqual(str(BIP32Path([], is_partial=False)), "m") self.assertEqual(str(BIP32Path([0])), "0") self.assertEqual(str(BIP32Path([])), "") self.assertEqual( str(BIP32Path([0xFFFFFFFF, 0x80000001, 1, 0x80000002])), "2147483647'/1'/1/2'") self.assertEqual( str( BIP32Path([0xFFFFFFFF, 0x80000001, 1, 2], hardened_marker='h', is_partial=False)), "m/2147483647h/1h/1/2") self.assertEqual( str( BIP32Path([n + BIP32_HARDENED_KEY_OFFSET for n in range(128)] + [n for n in range(127)], is_partial=False)), 'm/' + '/'.join("%u'" % n for n in range(128)) + '/' + '/'.join("%u" % n for n in range(127)))
def change_chain_keypath(): CHANGE_CHAIN_KEYPATH = "1" return BIP32Path(CHANGE_CHAIN_KEYPATH)
def bip_44_keypath(): BIP_44_KEYPATH = "44'" return BIP32Path(BIP_44_KEYPATH)
def bip_49_keypath(): BIP_49_KEYPATH = "49'" return BIP32Path(BIP_49_KEYPATH)
def test_from_string(self): with self.assertRaises(ValueError): BIP32Path('') # empty path that is not 'm' with self.assertRaises(ValueError): BIP32Path('m/') # empty path that is not 'm' with self.assertRaises(ValueError): BIP32Path('/') with self.assertRaises(ValueError): BIP32Path('nonsense') with self.assertRaises(ValueError): BIP32Path('m/-1') with self.assertRaises(ValueError): BIP32Path('m/4/') # slash at the end of the path with self.assertRaises(ValueError): BIP32Path("m/4h/1'") # inconsistent use of markers with self.assertRaises(ValueError): BIP32Path("m/2147483648'/1'") # hardened index too big with self.assertRaises(ValueError): BIP32Path("m/2147483648/1") # non-hardened index too big with self.assertRaises(ValueError): # wrong markers BIP32Path("m/2147483647'/1'/0", hardened_marker='h') with self.assertRaises(ValueError): # wrong markers BIP32Path("m/2147483647h/1h/0", hardened_marker="'") with self.assertRaises(ValueError): # invalid marker BIP32Path("m/2147483647h/1h/0", hardened_marker="?") with self.assertRaises(ValueError): # too long path BIP32Path('m/' + '/'.join('0' for _ in range(256))) self.assertEqual(list(BIP32Path('m/0')), [0]) self.assertEqual(list(BIP32Path('m')), []) self.assertEqual( list(BIP32Path("m/4h/5/1h")), [4 + BIP32_HARDENED_KEY_OFFSET, 5, 1 + BIP32_HARDENED_KEY_OFFSET]) # check that markers correctly picked up from the string self.assertEqual(str(BIP32Path("m/4h/5h/1/4")), "m/4h/5h/1/4") self.assertEqual(str(BIP32Path("m/4'/5'/1/4")), "m/4'/5'/1/4") self.assertEqual(list(BIP32Path("m/0'/2147483647'/1/10")), [BIP32_HARDENED_KEY_OFFSET, 0xFFFFFFFF, 1, 10]) self.assertEqual( list( BIP32Path('m/' + '/'.join("%u'" % n for n in range(128)) + '/' + '/'.join("%u" % (BIP32_HARDENED_KEY_OFFSET - n - 1) for n in range(127)))), [n + BIP32_HARDENED_KEY_OFFSET for n in range(128)] + [BIP32_HARDENED_KEY_OFFSET - n - 1 for n in range(127)])