// Copyright (c) 2017-2018 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "bip38.h" #include "base58.h" #include "hash.h" #include "pubkey.h" #include "util.h" #include "utilstrencodings.h" #include "random.h" #include #include #include #include /** 39 bytes - 78 characters * 1) Prefix - 2 bytes - 4 chars - strKey[0..3] * 2) Flagbyte - 1 byte - 2 chars - strKey[4..5] * 3) addresshash - 4 bytes - 8 chars - strKey[6..13] * 4) Owner Entropy - 8 bytes - 16 chars - strKey[14..29] * 5) Encrypted Part 1 - 8 bytes - 16 chars - strKey[30..45] * 6) Encrypted Part 2 - 16 bytes - 32 chars - strKey[46..77] */ static bool AES256ECBTransform(const unsigned char* input, const unsigned char* key, unsigned char* output, bool encrypt) { EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) return false; int outLen = 0; int finalLen = 0; bool ok = EVP_CipherInit_ex(ctx, EVP_aes_256_ecb(), NULL, key, NULL, encrypt ? 1 : 0) == 1 && EVP_CIPHER_CTX_set_padding(ctx, 0) == 1 && EVP_CipherUpdate(ctx, output, &outLen, input, 16) == 1 && EVP_CipherFinal_ex(ctx, output + outLen, &finalLen) == 1 && outLen + finalLen == 16; EVP_CIPHER_CTX_free(ctx); return ok; } static void EncryptAES(uint256 blockIn, uint256 encryptionKey, unsigned char* output) { if (!AES256ECBTransform(blockIn.begin(), encryptionKey.begin(), output, true)) throw std::runtime_error("AES-256-ECB encrypt failed"); } void DecryptAES(uint256 encryptedIn, uint256 decryptionKey, uint256& output) { if (!AES256ECBTransform(encryptedIn.begin(), decryptionKey.begin(), output.begin(), false)) throw std::runtime_error("AES-256-ECB decrypt failed"); } void ComputePreFactor(std::string strPassphrase, std::string strSalt, uint256& prefactor) { //passfactor is the scrypt hash of passphrase and ownersalt (NOTE this needs to handle alt cases too in the future) uint64_t s = uint256(ReverseEndianString(strSalt)).Get64(); scrypt_hash(strPassphrase.c_str(), strPassphrase.size(), BEGIN(s), strSalt.size() / 2, BEGIN(prefactor), 16384, 8, 8, 32); } void ComputePassfactor(std::string ownersalt, uint256 prefactor, uint256& passfactor) { //concat prefactor and ownersalt uint512 temp(ReverseEndianString(HexStr(prefactor) + ownersalt)); Hash(temp.begin(), 40, passfactor.begin()); //40 bytes is the length of prefactor + salt Hash(passfactor.begin(), 32, passfactor.begin()); } bool ComputePasspoint(uint256 passfactor, CPubKey& passpoint) { size_t clen = 65; secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); assert(ctx != nullptr); { // Pass in a random blinding seed to the secp256k1 context. std::vector> vseed(32); GetRandBytes(vseed.data(), 32); bool ret = secp256k1_context_randomize(ctx, vseed.data()); assert(ret); } secp256k1_pubkey pubkey; //passpoint is the ec_mult of passfactor on secp256k1 if (!secp256k1_ec_pubkey_create(ctx, &pubkey, passfactor.begin())) { secp256k1_context_destroy(ctx); return false; } secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)passpoint.begin(), &clen, &pubkey, SECP256K1_EC_COMPRESSED); secp256k1_context_destroy(ctx); if (passpoint.size() != clen) return false; if (!passpoint.IsValid()) return false; return true; } void ComputeSeedBPass(CPubKey passpoint, std::string strAddressHash, std::string strOwnerSalt, uint512& seedBPass) { // Derive decryption key for seedb using scrypt with passpoint, addresshash, and ownerentropy string salt = ReverseEndianString(strAddressHash + strOwnerSalt); uint256 s2(salt); scrypt_hash(BEGIN(passpoint), HexStr(passpoint).size() / 2, BEGIN(s2), salt.size() / 2, BEGIN(seedBPass), 1024, 1, 1, 64); } void ComputeFactorB(uint256 seedB, uint256& factorB) { //factorB - a double sha256 hash of seedb Hash(seedB.begin(), 24, factorB.begin()); //seedB is only 24 bytes Hash(factorB.begin(), 32, factorB.begin()); } std::string AddressToBip38Hash(std::string address) { uint256 addrCheck; Hash((void*)address.c_str(), address.size(), addrCheck.begin()); Hash(addrCheck.begin(), 32, addrCheck.begin()); return HexStr(addrCheck).substr(0, 8); } std::string BIP38_Encrypt(std::string strAddress, std::string strPassphrase, uint256 privKey, bool fCompressed) { string strAddressHash = AddressToBip38Hash(strAddress); uint512 hashed; uint64_t salt = uint256(ReverseEndianString(strAddressHash)).Get64(); scrypt_hash(strPassphrase.c_str(), strPassphrase.size(), BEGIN(salt), strAddressHash.size() / 2, BEGIN(hashed), 16384, 8, 8, 64); uint256 derivedHalf1(hashed.ToString().substr(64, 64)); uint256 derivedHalf2(hashed.ToString().substr(0, 64)); //block1 = (pointb[1...16] xor derivedhalf1[0...15]) uint256 block1 = uint256((privKey << 128) ^ (derivedHalf1 << 128)) >> 128; //encrypt part 1 uint512 encrypted1; EncryptAES(block1, derivedHalf2, encrypted1.begin()); //block2 = (pointb[17...32] xor derivedhalf1[16...31] uint256 p2 = privKey >> 128; uint256 dh12 = derivedHalf1 >> 128; uint256 block2 = uint256(p2 ^ dh12); //encrypt part 2 uint512 encrypted2; EncryptAES(block2, derivedHalf2, encrypted2.begin()); string strPrefix = "0142"; strPrefix += (fCompressed ? "E0" : "C0"); uint512 encryptedKey(ReverseEndianString(strPrefix + strAddressHash)); //add encrypted1 to the end of encryptedKey encryptedKey = encryptedKey | (encrypted1 << 56); //add encrypted2 to the end of encryptedKey encryptedKey = encryptedKey | (encrypted2 << (56 + 128)); //Base58 checksum is the 4 bytes of dSHA256 hash of the encrypted key uint256 hashChecksum = Hash(encryptedKey.begin(), encryptedKey.begin() + 39); uint512 b58Checksum(hashChecksum.ToString().substr(64 - 8, 8)); // append the encrypted key with checksum (currently occupies 312 bits) encryptedKey = encryptedKey | (b58Checksum << 312); //43 bytes is the total size that we are encoding return EncodeBase58(encryptedKey.begin(), encryptedKey.begin() + 43); } bool BIP38_Decrypt(std::string strPassphrase, std::string strEncryptedKey, uint256& privKey, bool& fCompressed) { std::string strKey = DecodeBase58(strEncryptedKey.c_str()); //incorrect encoding of key, it must be 39 bytes - and another 4 bytes for base58 checksum if (strKey.size() != (78 + 8)) return false; //invalid prefix if (uint256(ReverseEndianString(strKey.substr(0, 2))) != uint256(0x01)) return false; uint256 type(ReverseEndianString(strKey.substr(2, 2))); uint256 flag(ReverseEndianString(strKey.substr(4, 2))); std::string strAddressHash = strKey.substr(6, 8); std::string ownersalt = strKey.substr(14, 16); uint256 encryptedPart1(ReverseEndianString(strKey.substr(30, 16))); uint256 encryptedPart2(ReverseEndianString(strKey.substr(46, 32))); fCompressed = (flag & uint256(0x20)) != 0; //not ec multiplied if (type == uint256(0x42)) { uint512 hashed; encryptedPart1 = uint256(ReverseEndianString(strKey.substr(14, 32))); uint64_t salt = uint256(ReverseEndianString(strAddressHash)).Get64(); scrypt_hash(strPassphrase.c_str(), strPassphrase.size(), BEGIN(salt), strAddressHash.size() / 2, BEGIN(hashed), 16384, 8, 8, 64); uint256 derivedHalf1(hashed.ToString().substr(64, 64)); uint256 derivedHalf2(hashed.ToString().substr(0, 64)); uint256 decryptedPart1; DecryptAES(encryptedPart1, derivedHalf2, decryptedPart1); uint256 decryptedPart2; DecryptAES(encryptedPart2, derivedHalf2, decryptedPart2); //combine decrypted parts into 64 bytes uint256 temp1 = decryptedPart2 << 128; temp1 = temp1 | decryptedPart1; //xor the decryption with the derived half 1 for the final key privKey = temp1 ^ derivedHalf1; return true; } else if (type != uint256(0x43)) //invalid type return false; bool fLotSequence = (flag & 0x04) != 0; std::string prefactorSalt = ownersalt; if (fLotSequence) prefactorSalt = ownersalt.substr(0, 8); uint256 prefactor; ComputePreFactor(strPassphrase, prefactorSalt, prefactor); uint256 passfactor; if (fLotSequence) ComputePassfactor(ownersalt, prefactor, passfactor); else passfactor = prefactor; CPubKey passpoint; if (!ComputePasspoint(passfactor, passpoint)) return false; uint512 seedBPass; ComputeSeedBPass(passpoint, strAddressHash, ownersalt, seedBPass); //get derived halfs, being mindful for endian switch uint256 derivedHalf1(seedBPass.ToString().substr(64, 64)); uint256 derivedHalf2(seedBPass.ToString().substr(0, 64)); /** Decrypt encryptedpart2 using AES256Decrypt to yield the last 8 bytes of seedb and the last 8 bytes of encryptedpart1. **/ uint256 decryptedPart2; DecryptAES(encryptedPart2, derivedHalf2, decryptedPart2); //xor decryptedPart2 and 2nd half of derived half 1 uint256 x0 = derivedHalf1 >> 128; //drop off the first half (note: endian) uint256 x1 = decryptedPart2 ^ x0; uint256 seedbPart2 = x1 >> 64; /** Decrypt encryptedpart1 to yield the remainder of seedb. **/ uint256 decryptedPart1; uint256 x2 = x1 & uint256("0xffffffffffffffff"); // set x2 to seedbPart1 (still encrypted) x2 = x2 << 64; //make room to add encryptedPart1 to the front x2 = encryptedPart1 | x2; //combine with encryptedPart1 DecryptAES(x2, derivedHalf2, decryptedPart1); //decrypted part 1: seedb[0..15] xor derivedhalf1[0..15] uint256 x3 = derivedHalf1 & uint256("0xffffffffffffffffffffffffffffffff"); uint256 seedbPart1 = decryptedPart1 ^ x3; uint256 seedB = seedbPart1 | (seedbPart2 << 128); uint256 factorB; ComputeFactorB(seedB, factorB); //multiply passfactor by factorb mod N to yield the priv key secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); assert(ctx != nullptr); { // Pass in a random blinding seed to the secp256k1 context. std::vector> vseed(32); GetRandBytes(vseed.data(), 32); bool ret = secp256k1_context_randomize(ctx, vseed.data()); assert(ret); } privKey = factorB; if (!secp256k1_ec_privkey_tweak_mul(ctx, privKey.begin(), passfactor.begin())) { secp256k1_context_destroy(ctx); return false; } secp256k1_context_destroy(ctx); //double check that the address hash matches our final privkey CKey k; k.Set(privKey.begin(), privKey.end(), fCompressed); CPubKey pubkey = k.GetPubKey(); string address = CBitcoinAddress(pubkey.GetID()).ToString(); return strAddressHash == AddressToBip38Hash(address); }