LCOV - code coverage report
Current view: top level - src - bip38.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 69 150 46.0 %
Date: 2025-02-23 09:33:43 Functions: 5 10 50.0 %

          Line data    Source code
       1             : // Copyright (c) 2017-2021 The PIVX Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include "bip38.h"
       6             : 
       7             : #include "base58.h"
       8             : #include "crypto/aes.h"
       9             : #include "key_io.h"
      10             : #include "hash.h"
      11             : #include "utilstrencodings.h"
      12             : #include "random.h"
      13             : 
      14             : #include <secp256k1.h>
      15             : #include <string>
      16             : 
      17           1 : static bool Base58ToHex(const std::string& base58_str, std::string& hex_str)
      18             : {
      19             :     // Base58 decoding
      20             :     // it must be 39 bytes - and another 4 bytes for base58 checksum
      21           1 :     size_t key_size = 39 + 4;
      22           2 :     std::vector<unsigned char> vchKey;
      23           1 :     if (!DecodeBase58(base58_str.c_str(), vchKey, key_size) || vchKey.size() != key_size) {
      24             :         return false;
      25             :     }
      26             :     // Hex encoding
      27           1 :     std::stringstream ss;
      28           1 :     ss << std::hex;
      29          44 :     for (unsigned int i = 0; i < vchKey.size(); i++) {
      30          43 :         const unsigned char* c = vchKey.data() + i;
      31          43 :         ss << std::setw(2) << std::setfill('0') << (int)*c;
      32             :     }
      33           1 :     hex_str = ss.str();
      34           1 :     return true;
      35             : }
      36             : 
      37             : 
      38             : /** 39 bytes - 78 characters
      39             :  * 1) Prefix - 2 bytes - 4 chars - strKey[0..3]
      40             :  * 2) Flagbyte - 1 byte - 2 chars - strKey[4..5]
      41             :  * 3) addresshash - 4 bytes - 8 chars - strKey[6..13]
      42             :  * 4) Owner Entropy - 8 bytes - 16 chars - strKey[14..29]
      43             :  * 5) Encrypted Part 1 - 8 bytes - 16 chars - strKey[30..45]
      44             :  * 6) Encrypted Part 2 - 16 bytes - 32 chars - strKey[46..77]
      45             :  */
      46             : 
      47           2 : void DecryptAES(uint256 encryptedIn, uint256 decryptionKey, uint256& output)
      48             : {
      49           2 :     AES256Decrypt(decryptionKey.begin()).Decrypt(output.begin(), encryptedIn.begin());
      50           2 : }
      51             : 
      52           0 : void ComputePreFactor(std::string strPassphrase, std::string strSalt, uint256& prefactor)
      53             : {
      54             :     //passfactor is the scrypt hash of passphrase and ownersalt (NOTE this needs to handle alt cases too in the future)
      55           0 :     uint64_t s = uint256S(ReverseEndianString(strSalt)).GetCheapHash();
      56           0 :     scrypt_hash(strPassphrase.c_str(), strPassphrase.size(), BEGIN(s), strSalt.size() / 2, BEGIN(prefactor), 16384, 8, 8, 32);
      57           0 : }
      58             : 
      59           0 : void ComputePassfactor(std::string ownersalt, uint256 prefactor, uint256& passfactor)
      60             : {
      61             :     //concat prefactor and ownersalt
      62           0 :     uint512 temp = uint512S(ReverseEndianString(HexStr(prefactor) + ownersalt));
      63           0 :     Hash(temp.begin(), temp.end(), passfactor.begin(), passfactor.end());
      64           0 : }
      65             : 
      66           0 : bool ComputePasspoint(uint256 passfactor, CPubKey& passpoint)
      67             : {
      68           0 :     size_t clen = 65;
      69           0 :     secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
      70           0 :     assert(ctx != nullptr);
      71           0 :     {
      72             :         // Pass in a random blinding seed to the secp256k1 context.
      73           0 :         std::vector<unsigned char, secure_allocator<unsigned char>> vseed(32);
      74           0 :         GetRandBytes(vseed.data(), 32);
      75           0 :         bool ret = secp256k1_context_randomize(ctx, vseed.data());
      76           0 :         assert(ret);
      77             :     }
      78           0 :     secp256k1_pubkey pubkey;
      79             : 
      80             :     //passpoint is the ec_mult of passfactor on secp256k1
      81           0 :     if (!secp256k1_ec_pubkey_create(ctx, &pubkey, passfactor.begin())) {
      82           0 :         secp256k1_context_destroy(ctx);
      83           0 :         return false;
      84             :     }
      85             : 
      86           0 :     secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)passpoint.begin(), &clen, &pubkey, SECP256K1_EC_COMPRESSED);
      87           0 :     secp256k1_context_destroy(ctx);
      88             : 
      89           0 :     if (passpoint.size() != clen)
      90             :         return false;
      91             : 
      92           0 :     if (!passpoint.IsValid())
      93           0 :         return false;
      94             : 
      95             :     return true;
      96             : }
      97             : 
      98           0 : void ComputeSeedBPass(CPubKey passpoint, std::string strAddressHash, std::string strOwnerSalt, uint512& seedBPass)
      99             : {
     100             :     // Derive decryption key for seedb using scrypt with passpoint, addresshash, and ownerentropy
     101           0 :     std::string salt = ReverseEndianString(strAddressHash + strOwnerSalt);
     102           0 :     uint256 s2(uint256S(salt));
     103           0 :     scrypt_hash(BEGIN(passpoint), HexStr(passpoint).size() / 2, BEGIN(s2), salt.size() / 2, BEGIN(seedBPass), 1024, 1, 1, 64);
     104           0 : }
     105             : 
     106           0 : void ComputeFactorB(uint256 seedB, uint256& factorB)
     107             : {
     108             :     //factorB - a double sha256 hash of seedb
     109           0 :     Hash(seedB.begin(), seedB.end(), factorB.begin(), factorB.end());
     110           0 : }
     111             : 
     112           1 : std::string AddressToBip38Hash(const std::string& address)
     113             : {
     114           1 :     uint256 addrCheck = Hash(address.begin(), address.end());
     115             : 
     116           2 :     return HexStr(addrCheck).substr(0, 8);
     117             : }
     118             : 
     119           1 : std::string BIP38_Encrypt(std::string strAddress, std::string strPassphrase, uint256 privKey, bool fCompressed)
     120             : {
     121           1 :     std::string strAddressHash = AddressToBip38Hash(strAddress);
     122             : 
     123           1 :     uint512 hashed;
     124           3 :     uint64_t salt = uint256S(ReverseEndianString(strAddressHash)).GetCheapHash();
     125           1 :     scrypt_hash(strPassphrase.c_str(), strPassphrase.size(), BEGIN(salt), strAddressHash.size() / 2, BEGIN(hashed), 16384, 8, 8, 64);
     126             : 
     127           3 :     arith_uint256 derivedHalf1(hashed.ToString().substr(64, 64));
     128           3 :     arith_uint256 derivedHalf2(hashed.ToString().substr(0, 64));
     129             : 
     130             :     //block1 = (pointb[1...16] xor derivedhalf1[0...15])
     131           2 :     arith_uint256 block1 = ((UintToArith256(privKey) << 128) ^ (derivedHalf1 << 128)) >> 128;
     132             : 
     133             :     //encrypt part 1
     134           1 :     arith_uint512 encrypted1;
     135           2 :     AES256Encrypt enc(derivedHalf2.begin());
     136           1 :     enc.Encrypt(encrypted1.begin(), block1.begin());
     137             : 
     138             :     //block2 = (pointb[17...32] xor derivedhalf1[16...31]
     139           1 :     arith_uint256 p2 = UintToArith256(privKey) >> 128;
     140           1 :     arith_uint256 dh12 = derivedHalf1 >> 128;
     141           2 :     arith_uint256 block2 = p2 ^ dh12;
     142             : 
     143             :     //encrypt part 2
     144           1 :     arith_uint512 encrypted2;
     145           1 :     enc.Encrypt(encrypted2.begin(), block2.begin());
     146             : 
     147           2 :     std::string strPrefix = "0142";
     148           1 :     strPrefix += (fCompressed ? "E0" : "C0");
     149             : 
     150           2 :     arith_uint512 encryptedKey(ReverseEndianString(strPrefix + strAddressHash));
     151             : 
     152             :     //add encrypted1 to the end of encryptedKey
     153          20 :     encryptedKey = encryptedKey | (encrypted1 << 56);
     154             : 
     155             :     //add encrypted2 to the end of encryptedKey
     156          20 :     encryptedKey = encryptedKey | (encrypted2 << (56 + 128));
     157             : 
     158             :     //Base58 checksum is the 4 bytes of dSHA256 hash of the encrypted key
     159           1 :     uint256 hashChecksum = Hash(encryptedKey.begin(), encryptedKey.begin() + 39);
     160           2 :     arith_uint512 b58Checksum(hashChecksum.ToString().substr(64 - 8, 8));
     161             : 
     162             :     // append the encrypted key with checksum (currently occupies 312 bits)
     163          20 :     encryptedKey = encryptedKey | (b58Checksum << 312);
     164             : 
     165             :     //43 bytes is the total size that we are encoding
     166           2 :     return EncodeBase58(encryptedKey.begin(), encryptedKey.begin() + 43);
     167             : }
     168             : 
     169           1 : bool BIP38_Decrypt(std::string strPassphrase, std::string strEncryptedKey, uint256& privKey, bool& fCompressed)
     170             : {
     171           2 :     std::string strKey;
     172           1 :     if (!Base58ToHex(strEncryptedKey, strKey)) {
     173             :         // incorrect encoding of key
     174             :         return false;
     175             :     }
     176             : 
     177             :     // invalid prefix
     178           2 :     if (uint256S(ReverseEndianString(strKey.substr(0, 2))) != UINT256_ONE)
     179             :         return false;
     180             : 
     181           2 :     arith_uint256 type(ReverseEndianString(strKey.substr(2, 2)));
     182           2 :     arith_uint256 flag(ReverseEndianString(strKey.substr(4, 2)));
     183           2 :     std::string strAddressHash = strKey.substr(6, 8);
     184           2 :     std::string ownersalt = strKey.substr(14, 16);
     185           3 :     uint256 encryptedPart1 = uint256S(ReverseEndianString(strKey.substr(30, 16)));
     186           3 :     uint256 encryptedPart2 = uint256S(ReverseEndianString(strKey.substr(46, 32)));
     187             : 
     188           3 :     fCompressed = (flag & 0x20) != ARITH_UINT256_ZERO;
     189             : 
     190             :     //not ec multiplied
     191           2 :     if (type == arith_uint256(0x42)) {
     192           1 :         uint512 hashed;
     193           3 :         encryptedPart1 = uint256S(ReverseEndianString(strKey.substr(14, 32)));
     194           3 :         uint64_t salt = uint256S(ReverseEndianString(strAddressHash)).GetCheapHash();
     195           1 :         scrypt_hash(strPassphrase.c_str(), strPassphrase.size(), BEGIN(salt), strAddressHash.size() / 2, BEGIN(hashed), 16384, 8, 8, 64);
     196             : 
     197           3 :         const uint256& derivedHalf1 = uint256S(hashed.ToString().substr(64, 64));
     198           3 :         const uint256& derivedHalf2 = uint256S(hashed.ToString().substr(0, 64));
     199             : 
     200           1 :         uint256 decryptedPart1;
     201           1 :         DecryptAES(encryptedPart1, derivedHalf2, decryptedPart1);
     202             : 
     203           1 :         uint256 decryptedPart2;
     204           1 :         DecryptAES(encryptedPart2, derivedHalf2, decryptedPart2);
     205             : 
     206             :         //combine decrypted parts into 64 bytes
     207           1 :         arith_uint256 temp1 = UintToArith256(decryptedPart2) << 128;
     208          11 :         temp1 = temp1 | UintToArith256(decryptedPart1);
     209             : 
     210             :         //xor the decryption with the derived half 1 for the final key
     211           3 :         privKey = ArithToUint256(temp1 ^ UintToArith256(derivedHalf1));
     212             : 
     213           1 :         return true;
     214           0 :     } else if (type != arith_uint256(0x43)) {
     215             :         //invalid type
     216             :         return false;
     217             :     }
     218             : 
     219           0 :     bool fLotSequence = (flag & 0x04) != 0;
     220             : 
     221           1 :     std::string prefactorSalt = ownersalt;
     222           0 :     if (fLotSequence)
     223           0 :         prefactorSalt = ownersalt.substr(0, 8);
     224             : 
     225           0 :     uint256 prefactor;
     226           0 :     ComputePreFactor(strPassphrase, prefactorSalt, prefactor);
     227             : 
     228           0 :     uint256 passfactor;
     229           0 :     if (fLotSequence)
     230           0 :         ComputePassfactor(ownersalt, prefactor, passfactor);
     231             :     else
     232           0 :         passfactor = prefactor;
     233             : 
     234           0 :     CPubKey passpoint;
     235           0 :     if (!ComputePasspoint(passfactor, passpoint))
     236             :         return false;
     237             : 
     238           0 :     uint512 seedBPass;
     239           0 :     ComputeSeedBPass(passpoint, strAddressHash, ownersalt, seedBPass);
     240             : 
     241             :     //get derived halfs, being mindful for endian switch
     242           0 :     const uint256 derivedHalf1 = uint256S(seedBPass.ToString().substr(64, 64));
     243           0 :     const uint256 derivedHalf2 = uint256S(seedBPass.ToString().substr(0, 64));
     244             : 
     245             :     /** Decrypt encryptedpart2 using AES256Decrypt to yield the last 8 bytes of seedb and the last 8 bytes of encryptedpart1. **/
     246           0 :     uint256 decryptedPart2;
     247           0 :     DecryptAES(encryptedPart2, derivedHalf2, decryptedPart2);
     248             : 
     249             :     //xor decryptedPart2 and 2nd half of derived half 1
     250           0 :     arith_uint256 x0 = UintToArith256(derivedHalf1) >> 128; //drop off the first half (note: endian)
     251           0 :     arith_uint256 x1 = UintToArith256(decryptedPart2) ^ x0;
     252           0 :     arith_uint256 seedbPart2 = x1 >> 64;
     253             : 
     254             :     /** Decrypt encryptedpart1 to yield the remainder of seedb. **/
     255           0 :     uint256 decryptedPart1;
     256           0 :     arith_uint256 x2 = x1 & arith_uint256("0xffffffffffffffff"); // set x2 to seedbPart1 (still encrypted)
     257           0 :     x2 = x2 << 64;                                   //make room to add encryptedPart1 to the front
     258           0 :     x2 = UintToArith256(encryptedPart1) | x2;                        //combine with encryptedPart1
     259           0 :     DecryptAES(ArithToUint256(x2), derivedHalf2, decryptedPart1);
     260             : 
     261             :     //decrypted part 1: seedb[0..15] xor derivedhalf1[0..15]
     262           0 :     arith_uint256 x3 = UintToArith256(derivedHalf1) & arith_uint256("0xffffffffffffffffffffffffffffffff");
     263           0 :     arith_uint256 seedbPart1 = UintToArith256(decryptedPart1) ^ x3;
     264           0 :     arith_uint256 seedB = seedbPart1 | (seedbPart2 << 128);
     265             : 
     266           0 :     uint256 factorB;
     267           0 :     ComputeFactorB(ArithToUint256(seedB), factorB);
     268             : 
     269             :     //multiply passfactor by factorb mod N to yield the priv key
     270           0 :     secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
     271           0 :     assert(ctx != nullptr);
     272           0 :     {
     273             :         // Pass in a random blinding seed to the secp256k1 context.
     274           0 :         std::vector<unsigned char, secure_allocator<unsigned char>> vseed(32);
     275           0 :         GetRandBytes(vseed.data(), 32);
     276           0 :         bool ret = secp256k1_context_randomize(ctx, vseed.data());
     277           0 :         assert(ret);
     278             :     }
     279           0 :     privKey = factorB;
     280           0 :     if (!secp256k1_ec_privkey_tweak_mul(ctx, privKey.begin(), passfactor.begin())) {
     281           0 :         secp256k1_context_destroy(ctx);
     282             :         return false;
     283             :     }
     284           0 :     secp256k1_context_destroy(ctx);
     285             : 
     286             :     //double check that the address hash matches our final privkey
     287           0 :     CKey k;
     288           0 :     k.Set(privKey.begin(), privKey.end(), fCompressed);
     289           0 :     CPubKey pubkey = k.GetPubKey();
     290           0 :     std::string address = EncodeDestination(pubkey.GetID());
     291             : 
     292           0 :     return strAddressHash == AddressToBip38Hash(address);
     293             : }

Generated by: LCOV version 1.14