electrum-personal-server

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

deterministic.py (6358B)


      1 from electrumpersonalserver.bitcoin.main import *
      2 import hmac
      3 import hashlib
      4 from binascii import hexlify
      5 
      6 # Below code ASSUMES binary inputs and compressed pubkeys
      7 MAINNET_PRIVATE = b'\x04\x88\xAD\xE4'
      8 #MAINNET_PUBLIC = b'\x04\x88\xB2\x1E'
      9 TESTNET_PRIVATE = b'\x04\x35\x83\x94'
     10 #TESTNET_PUBLIC = b'\x04\x35\x87\xCF'
     11 PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE]
     12 #PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC]
     13 
     14 #updated for electrum's bip32 version bytes
     15 #only public keys because electrum personal server only needs them
     16 #https://github.com/spesmilo/electrum-docs/blob/master/xpub_version_bytes.rst
     17 PUBLIC = [  b'\x04\x88\xb2\x1e', #mainnet p2pkh or p2sh xpub
     18             b'\x04\x9d\x7c\xb2', #mainnet p2wpkh-p2sh ypub
     19             b'\x02\x95\xb4\x3f', #mainnet p2wsh-p2sh Ypub
     20             b'\x04\xb2\x47\x46', #mainnet p2wpkh zpub
     21             b'\x02\xaa\x7e\xd3', #mainnet p2wsh Zpub
     22             b'\x04\x35\x87\xcf', #testnet p2pkh or p2sh tpub
     23             b'\x04\x4a\x52\x62', #testnet p2wpkh-p2sh upub
     24             b'\x02\x42\x89\xef', #testnet p2wsh-p2sh Upub
     25             b'\x04\x5f\x1c\xf6', #testnet p2wpkh vpub
     26             b'\x02\x57\x54\x83' #testnet p2wsh Vpub
     27         ]
     28 
     29 # BIP32 child key derivation
     30 
     31 def raw_bip32_ckd(rawtuple, i):
     32     vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple
     33     i = int(i)
     34 
     35     if vbytes in PRIVATE:
     36         priv = key
     37         pub = privtopub(key)
     38     else:
     39         pub = key
     40 
     41     if i >= 2**31:
     42         if vbytes in PUBLIC:
     43             raise ValueError("Can't do private derivation on public key!")
     44         I = hmac.new(chaincode, b'\x00' + priv[:32] + encode(i, 256, 4),
     45                      hashlib.sha512).digest()
     46     else:
     47         I = hmac.new(chaincode, pub + encode(i, 256, 4),
     48                      hashlib.sha512).digest()
     49 
     50     if vbytes in PRIVATE:
     51         newkey = add_privkeys(I[:32] + B'\x01', priv)
     52         fingerprint = bin_hash160(privtopub(key))[:4]
     53     if vbytes in PUBLIC:
     54         newkey = add_pubkeys(compress(privtopub(I[:32])), key)
     55         fingerprint = bin_hash160(key)[:4]
     56 
     57     return (vbytes, depth + 1, fingerprint, i, I[32:], newkey)
     58 
     59 
     60 def bip32_serialize(rawtuple):
     61     vbytes, depth, fingerprint, i, chaincode, key = rawtuple
     62     i = encode(i, 256, 4)
     63     chaincode = encode(hash_to_int(chaincode), 256, 32)
     64     keydata = b'\x00' + key[:-1] if vbytes in PRIVATE else key
     65     bindata = vbytes + from_int_to_byte(
     66         depth % 256) + fingerprint + i + chaincode + keydata
     67     return changebase(bindata + bin_dbl_sha256(bindata)[:4], 256, 58)
     68 
     69 
     70 def bip32_deserialize(data):
     71     dbin = changebase(data, 58, 256)
     72     if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]:
     73         raise ValueError("Invalid checksum")
     74     vbytes = dbin[0:4]
     75     depth = from_byte_to_int(dbin[4])
     76     fingerprint = dbin[5:9]
     77     i = decode(dbin[9:13], 256)
     78     chaincode = dbin[13:45]
     79     key = dbin[46:78] + b'\x01' if vbytes in PRIVATE else dbin[45:78]
     80     return (vbytes, depth, fingerprint, i, chaincode, key)
     81 
     82 
     83 def raw_bip32_privtopub(rawtuple):
     84     vbytes, depth, fingerprint, i, chaincode, key = rawtuple
     85     newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC
     86     return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key))
     87 
     88 
     89 def bip32_privtopub(data):
     90     return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data)))
     91 
     92 
     93 def bip32_ckd(data, i):
     94     return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i))
     95 
     96 
     97 def bip32_master_key(seed, vbytes=MAINNET_PRIVATE):
     98     I = hmac.new(
     99         from_string_to_bytes("Bitcoin seed"), seed, hashlib.sha512).digest()
    100     return bip32_serialize((vbytes, 0, b'\x00' * 4, 0, I[32:], I[:32] + b'\x01'
    101                            ))
    102 
    103 
    104 def bip32_bin_extract_key(data):
    105     return bip32_deserialize(data)[-1]
    106 
    107 
    108 def bip32_extract_key(data):
    109     return safe_hexlify(bip32_deserialize(data)[-1])
    110 
    111 # Exploits the same vulnerability as above in Electrum wallets
    112 # Takes a BIP32 pubkey and one of the child privkeys of its corresponding
    113 # privkey and returns the BIP32 privkey associated with that pubkey
    114 
    115 def raw_crack_bip32_privkey(parent_pub, priv):
    116     vbytes, depth, fingerprint, i, chaincode, key = priv
    117     pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub
    118     i = int(i)
    119 
    120     if i >= 2**31:
    121         raise ValueError("Can't crack private derivation!")
    122 
    123     I = hmac.new(pchaincode, pkey + encode(i, 256, 4), hashlib.sha512).digest()
    124 
    125     pprivkey = subtract_privkeys(key, I[:32] + b'\x01')
    126 
    127     newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE
    128     return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey)
    129 
    130 
    131 def crack_bip32_privkey(parent_pub, priv):
    132     dsppub = bip32_deserialize(parent_pub)
    133     dspriv = bip32_deserialize(priv)
    134     return bip32_serialize(raw_crack_bip32_privkey(dsppub, dspriv))
    135 
    136 def bip32_descend(*args):
    137     if len(args) == 2:
    138         key, path = args
    139     else:
    140         key, path = args[0], map(int, args[1:])
    141     for p in path:
    142         key = bip32_ckd(key, p)
    143     return bip32_extract_key(key)
    144 
    145 # electrum
    146 def electrum_stretch(seed):
    147     return slowsha(seed)
    148 
    149 # Accepts seed or stretched seed, returns master public key
    150 
    151 def electrum_mpk(seed):
    152     if len(seed) == 32:
    153         seed = electrum_stretch(seed)
    154     return privkey_to_pubkey(seed)[2:]
    155 
    156 # Accepts (seed or stretched seed), index and secondary index
    157 # (conventionally 0 for ordinary addresses, 1 for change) , returns privkey
    158 
    159 
    160 def electrum_privkey(seed, n, for_change=0):
    161     if len(seed) == 32:
    162         seed = electrum_stretch(seed)
    163     mpk = electrum_mpk(seed)
    164     offset = dbl_sha256(from_int_representation_to_bytes(n)+b':'+
    165         from_int_representation_to_bytes(for_change)+b':'+
    166         binascii.unhexlify(mpk))
    167     return add_privkeys(seed, offset)
    168 
    169 # Accepts (seed or stretched seed or master pubkey), index and secondary index
    170 # (conventionally 0 for ordinary addresses, 1 for change) , returns pubkey
    171 
    172 def electrum_pubkey(masterkey, n, for_change=0):
    173     if len(masterkey) == 32:
    174         mpk = electrum_mpk(electrum_stretch(masterkey))
    175     elif len(masterkey) == 64:
    176         mpk = electrum_mpk(masterkey)
    177     else:
    178         mpk = masterkey
    179     bin_mpk = encode_pubkey(mpk, 'bin_electrum')
    180     offset = bin_dbl_sha256(from_int_representation_to_bytes(n)+b':'+
    181         from_int_representation_to_bytes(for_change)+b':'+bin_mpk)
    182     return add_pubkeys('04'+mpk, privtopub(offset))
    183