def confirmcode(confirmationcode, passphrase): """ A confirmation tool, given a passphrase and a confirmation code, can recalculate the address, verify the address hash, and then assert the following: "It is confirmed that Bitcoin address address depends on this passphrase". To recalculate the address: """ #decode the confirmationcode to give addresshash, ownerentropy and encryptedpointb data = enc.b58decode(confirmationcode) checksum = data[-4:] hash = hashlib.sha256(hashlib.sha256(data[:-4]).digest()).digest()[:4] assert hash == checksum addresshash = data[6:10] ownerentropy = data[10:18] encryptedpointb = data[18:51] pointbx1 = encryptedpointb[1:17] pointbx2 = encryptedpointb[17:33] #1. Derive passfactor using scrypt with ownerentropy and the user's passphrase and use it to recompute passpoint passfactor = scrypt.hash(passphrase, ownerentropy, 16384, 8, 8, 32) pub = elip.base10_multiply(elip.G, enc.decode(passfactor, 256)) passpoint = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)).decode('hex') #2. Derive decryption key for pointb using scrypt with passpoint, addresshash, and ownerentropy key = scrypt.hash(passpoint, addresshash + ownerentropy, 1024, 1, 1, 64) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Decrypt encryptedpointb to yield pointb Aes = aes.Aes(derivedhalf2) decryptedhalf1 = Aes.dec(pointbx1) decryptedhalf2 = Aes.dec(pointbx2) pointb = '0' + decryptedhalf1 + decryptedhalf2 pointb = binascii.unhexlify('%064x' % (long(binascii.hexlify(pointb), 16) ^ long(binascii.hexlify(derivedhalf1), 16))) #4. ECMultiply pointb by passfactor. Use the resulting EC point as a public key and hash it into address using either compressed or uncompressed public key # methodology as specified in flagbyte. pub = elip.base10_multiply(pointb, enc.decode(passfactor, 256)) print('pub[0] = ' + str(pub[0])) print('pub[1] = ' + str(pub[1])) publicKey = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)) print('pubKey = ' + publicKey) generatedaddress = address.publicKey2Address(publicKey) print('generatedaddress = ' + generatedaddress) #print(hashlib.sha256(hashlib.sha256(generatedaddress).digest()).digest()[:4]) #print(addresshash) #assert hashlib.sha256(hashlib.sha256(generatedaddress).digest()).digest()[:4] == addresshash return
def confirmcode(confirmationcode, passphrase): """ A confirmation tool, given a passphrase and a confirmation code, can recalculate the address, verify the address hash, and then assert the following: "It is confirmed that Bitcoin address address depends on this passphrase". If applicable: "The lot number is lotnumber and the sequence number is sequencenumber." To recalculate the address: """ #decode the confirmationcode to give addresshash, ownerentropy and encryptedpointb data = enc.encode(enc.decode(confirmationcode, 58), 256) assert hashlib.sha256(hashlib.sha256( data[:-4]).digest()).digest()[:4] == data[-4:] addresshash = data[6:10] ownerentropy = data[10:18] encryptedpointb = data[18:51] pointbprefix = encryptedpointb[:1] pointbx1 = encryptedpointb[1:17] pointbx2 = encryptedpointb[17:] #1. Derive passfactor using scrypt with ownerentropy and the user's passphrase and use it to recompute passpoint prefactor = scrypt.hash(passphrase, ownerentropy[:4], 16384, 8, 8, 32) passfactor = hashlib.sha256( hashlib.sha256(prefactor + ownerentropy).digest()).digest() pub = elip.base10_multiply(elip.G, enc.decode(passfactor, 256)) passpoint = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)).decode('hex') #2. Derive decryption key for pointb using scrypt with passpoint, addresshash, and ownerentropy key = scrypt.hash(passpoint, addresshash + ownerentropy, 1024, 1, 1, 64) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Decrypt encryptedpointb to yield pointb Aes = aes.Aes(derivedhalf2) pointb = pointbprefix + Aes.dec(pointbx1) + Aes.dec(pointbx2) print('pointb = ' + pointb.encode('hex')) #4. ECMultiply pointb by passfactor. Use the resulting EC point as a public key and hash it into address using either compressed or uncompressed public key # methodology as specifid in flagbyte. pub = elip.base10_multiply(enc.decode(passfactor, 256), enc.decode(pointb, 256)) privK = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)) generatedaddress = address.publicKey2Address(privK) #print(generatedaddress) #print(hashlib.sha256(hashlib.sha256(generatedaddress).digest()).digest()[:4]) #print(addresshash) #assert hashlib.sha256(hashlib.sha256(generatedaddress).digest()).digest()[:4] == addresshash return
def encrypt(pub_address, passphrase): """ Encrypt the public address """ #1 base 58 decode the public address address = enc.b58decode(pub_address) #2. Take a hash of the decoded address to act as a scrypt salt salt = hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] #2. Derive a key from the passphrase using scrypt key = scrypt.hash(passphrase, salt, 16384, 8, 8) #3. Split the key into half 1 and half 2 derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #4 AES encrypt address halves as per bip38 halflength = int(math.floor(len(address) / 2)) Aes = aes.Aes(derivedhalf2) #for Aes encryption, string needs to have length = 16 xorhalf1 = enc.sxor(address[:halflength], derivedhalf1[:16]).ljust(16, '0') xorhalf2 = enc.sxor(address[halflength:], derivedhalf1[16:32]).ljust(16, '0') encryptedhalf1 = Aes.enc(xorhalf1) encryptedhalf2 = Aes.enc(xorhalf2) #5. The encrypted private key is the Base58Check-encoded concatenation of the following # \x78\x8e\xa3\x69\xb6 + address_length + salt + encryptedhalf1 + encryptedhalf2 encAddress = '\x78\x8e\xa3\x69\xb6' + chr(len(address)) + salt + encryptedhalf1 + encryptedhalf2 check = hashlib.sha256(hashlib.sha256(encAddress).digest()).digest()[:4] return enc.b58encode(encAddress + check)
def decrypt(encrypted_privkey, passphrase, p): """ decrypt a bip0038 encrypted private key return the key and address hash for adress verification """ print('Decrypting private key...') #1. Collect encrypted private key and passphrase from user. # passed as parameters #2. Derive passfactor using scrypt with ownersalt and the user's passphrase and use it to recompute passpoint d = enc.b58decode(encrypted_privkey) d = d[2:] flagbyte = d[0:1] d = d[1:] addresshash = d[0:4] d = d[4:-4] #3. Derive decryption key for seedb using scrypt with passpoint, addresshash, and ownersalt key = scrypt.hash(passphrase,addresshash, 16384, 8, p) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] encryptedhalf1 = d[0:16] encryptedhalf2 = d[16:32] Aes = aes.Aes(derivedhalf2) #4. Decrypt encryptedpart2 using AES256Decrypt to yield the last 8 bytes of seedb and the last 8 bytes of encryptedpart1. decryptedhalf2 = Aes.dec(encryptedhalf2) #5. Decrypt encryptedpart1 to yield the remainder of seedb. decryptedhalf1 = Aes.dec(encryptedhalf1) priv = decryptedhalf1 + decryptedhalf2 priv = binascii.unhexlify('%064x' % (long(binascii.hexlify(priv), 16) ^ long(binascii.hexlify(derivedhalf1), 16))) return priv, addresshash
def encrypt(pub_address, passphrase): """ Encrypt the public address """ #1 base 58 decode the public address address = enc.b58decode(pub_address) #2. Take a hash of the decoded address to act as a scrypt salt salt = hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] #2. Derive a key from the passphrase using scrypt key = scrypt.hash(passphrase, salt, 16384, 8, 8) #3. Split the key into half 1 and half 2 derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #4 AES encrypt address halves as per bip38 halflength = int(math.floor(len(address) / 2)) Aes = aes.Aes(derivedhalf2) #for Aes encryption, string needs to have length = 16 xorhalf1 = enc.sxor(address[:halflength], derivedhalf1[:16]).ljust(16, '0') xorhalf2 = enc.sxor(address[halflength:], derivedhalf1[16:32]).ljust(16, '0') encryptedhalf1 = Aes.enc(xorhalf1) encryptedhalf2 = Aes.enc(xorhalf2) #5. The encrypted private key is the Base58Check-encoded concatenation of the following # \x78\x8e\xa3\x69\xb6 + address_length + salt + encryptedhalf1 + encryptedhalf2 encAddress = '\x78\x8e\xa3\x69\xb6' + chr( len(address)) + salt + encryptedhalf1 + encryptedhalf2 check = hashlib.sha256(hashlib.sha256(encAddress).digest()).digest()[:4] return enc.b58encode(encAddress + check)
def decrypt(encrypted_privkey, passphrase, p): #1. Collect encrypted private key and passphrase from user. # passed as parameters #2. Derive passfactor using scrypt with ownersalt and the user's passphrase and use it to recompute passpoint d = enc.b58decode(encrypted_privkey) d = d[2:] flagbyte = d[0:1] d = d[1:] addresshash = d[0:4] d = d[4:-4] #3. Derive decryption key for seedb using scrypt with passpoint, addresshash, and ownersalt key = scrypt.hash(passphrase, addresshash, 16384, 8, p) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] encryptedhalf1 = d[0:16] encryptedhalf2 = d[16:32] #4. Decrypt encryptedpart2 using AES256Decrypt to yield the last 8 bytes of seedb and the last 8 bytes of encryptedpart1. Aes = aes.Aes(derivedhalf2) decryptedhalf2 = Aes.dec(encryptedhalf2) #5. Decrypt encryptedpart1 to yield the remainder of seedb. decryptedhalf1 = Aes.dec(encryptedhalf1) priv = decryptedhalf1 + decryptedhalf2 priv = binascii.unhexlify('%064x' % (long(binascii.hexlify(priv), 16) ^ long(binascii.hexlify(derivedhalf1), 16))) return priv, addresshash
def encrypt(seed, passphrase): """ Encrypt the Electrum seed """ #1. Decode the seed value to the original number seed = mn_decode(seed.split()) #2. Take a hash of the decoded seed to act as a scrypt salt salt = hashlib.sha256(hashlib.sha256(seed).digest()).digest()[:4] #3. Derive a key from the passphrase using scrypt key = scrypt.hash(passphrase, salt, 16384, 8, 8) #4. Split the key into half 1 and half 2 derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #5. Do AES256Encrypt(seedhalf1 xor derivedhalf1[0...15], derivedhalf2), call the 16-byte result encryptedhalf1 # (Electrum may change the number of words in a seed so we should future proof by just using the halfs rather than hardcoded lengths) Aes = aes.Aes(derivedhalf2) encryptedhalf1 = Aes.enc(enc.sxor(seed[:int(math.floor(len(seed)/2))], derivedhalf1[:16])) #6. Do AES256Encrypt(seedhalf2 xor derivedhalf1[16...31], derivedhalf2), call the 16-byte result encryptedhalf2 encryptedhalf2 = Aes.enc(enc.sxor(seed[int(math.floor(len(seed)/2)):len(seed)], derivedhalf1[16:32])) #7. The encrypted private key is the Base58Check-encoded concatenation of the following # \x4E\xE3\x13\x35 + salt + encryptedhalf1 + encryptedhalf2 # (\x4E\xE3\x13\x35) gives the 'SeedE' prefix) encSeed = '\x4E\xE3\x13\x35' + salt + encryptedhalf1 + encryptedhalf2 check = hashlib.sha256(hashlib.sha256(encSeed).digest()).digest()[:4] return enc.b58encode(encSeed + check)
def intermediate2privK(intermediate_passphrase_string): """ Steps to create new encrypted private keys given intermediate_passphrase_string from owner (so we have ownerentropy, and passpoint, but we do not have passfactor or the passphrase): """ #get ownerentropy and passpoint from the intermediate key leadingzbytes = len(re.match('^1*',intermediate_passphrase_string).group(0)) data = '\x00' * leadingzbytes + enc.encode(enc.decode(intermediate_passphrase_string,58),256) assert hashlib.sha256(hashlib.sha256(data[:-4]).digest()).digest().encode('hex')[:4] == data[-4:] decodedstring = data[1:-4] ownerentropy = decodedstring[7:15] passpoint = decodedstring[-33:] #1. Set flagbyte. #Turn on bit 0x20 if the Bitcoin address will be formed by hashing the compressed public key (optional, saves space, but many Bitcoin implementations aren't compatible with it) #Turn on bit 0x04 if ownerentropy contains a value for lotsequence. #(While it has no effect on the keypair generation process, the decryption process needs this flag to know how to process ownerentropy) flagbyte = chr(0b00100100) # 00 EC 1 compressed 00 future 1 has lotsequence 00 future #2. Generate 24 random bytes, call this seedb. Take SHA256(SHA256(seedb)) to yield 32 bytes, call this factorb. seedb = os.urandom(24) factorb = hashlib.sha256(hashlib.sha256(seedb).digest()).digest() #3. ECMultiply passpoint by factorb. pub = elip.base10_multiply(enc.decode(factorb, 256), enc.decode(passpoint, 256)) #4. Use the resulting EC point as a public key and hash it into a Bitcoin address using either compressed or uncompressed public key methodology # (specify which methodology is used inside flagbyte). # This is the generated Bitcoin address, call it generatedaddress. publicKey = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)) generatedaddress = address.publicKey2Address(publicKey) ## Remember to add in the currency details here #5. Take the first four bytes of SHA256(SHA256(generatedaddress)) and call it addresshash. addresshash = hashlib.sha256(hashlib.sha256(generatedaddress).digest()).digest()[:4] #6. Now we will encrypt seedb. Derive a second key from passpoint using scrypt #Parameters: passphrase is passpoint provided from the first party (expressed in binary as 33 bytes). # salt is addresshash + ownerentropy, n=1024, r=1, p=1, length=64. The "+" operator is concatenation. encseedb = scrypt.hash(passpoint, addresshash + ownerentropy, 1024, 1, 1, 64) #7. Split the result into two 32-byte halves and call them derivedhalf1 and derivedhalf2. derivedhalf1 = encseedb[0:32] derivedhalf2 = encseedb[32:64] #8. Do AES256Encrypt(seedb[0...15] xor derivedhalf1[0...15], derivedhalf2), call the 16-byte result encryptedpart1 Aes = aes.Aes(derivedhalf2) encryptedpart1 = Aes.enc(enc.sxor(seedb[:16], derivedhalf1[:16])) #9. Do AES256Encrypt((encryptedpart1[8...15] + seedb[16...23]) xor derivedhalf1[16...31], derivedhalf2), call the 16-byte result encryptedpart2. # The "+" operator is concatenation. encryptedpart2 = Aes.enc(enc.sxor(encryptedpart1[8:16] + seedb[16:24], derivedhalf1[16:32])) #10. The encrypted private key is the Base58Check-encoded concatenation of the following, which totals 39 bytes without Base58 checksum: #0x01 0x43 + flagbyte + addresshash + ownerentropy + encryptedpart1[0...7] + encryptedpart2 inp_fmtd = '\x01\x43' + flagbyte + addresshash + ownerentropy + encryptedpart1[0:8] + encryptedpart2 check = hashlib.sha256(hashlib.sha256(inp_fmtd).digest()).digest()[:4] BIPKey = enc.b58encode(inp_fmtd + check) cnfrmcode = confirmationcode(flagbyte, addresshash, ownerentropy, factorb, derivedhalf1, derivedhalf2) return BIPKey, generatedaddress, cnfrmcode
def decrypt(encrypted_privkey, passphrase): # 1. Collect encrypted private key and passphrase from user. # passed as parameters data = enc.b58decode(encrypted_privkey) flagbyte = data[2:3] check = data[-4:] if check != hashlib.sha256(hashlib.sha256(data[:-4]).digest()).digest()[:4]: return False, 'checksum' addresshash = data[3:7] encryptedhalf1 = data[7:23] encryptedhalf2 = data[23:39] #3. Derive decryption key for seedb using scrypt with passpoint, addresshash, and ownersalt key = scrypt.hash(passphrase, addresshash, 16384, 8, 8) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #4. Decrypt encryptedpart2 using AES256Decrypt to yield the last 8 bytes of seedb and the last 8 bytes of encryptedpart1. Aes = aes.Aes(derivedhalf2) decryptedhalf2 = Aes.dec(encryptedhalf2) #5. Decrypt encryptedpart1 to yield the remainder of seedb. decryptedhalf1 = Aes.dec(encryptedhalf1) priv = decryptedhalf1 + decryptedhalf2 priv = binascii.unhexlify('%064x' % (long(binascii.hexlify(priv), 16) ^ long(binascii.hexlify(derivedhalf1), 16))) return priv, addresshash
def encrypt(privK, Baddress, Saddress, passphrase): """ BIP0038 private key encryption, Non-EC """ # 1. take the first four bytes of SHA256(SHA256(address)) of it. Let's call this "addresshash". addresshash = hashlib.sha256(hashlib.sha256(Baddress + Saddress).digest()).digest()[:4] #2. Derive a key from the passphrase using scrypt # a. Parameters: passphrase is the passphrase itself encoded in UTF-8. # addresshash came from the earlier step, n=16384, r=8, p=8, length=64 # (n, r, p are provisional and subject to consensus) key = scrypt.hash(passphrase, addresshash, 16384, 8, 8) #Let's split the resulting 64 bytes in half, and call them derivedhalf1 and derivedhalf2. derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Do AES256Encrypt(bitcoinprivkey[0...15] xor derivedhalf1[0...15], derivedhalf2), call the 16-byte result encryptedhalf1 Aes = aes.Aes(derivedhalf2) encryptedhalf1 = Aes.enc(enc.sxor(privK[:16], derivedhalf1[:16])) #4. Do AES256Encrypt(bitcoinprivkey[16...31] xor derivedhalf1[16...31], derivedhalf2), call the 16-byte result encryptedhalf2 encryptedhalf2 = Aes.enc(enc.sxor(privK[16:32], derivedhalf1[16:32])) #5. The encrypted private key is the Base58Check-encoded concatenation of the following, which totals 39 bytes without Base58 checksum: # 0x01 0x42 + flagbyte + salt + encryptedhalf1 + encryptedhalf2 flagbyte = chr(0b11100000) # 11 no-ec 1 compressed-pub 00 future 0 ec only 00 future privkey = ('\x01\x42' + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2) check = hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4] return enc.b58encode(privkey + check)
def intermediate(passphrase): """ Encrypting a private key with EC multiplication offers the ability for someone to generate encrypted keys knowing only an EC point derived from the original passphrase and some salt generated by the passphrase's owner, and without knowing the passphrase itself. Only the person who knows the original passphrase can decrypt the private key. A code known as an intermediate code conveys the information needed to generate such a key without knowledge of the passphrase. This methodology does not offer the ability to encrypt a known private key - this means that the process of creating encrypted keys is also the process of generating new addresses. On the other hand, this serves a security benefit for someone possessing an address generated this way: if the address can be recreated by decrypting its private key with a passphrase, and it's a strong passphrase one can be certain only he knows himself, then he can safely conclude that nobody could know the private key to that address. The person who knows the passphrase and who is the intended beneficiary of the private keys is called the owner. He will generate one or more "intermediate codes", which are the first factor of a two-factor redemption system, and will give them to someone else we'll call printer, who generates a key pair with an intermediate code can know the address and encrypted private key, but cannot decrypt the private key without the original passphrase. An intermediate code should, but is not required to, embed a printable "lot" and "sequence" number for the benefit of the user. The proposal forces these lot and sequence numbers to be included in any valid private keys generated from them. An owner who has requested multiple private keys to be generated for him will be advised by applications to ensure that each private key has a unique lot and sequence number consistent with the intermediate codes he generated. These mainly help protect owner from potential mistakes and/or attacks that could be made by printer. The "lot" and "sequence" number are combined into a single 32 bit number. 20 bits are used for the lot number and 12 bits are used for the sequence number, such that the lot number can be any decimal number between 0 and 1048575, and the sequence number can be any decimal number between 0 and 4095. For programs that generate batches of intermediate codes for an owner, it is recommended that lot numbers be chosen at random within the range 100000-999999 and that sequence numbers are assigned starting with 1. We are not using Lot Sequence and sequence so some changes are made to the instructions """ #1. Generate 8 random bytes, call them ownerentropy. ownerentropy = os.urandom(8) #4. Derive a key from the passphrase using scrypt #Parameters: passphrase is the passphrase itself encoded in UTF-8. salt is ownersalt. n=16384, r=8, p=8, length=32. #Call the resulting 32 bytes passfactor. passfactor = scrypt.hash(passphrase, ownerentropy, 16384, 8, 8, 32) #6. Compute the elliptic curve point G * passfactor, and convert the result to compressed notation (33 bytes). Call this passpoint. #Compressed notation is used for this purpose regardless of whether the intent is to create Bitcoin addresses with or without compressed public keys. pub = elip.base10_multiply(elip.G, enc.decode(passfactor, 256)) passpoint = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)).decode('hex') #7. Convey ownerentropy and passpoint to the party generating the keys, along with a checksum to ensure integrity. #The following Base58Check-encoded format is recommended for this purpose: magic bytes "2C E9 B3 E1 FF 39 E2 53" followed by ownerentropy, and then passpoint. #The resulting string will start with the word "passphrase" due to the constant bytes, #will be 72 characters in length, and encodes 49 bytes (8 bytes constant + 8 bytes ownerentropy + 33 bytes passpoint). #The checksum is handled in the Base58Check encoding. The resulting string is called intermediate_passphrase_string. input = '\x2C\xE9\xB3\xE1\xFF\x39\xE2\x53' + ownerentropy + passpoint checksum = hashlib.sha256(hashlib.sha256(input).digest()).digest()[:4] intermediate_passphrase_string = enc.b58encode(input + checksum) return intermediate_passphrase_string
def decrypt(encSeed, passphrase): """ Decrypt an Electrum seed encrypted with the above method """ #1. Base 58 decrypt the encrypted key # get the two encrypted halves, the check and the salt decSeed = enc.b58decode(encSeed) check = decSeed[-4:] #check that it's not been tampered with if check != hashlib.sha256(hashlib.sha256( decSeed[:-4]).digest()).digest()[:4]: return False, 'checksum' salt = decSeed[4:8] encryptedhalfs = decSeed[8:len(decSeed) - 4] encryptedhalf1 = encryptedhalfs[0:int(math.floor(len(encryptedhalfs) / 2))] encryptedhalf2 = encryptedhalfs[int(math.floor(len(encryptedhalfs) / 2)):] #2. Derive the decryption key using scrypt key = scrypt.hash(passphrase, salt, 16384, 8, 8) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Decrypt the encrypted halves Aes = aes.Aes(derivedhalf2) decryptedhalf1 = Aes.dec(encryptedhalf1) decryptedhalf2 = Aes.dec(encryptedhalf2) #4 . xor them with the two halves of derivedhalf1 to get the original values half1 = enc.sxor(decryptedhalf1, derivedhalf1[:16]) half2 = enc.sxor(decryptedhalf2, derivedhalf1[16:32]) #5. build the seed and check it against the check hash seed = half1 + half2 if salt != hashlib.sha256(hashlib.sha256(seed).digest()).digest()[:4]: return False, 'salt' #6. encode the seed as an Electrum Mnemonic list mn = mn_encode(str(seed)) #6 . return the mnemonic as a single string seed = '' for word in mn: seed += word + ' ' return True, seed
def decrypt(encSeed, passphrase): """ Decrypt an Electrum seed encrypted with the above method """ #1. Base 58 decrypt the encrypted key # get the two encrypted halves, the check and the salt decSeed = enc.b58decode(encSeed) check = decSeed[-4:] #check that it's not been tampered with if check != hashlib.sha256(hashlib.sha256(decSeed[:-4]).digest()).digest()[:4]: return False, 'checksum' salt = decSeed[4:8] encryptedhalfs = decSeed[8:len(decSeed)-4] encryptedhalf1 = encryptedhalfs[0:int(math.floor(len(encryptedhalfs) / 2))] encryptedhalf2 = encryptedhalfs[int(math.floor(len(encryptedhalfs) / 2)):] #2. Derive the decryption key using scrypt key = scrypt.hash(passphrase, salt, 16384, 8, 8) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Decrypt the encrypted halves Aes = aes.Aes(derivedhalf2) decryptedhalf1 = Aes.dec(encryptedhalf1) decryptedhalf2 = Aes.dec(encryptedhalf2) #4 . xor them with the two halves of derivedhalf1 to get the original values half1 = enc.sxor(decryptedhalf1, derivedhalf1[:16]) half2 = enc.sxor(decryptedhalf2, derivedhalf1[16:32]) #5. build the seed and check it against the check hash seed = half1 + half2 if salt != hashlib.sha256(hashlib.sha256(seed).digest()).digest()[:4]: return False, 'salt' #6. encode the seed as an Electrum Mnemonic list mn = mn_encode(str(seed)) #6 . return the mnemonic as a single string seed = '' for word in mn: seed += word + ' ' return True, seed
def decrypt(enc_address, passphrase): """ Decrypt an Public Address encrypted with the above method """ #1. Base 58 decrypt the encrypted key # get the two encrypted halves, the check and the salt dec_address = enc.b58decode(enc_address) check = dec_address[-4:] #check that it's not been tampered with if check != hashlib.sha256(hashlib.sha256( dec_address[:-4]).digest()).digest()[:4]: return False, 'checksum' length = ord(dec_address[5:6]) halflength = int(math.floor(length / 2)) salt = dec_address[6:10] encryptedhalfs = dec_address[10:len(dec_address) - 4] encryptedhalf1 = encryptedhalfs[:16] encryptedhalf2 = encryptedhalfs[16:] #2. Derive the decryption key using scrypt key = scrypt.hash(passphrase, salt, 16384, 8, 8) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Aes decrypt the encrypted halves Aes = aes.Aes(derivedhalf2) xorhalf1 = Aes.dec(encryptedhalf1) xorhalf2 = Aes.dec(encryptedhalf2) #4 . xor them with the two halves of derivedhalf1 to get the original values half1 = enc.sxor(xorhalf1[:halflength], derivedhalf1[:16]) half2 = enc.sxor(xorhalf2[:(length - halflength)], derivedhalf1[16:32]) #5. build the address and check it against the check hash pub_address = half1 + half2 if salt != hashlib.sha256( hashlib.sha256(pub_address).digest()).digest()[:4]: return False, 'salt' #6. return the address as a single string return True, enc.b58encode(pub_address)
def decrypt(enc_address, passphrase): """ Decrypt an Public Address encrypted with the above method """ #1. Base 58 decrypt the encrypted key # get the two encrypted halves, the check and the salt dec_address = enc.b58decode(enc_address) check = dec_address[-4:] #check that it's not been tampered with if check != hashlib.sha256(hashlib.sha256(dec_address[:-4]).digest()).digest()[:4]: return False, 'checksum' length = ord(dec_address[5:6]) halflength = int(math.floor(length / 2)) salt = dec_address[6:10] encryptedhalfs = dec_address[10:len(dec_address) - 4] encryptedhalf1 = encryptedhalfs[:16] encryptedhalf2 = encryptedhalfs[16:] #2. Derive the decryption key using scrypt key = scrypt.hash(passphrase, salt, 16384, 8, 8) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Aes decrypt the encrypted halves Aes = aes.Aes(derivedhalf2) xorhalf1 = Aes.dec(encryptedhalf1) xorhalf2 = Aes.dec(encryptedhalf2) #4 . xor them with the two halves of derivedhalf1 to get the original values half1 = enc.sxor(xorhalf1[:halflength], derivedhalf1[:16]) half2 = enc.sxor(xorhalf2[:(length - halflength)], derivedhalf1[16:32]) #5. build the address and check it against the check hash pub_address = half1 + half2 if salt != hashlib.sha256(hashlib.sha256(pub_address).digest()).digest()[:4]: return False, 'salt' #6. return the address as a single string return True, enc.b58encode(pub_address)
def intermediate2privK(intermediate_passphrase_string): """ Steps to create new encrypted private keys given intermediate_passphrase_string from owner (so we have ownerentropy, and passpoint, but we do not have passfactor or the passphrase): """ #get ownerentropy and passpoint from the intermediate key #check the checksum en route decstring = enc.b58decode(intermediate_passphrase_string) checksum = decstring[-4:] if checksum != hashlib.sha256(hashlib.sha256( decstring[:-4]).digest()).digest()[:4]: return False, 'checksum' decodedstring = decstring[:-4] ownerentropy = decodedstring[8:16] passpoint = decodedstring[-33:] print(passpoint) #1. Set flagbyte. #Turn on bit 0x20 if the Bitcoin address will be formed by hashing the compressed public key (optional, saves space, but many Bitcoin implementations aren't compatible with it) #Turn on bit 0x04 if ownerentropy contains a value for lotsequence. #(While it has no effect on the keypair generation process, the decryption process needs this flag to know how to process ownerentropy) flagbyte = chr( 0b00100000 ) # 00 EC 1 compressed 00 future 0 has no lotsequence 00 future #2. Generate 24 random bytes, call this seedb. Take SHA256(SHA256(seedb)) to yield 32 bytes, call this factorb. seedb = os.urandom(24) seedb = b'ABCDEFGHIJKLMNOPQRSTUVWX' #seedb = bytearray(b'ABCDEFGHIJKLMNOPQRSTUVWX') #for c in seedb: print(c) factorb = hashlib.sha256(hashlib.sha256(seedb).digest()).digest() #3. ECMultiply passpoint by factorb. pub = elip.base10_multiply(enc.decode(passpoint, 256), enc.decode(factorb, 256)) #4. Use the resulting EC point as a public key and hash it into a Bitcoin address using either compressed or uncompressed public key methodology # (specify which methodology is used inside flagbyte). # This is the generated Bitcoin address, call it generatedaddress. publicKey = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)) generatedaddress = address.publicKey2Address( publicKey) ## TODO Remember to add in the currency details here #5. Take the first four bytes of SHA256(SHA256(generatedaddress)) and call it addresshash. addresshash = hashlib.sha256( hashlib.sha256(generatedaddress).digest()).digest()[:4] #6. Now we will encrypt seedb. Derive a second key from passpoint using scrypt #Parameters: passphrase is passpoint provided from the first party (expressed in binary as 33 bytes). # salt is addresshash + ownerentropy, n=1024, r=1, p=1, length=64. The "+" operator is concatenation. encseedb = scrypt.hash(passpoint, addresshash + ownerentropy, 1024, 1, 1, 64) #7. Split the result into two 32-byte halves and call them derivedhalf1 and derivedhalf2. derivedhalf1 = encseedb[0:32] derivedhalf2 = encseedb[32:64] #8. Do AES256Encrypt(seedb[0...15] xor derivedhalf1[0...15], derivedhalf2), call the 16-byte result encryptedpart1 Aes = aes.Aes(derivedhalf2) encryptedpart1 = Aes.enc(enc.sxor(seedb[:16], derivedhalf1[:16])) #9. Do AES256Encrypt((encryptedpart1[8...15] + seedb[16...23]) xor derivedhalf1[16...31], derivedhalf2), call the 16-byte result encryptedpart2. # The "+" operator is concatenation. encryptedpart2 = Aes.enc( enc.sxor(encryptedpart1[8:16] + seedb[16:24], derivedhalf1[16:32])) #10. The encrypted private key is the Base58Check-encoded concatenation of the following, which totals 39 bytes without Base58 checksum: #0x01 0x43 + flagbyte + addresshash + ownerentropy + encryptedpart1[0...7] + encryptedpart2 input = '\x01\x43' + flagbyte + addresshash + ownerentropy + encryptedpart1[ 0:8] + encryptedpart2 checksum = hashlib.sha256(hashlib.sha256(input).digest()).digest()[:4] BIPKey = enc.b58encode(input + checksum) cnfrmcode = confirmationcode(flagbyte, addresshash, ownerentropy, factorb, derivedhalf1, derivedhalf2) return BIPKey, generatedaddress, cnfrmcode
def confirmcode(confirmationcode, passphrase): """ A confirmation tool, given a passphrase and a confirmation code, can recalculate the address, verify the address hash, and then assert the following: "It is confirmed that Bitcoin address address depends on this passphrase". If applicable: "The lot number is lotnumber and the sequence number is sequencenumber." To recalculate the address: """ #decode the confirmationcode to give addresshash, ownerentropy and encryptedpointb data = enc.encode(enc.decode(confirmationcode,58),256) assert hashlib.sha256(hashlib.sha256(data[:-4]).digest()).digest()[:4] == data[-4:] addresshash = data[6:10] ownerentropy = data[10:18] encryptedpointb = data[18:51] pointbprefix = encryptedpointb[:1] pointbx1 = encryptedpointb[1:17] pointbx2 = encryptedpointb[17:] #1. Derive passfactor using scrypt with ownerentropy and the user's passphrase and use it to recompute passpoint prefactor = scrypt.hash(passphrase, ownerentropy[:4], 16384, 8, 8, 32) passfactor = hashlib.sha256(hashlib.sha256(prefactor + ownerentropy).digest()).digest() pub = elip.base10_multiply(elip.G, enc.decode(passfactor, 256)) passpoint = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)).decode('hex') #2. Derive decryption key for pointb using scrypt with passpoint, addresshash, and ownerentropy key = scrypt.hash(passpoint, addresshash + ownerentropy, 1024, 1, 1, 64) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] #3. Decrypt encryptedpointb to yield pointb Aes = aes.Aes(derivedhalf2) pointb = pointbprefix + Aes.dec(pointbx1) + Aes.dec(pointbx2) print('pointb = ' + pointb.encode('hex')) #4. ECMultiply pointb by passfactor. Use the resulting EC point as a public key and hash it into address using either compressed or uncompressed public key # methodology as specifid in flagbyte. pub = elip.base10_multiply(enc.decode(passfactor, 256), enc.decode(pointb, 256)) privK = ('0' + str(2 + (pub[1] % 2)) + enc.encode(pub[0], 16, 64)) generatedaddress = address.publicKey2Address(privK) #print(generatedaddress) #print(hashlib.sha256(hashlib.sha256(generatedaddress).digest()).digest()[:4]) #print(addresshash) #assert hashlib.sha256(hashlib.sha256(generatedaddress).digest()).digest()[:4] == addresshash return #Decryption # #Collect encrypted private key and passphrase from user. #Derive passfactor using scrypt with ownerentropy and the user's passphrase and use it to recompute passpoint #Derive decryption key for seedb using scrypt with passpoint, addresshash, and ownersalt #Decrypt encryptedpart2 using AES256Decrypt to yield the last 8 bytes of seedb and the last 8 bytes of encryptedpart1. #Decrypt encryptedpart1 to yield the remainder of seedb. #Use seedb to compute factorb. #Multiply passfactor by factorb mod N to yield the private key associated with generatedaddress. #Convert that private key into a Bitcoin address, honoring the compression preference specified in the encrypted key. #Hash the Bitcoin address, and verify that addresshash from the encrypted private key record matches the hash. If not, report that the passphrase entry was incorrect. #