diff --git a/src/Makefile.am b/src/Makefile.am index c13918d059d80..dfdbe42c447b3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -76,6 +76,7 @@ BITCOIN_CORE_H = \ allocators.h \ amount.h \ base58.h \ + bip38.h \ bloom.h \ chain.h \ chainparams.h \ @@ -210,6 +211,7 @@ libbitcoin_wallet_a_CFLAGS = -fPIC libbitcoin_wallet_a_CPPFLAGS = $(BITCOIN_INCLUDES) libbitcoin_wallet_a_SOURCES = \ activemasternode.cpp \ + bip38.cpp \ obfuscation.cpp \ obfuscation-relay.cpp \ db.cpp \ @@ -240,6 +242,7 @@ crypto_libbitcoin_crypto_a_SOURCES = \ crypto/hmac_sha256.cpp \ crypto/rfc6979_hmac_sha256.cpp \ crypto/hmac_sha512.cpp \ + crypto/scrypt.cpp \ crypto/ripemd160.cpp \ crypto/aes_helper.c \ crypto/blake.c \ @@ -254,6 +257,7 @@ crypto_libbitcoin_crypto_a_SOURCES = \ crypto/hmac_sha256.h \ crypto/rfc6979_hmac_sha256.h \ crypto/hmac_sha512.h \ + crypto/scrypt.h \ crypto/sha1.h \ crypto/ripemd160.h \ crypto/sph_blake.h \ @@ -278,6 +282,7 @@ libbitcoin_common_a_SOURCES = \ allocators.cpp \ amount.cpp \ base58.cpp \ + bip38.cpp \ chainparams.cpp \ coins.cpp \ compressor.cpp \ @@ -402,6 +407,7 @@ libbitcoinconsensus_la_SOURCES = \ allocators.cpp \ primitives/transaction.cpp \ crypto/hmac_sha512.cpp \ + crypto/scrypt.cpp \ crypto/sha1.cpp \ crypto/sha256.cpp \ crypto/sha512.cpp \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index f13928b234a00..8440529a9c227 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -23,6 +23,7 @@ QT_TS = \ QT_FORMS_UI = \ qt/forms/addressbookpage.ui \ qt/forms/askpassphrasedialog.ui \ + qt/forms/bip38tooldialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/obfuscationconfig.ui \ qt/forms/editaddressdialog.ui \ @@ -46,6 +47,7 @@ QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ + qt/moc_bip38tooldialog.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ qt/moc_bitcoinamountfield.cpp \ qt/moc_bitcoingui.cpp \ @@ -115,6 +117,7 @@ BITCOIN_QT_H = \ qt/addressbookpage.h \ qt/addresstablemodel.h \ qt/askpassphrasedialog.h \ + qt/bip38tooldialog.h \ qt/bitcoinaddressvalidator.h \ qt/bitcoinamountfield.h \ qt/bitcoingui.h \ @@ -266,6 +269,7 @@ BITCOIN_QT_CPP += \ qt/addressbookpage.cpp \ qt/addresstablemodel.cpp \ qt/askpassphrasedialog.cpp \ + qt/bip38tooldialog.cpp \ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ qt/obfuscationconfig.cpp \ diff --git a/src/base58.cpp b/src/base58.cpp index c594993ea0952..fd0cee4ac25c5 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -64,6 +65,22 @@ bool DecodeBase58(const char* psz, std::vector& vch) return true; } +std::string DecodeBase58(const char *psz) +{ + std::vector vch; + DecodeBase58(psz, vch); + std::stringstream ss; + ss << std::hex; + + for(unsigned int i = 0; i < vch.size(); i++) + { + unsigned char* c = &vch[i]; + ss << setw(2) << setfill('0') << (int)c[0]; + } + + return ss.str(); +} + std::string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend) { // Skip & count leading zeroes. diff --git a/src/base58.h b/src/base58.h index 67585c64637a1..c74fd2f45563e 100644 --- a/src/base58.h +++ b/src/base58.h @@ -41,6 +41,12 @@ std::string EncodeBase58(const std::vector& vch); */ bool DecodeBase58(const char* psz, std::vector& vchRet); +/** + * Decode a base58-encoded string (psz) into a string. + * psz cannot be NULL. + */ +std::string DecodeBase58(const char* psz); + /** * Decode a base58-encoded string (str) into a byte vector (vchRet). * return true if decoding is successful. diff --git a/src/bip38.cpp b/src/bip38.cpp new file mode 100644 index 0000000000000..936bf9acda43f --- /dev/null +++ b/src/bip38.cpp @@ -0,0 +1,230 @@ + +#include "bip38.h" +#include "base58.h" +#include "hash.h" +#include "pubkey.h" +#include "util.h" +#include "utilstrencodings.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] + */ + +void DecryptAES(uint256 encryptedIn, uint256 decryptionKey, uint256& output) +{ + AES_KEY key; + AES_set_decrypt_key(decryptionKey.begin(), 256, &key); + AES_decrypt(encryptedIn.begin(), output.begin(), &key); +} + +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) +{ + //passpoint is the ec_mult of passfactor on secp256k1 + int clen = 65; + return secp256k1_ec_pubkey_create(UBEGIN(passpoint), &clen, passfactor.begin(), true) != 0; +} + +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) +{ + 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; + AES_KEY key; + AES_set_encrypt_key(derivedHalf2.begin(), 256, &key); + AES_encrypt(block1.begin(), encrypted1.begin(), &key); + + //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; + AES_encrypt(block2.begin(), encrypted2.begin(), &key); + + uint512 encryptedKey(ReverseEndianString("0142E0" + strAddressHash)); + + //add encrypted1 to the end of encryptedKey + encryptedKey = encryptedKey | (encrypted1 << 56); + + //add encrypted2 to the end of encryptedKey + encryptedKey = encryptedKey | (encrypted2 << (56 + 128)); + + //TODO: ensure +43 works on different OS + 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 + privKey = factorB; + if(!secp256k1_ec_privkey_tweak_mul(privKey.begin(), passfactor.begin())) + return false; + + //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); +} diff --git a/src/bip38.h b/src/bip38.h new file mode 100644 index 0000000000000..52eae46adece5 --- /dev/null +++ b/src/bip38.h @@ -0,0 +1,39 @@ +#ifndef BITCOIN_BIP38_H +#define BITCOIN_BIP38_H + +#include "pubkey.h" +#include "uint256.h" + +#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] + */ + +void DecryptAES(uint256 encryptedIn, uint256 decryptionKey, uint256& output); + +void ComputePreFactor(std::string strPassphrase, std::string strSalt, uint256& prefactor); + +void ComputePassfactor(std::string ownersalt, uint256 prefactor, uint256& passfactor); + +bool ComputePasspoint(uint256 passfactor, CPubKey& passpoint); + + +void ComputeSeedBPass(CPubKey passpoint, std::string strAddressHash, std::string strOwnerSalt, uint512& seedBPass); + + +void ComputeFactorB(uint256 seedB, uint256& factorB); + +std::string BIP38_Encrypt(std::string strAddress, std::string strPassphrase, uint256 privKey); +bool BIP38_Decrypt(std::string strPassphrase, std::string strEncryptedKey, uint256& privKey, bool& fCompressed); + +std::string AddressToBip38Hash(std::string address); + +#endif // BIP38_H diff --git a/src/crypto/scrypt.cpp b/src/crypto/scrypt.cpp new file mode 100644 index 0000000000000..f01be3ac95d96 --- /dev/null +++ b/src/crypto/scrypt.cpp @@ -0,0 +1,382 @@ +/* + * Copyright 2009 Colin Percival, 2011 ArtForz, 2012-2013 pooler + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ + +#include "crypto/scrypt.h" +#include "uint256.h" +#include "utilstrencodings.h" +#include +#include + +#include +#include + +static inline void be32enc(void *pp, uint32_t x) +{ + uint8_t *p = (uint8_t *)pp; + p[3] = x & 0xff; + p[2] = (x >> 8) & 0xff; + p[1] = (x >> 16) & 0xff; + p[0] = (x >> 24) & 0xff; +} + +typedef struct HMAC_SHA256Context { + SHA256_CTX ictx; + SHA256_CTX octx; +} HMAC_SHA256_CTX; + +/* Initialize an HMAC-SHA256 operation with the given key. */ +static void +HMAC_SHA256_Init(HMAC_SHA256_CTX *ctx, const void *_K, size_t Klen) +{ + unsigned char pad[64]; + unsigned char khash[32]; + const unsigned char *K = (const unsigned char *)_K; + size_t i; + + /* If Klen > 64, the key is really SHA256(K). */ + if (Klen > 64) { + SHA256_Init(&ctx->ictx); + SHA256_Update(&ctx->ictx, K, Klen); + SHA256_Final(khash, &ctx->ictx); + K = khash; + Klen = 32; + } + + /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ + SHA256_Init(&ctx->ictx); + memset(pad, 0x36, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->ictx, pad, 64); + + /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ + SHA256_Init(&ctx->octx); + memset(pad, 0x5c, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->octx, pad, 64); + + /* Clean the stack. */ + memset(khash, 0, 32); +} + +/* Add bytes to the HMAC-SHA256 operation. */ +static void +HMAC_SHA256_Update(HMAC_SHA256_CTX *ctx, const void *in, size_t len) +{ + /* Feed data to the inner SHA256 operation. */ + SHA256_Update(&ctx->ictx, in, len); +} + +/* Finish an HMAC-SHA256 operation. */ +static void +HMAC_SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX *ctx) +{ + unsigned char ihash[32]; + + /* Finish the inner SHA256 operation. */ + SHA256_Final(ihash, &ctx->ictx); + + /* Feed the inner hash to the outer SHA256 operation. */ + SHA256_Update(&ctx->octx, ihash, 32); + + /* Finish the outer SHA256 operation. */ + SHA256_Final(digest, &ctx->octx); + + /* Clean the stack. */ + memset(ihash, 0, 32); +} + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void +PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, const uint8_t *salt, + size_t saltlen, uint64_t c, uint8_t *buf, size_t dkLen) +{ + HMAC_SHA256_CTX PShctx, hctx; + size_t i; + uint8_t ivec[4]; + uint8_t U[32]; + uint8_t T[32]; + uint64_t j; + int k; + size_t clen; + + /* Compute HMAC state after processing P and S. */ + HMAC_SHA256_Init(&PShctx, passwd, passwdlen); + HMAC_SHA256_Update(&PShctx, salt, saltlen); + + /* Iterate through the blocks. */ + for (i = 0; i * 32 < dkLen; i++) { + /* Generate INT(i + 1). */ + be32enc(ivec, (uint32_t)(i + 1)); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); + HMAC_SHA256_Update(&hctx, ivec, 4); + HMAC_SHA256_Final(U, &hctx); + + /* T_i = U_1 ... */ + memcpy(T, U, 32); + + for (j = 2; j <= c; j++) { + /* Compute U_j. */ + HMAC_SHA256_Init(&hctx, passwd, passwdlen); + HMAC_SHA256_Update(&hctx, U, 32); + HMAC_SHA256_Final(U, &hctx); + + /* ... xor U_j ... */ + for (k = 0; k < 32; k++) + T[k] ^= U[k]; + } + + /* Copy as many bytes as necessary into buf. */ + clen = dkLen - i * 32; + if (clen > 32) + clen = 32; + memcpy(&buf[i * 32], T, clen); + } + + /* Clean PShctx, since we never called _Final on it. */ + memset(&PShctx, 0, sizeof(HMAC_SHA256_CTX)); +} + +static inline uint32_t +le32dec_2(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) + + ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24)); +} + +static inline void +le32enc_2(void * pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; +} + +static void +blkcpy(void * dest, const void * src, size_t len) +{ + size_t * D = (size_t*)dest; + const size_t * S = (size_t*)src; + size_t L = len / sizeof(size_t); + size_t i; + + for (i = 0; i < L; i++) + D[i] = S[i]; +} + +static void +blkxor(void * dest, const void * src, size_t len) +{ + size_t * D = (size_t*)dest; + const size_t* S = (size_t*)src; + size_t L = len / sizeof(size_t); + size_t i; + + for (i = 0; i < L; i++) + D[i] ^= S[i]; +} + +/** + * salsa20_8(B): + * Apply the salsa20/8 core to the provided block. + */ +static void +salsa20_8(uint32_t B[16]) +{ + uint32_t x[16]; + size_t i; + + blkcpy(x, B, 64); + for (i = 0; i < 8; i += 2) { +#define R(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) + /* Operate on columns. */ + x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); + x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); + + x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); + x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); + + x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); + x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); + + x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); + x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); + + /* Operate on rows. */ + x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); + x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); + + x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); + x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); + + x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); + x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); + + x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); + x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); +#undef R + } + for (i = 0; i < 16; i++) + B[i] += x[i]; +} + +/** + * blockmix_salsa8(Bin, Bout, X, r): + * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r + * bytes in length; the output Bout must also be the same size. The + * temporary space X must be 64 bytes. + */ +static void +blockmix_salsa8(const uint32_t * Bin, uint32_t * Bout, uint32_t * X, size_t r) +{ + size_t i; + + /* 1: X <-- B_{2r - 1} */ + blkcpy(X, &Bin[(2 * r - 1) * 16], 64); + + /* 2: for i = 0 to 2r - 1 do */ + for (i = 0; i < 2 * r; i += 2) { + /* 3: X <-- H(X \xor B_i) */ + blkxor(X, &Bin[i * 16], 64); + salsa20_8(X); + + /* 4: Y_i <-- X */ + /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ + blkcpy(&Bout[i * 8], X, 64); + + /* 3: X <-- H(X \xor B_i) */ + blkxor(X, &Bin[i * 16 + 16], 64); + salsa20_8(X); + + /* 4: Y_i <-- X */ + /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ + blkcpy(&Bout[i * 8 + r * 16], X, 64); + } +} + +/** + * integerify(B, r): + * Return the result of parsing B_{2r-1} as a little-endian integer. + */ +static uint64_t +integerify(const void * B, size_t r) +{ + const uint32_t * X = (const uint32_t*)((uintptr_t)(B) + (2 * r - 1) * 64); + + return (((uint64_t)(X[1]) << 32) + X[0]); +} + +void SMix(uint8_t *B, unsigned int r, unsigned int N, void* _V, void* XY) +{ + //new + uint32_t* X = (uint32_t*)XY; + uint32_t* Y = (uint32_t*)((uint8_t*)(XY) + 128 * r); + uint32_t* Z = (uint32_t*)((uint8_t *)(XY) + 256 * r); + uint32_t * V = (uint32_t*)_V; + + uint32_t j, k; + + /* 1: X <-- B */ + for (k = 0; k < 32 * r; k++) + X[k] = le32dec_2(&B[4 * k]); + + /* 2: for i = 0 to N - 1 do */ + for (unsigned int i = 0; i < N; i += 2) + { + /* 3: V_i <-- X */ + blkcpy(&V[i * (32 * r)], X, 128 * r); + + /* 4: X <-- H(X) */ + blockmix_salsa8(X, Y, Z, r); + + /* 3: V_i <-- X */ + blkcpy(&V[(i + 1) * (32 * r)], Y, 128 * r); + + /* 4: X <-- H(X) */ + blockmix_salsa8(Y, X, Z, r); + } + + /* 6: for i = 0 to N - 1 do */ + for (unsigned int i = 0; i < N; i += 2) + { + /* 7: j <-- Integerify(X) mod N */ + j = integerify(X, r) & (N - 1); + + /* 8: X <-- H(X \xor V_j) */ + blkxor(X, &V[j * (32 * r)], 128 * r); + blockmix_salsa8(X, Y, Z, r); + + /* 7: j <-- Integerify(X) mod N */ + j = integerify(Y, r) & (N - 1); + + /* 8: X <-- H(X \xor V_j) */ + blkxor(Y, &V[j * (32 * r)], 128 * r); + blockmix_salsa8(Y, X, Z, r); + } + + /* 10: B' <-- X */ + for (k = 0; k < 32 * r; k++) + le32enc_2(&B[4 * k], X[k]); +} + +void scrypt(const char* pass, unsigned int pLen, const char* salt, unsigned int sLen, char *output, unsigned int N, unsigned int r, unsigned int p, unsigned int dkLen) +{ + //containers + void* V0 = malloc(128 * r * N + 63); + void* XY0 = malloc(256 * r + 64 + 63); + void* B1 = malloc(128 * r * p + 63); + uint8_t* B = (uint8_t *)(((uintptr_t)(B1) + 63) & ~ (uintptr_t)(63)); + uint32_t* V = (uint32_t *)(((uintptr_t)(V0) + 63) & ~ (uintptr_t)(63)); + uint32_t* XY = (uint32_t *)(((uintptr_t)(XY0) + 63) & ~ (uintptr_t)(63)); + + PBKDF2_SHA256((const uint8_t *)pass, pLen, (const uint8_t *)salt, sLen, 1, B, p * 128 * r); + + for(unsigned int i = 0; i < p; i++) + { + SMix(&B[i * 128 * r], r, N, V, XY); + } + + PBKDF2_SHA256((const uint8_t *)pass, pLen, B, p * 128 * r, 1, (uint8_t *)output, dkLen); + + free(V0); + free(XY0); + free(B1); +} diff --git a/src/crypto/scrypt.h b/src/crypto/scrypt.h new file mode 100644 index 0000000000000..68f07acad5aa2 --- /dev/null +++ b/src/crypto/scrypt.h @@ -0,0 +1,9 @@ +#ifndef SCRYPT_H +#define SCRYPT_H +#include +#include +#include + +void scrypt(const char* pass, unsigned int pLen, const char* salt, unsigned int sLen, char *output, unsigned int N, unsigned int r, unsigned int p, unsigned int dkLen); + +#endif diff --git a/src/hash.cpp b/src/hash.cpp index aaca00ea2d387..8db38161c3032 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -4,6 +4,7 @@ #include "hash.h" #include "crypto/hmac_sha512.h" +#include "crypto/scrypt.h" inline uint32_t ROTL32(uint32_t x, int8_t r) { @@ -81,3 +82,8 @@ void BIP32Hash(const unsigned char chainCode[32], unsigned int nChild, unsigned .Write(num, 4) .Finalize(output); } + +void scrypt_hash(const char* pass, unsigned int pLen, const char* salt, unsigned int sLen, char *output, unsigned int N, unsigned int r, unsigned int p, unsigned int dkLen) +{ + scrypt(pass, pLen, salt, sLen, output, N, r, p, dkLen); +} diff --git a/src/hash.h b/src/hash.h index 71b1ecd4c7c13..2f3ada59fb7ee 100644 --- a/src/hash.h +++ b/src/hash.h @@ -22,6 +22,11 @@ #include "crypto/sph_skein.h" #include +#include +#include +#include + +using namespace std; /** A hasher class for Bitcoin's 256-bit hash (double SHA-256). */ class CHash256 { @@ -101,6 +106,31 @@ class CHash160 { } }; +/** Compute the 256-bit hash of a std::string */ +inline std::string Hash(std::string input) +{ + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, input.c_str(), input.size()); + SHA256_Final(hash, &sha256); + stringstream ss; + for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) + { + ss << hex << setw(2) << setfill('0') << (int)hash[i]; + } + return ss.str(); +} + +/** Compute the 256-bit hash of a void pointer */ +inline void Hash(void* in, unsigned int len, unsigned char* out) +{ + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, in, len); + SHA256_Final(out, &sha256); +} + /** Compute the 256-bit hash of an object. */ template inline uint256 Hash(const T1 pbegin, const T1 pend) @@ -354,4 +384,6 @@ inline uint256 HashQuark(const T1 pbegin, const T1 pend) } +void scrypt_hash(const char* pass, unsigned int pLen, const char* salt, unsigned int sLen, char *output, unsigned int N, unsigned int r, unsigned int p, unsigned int dkLen); + #endif // BITCOIN_HASH_H diff --git a/src/key.cpp b/src/key.cpp index acf62360a4782..f42c24bebed53 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -49,6 +49,14 @@ bool CKey::SetPrivKey(const CPrivKey &privkey, bool fCompressedIn) { return true; } +uint256 CKey::GetPrivKey_256() +{ + void* key = &vch; + uint256* key_256 = (uint256*)key; + + return *key_256; +} + CPrivKey CKey::GetPrivKey() const { assert(fValid); CPrivKey privkey; diff --git a/src/key.h b/src/key.h index d85abe095c7f0..46efc4c9c93d8 100644 --- a/src/key.h +++ b/src/key.h @@ -110,6 +110,8 @@ class CKey //! Generate a new private key using a cryptographic PRNG. void MakeNewKey(bool fCompressed); + uint256 GetPrivKey_256(); + /** * Convert the private key to a CPrivKey (serialized OpenSSL private key data). * This is expensive. diff --git a/src/pubkey.h b/src/pubkey.h index 15ffbe1cac71a..261a204491661 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -187,6 +187,12 @@ class CPubKey std::vector Raw() const { return std::vector(vch, vch + size()); } + + std::string GetHex() + { + std::string my_std_string(reinterpret_cast(vch), 65); + return my_std_string; + } }; struct CExtPubKey { diff --git a/src/qt/bip38tooldialog.cpp b/src/qt/bip38tooldialog.cpp new file mode 100644 index 0000000000000..c278d8f8a0e5b --- /dev/null +++ b/src/qt/bip38tooldialog.cpp @@ -0,0 +1,287 @@ +// Copyright (c) 2011-2014 The Bitcoin developers +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2016 The Darknet developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bip38tooldialog.h" +#include "ui_bip38tooldialog.h" + +#include "addressbookpage.h" +#include "guiutil.h" +#include "walletmodel.h" + +#include "base58.h" +#include "bip38.h" +#include "init.h" +#include "wallet.h" + +#include +#include + +#include + +Bip38ToolDialog::Bip38ToolDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::Bip38ToolDialog), + model(0) +{ + ui->setupUi(this); + +#if QT_VERSION >= 0x040700 + ui->decryptedKeyOut_DEC->setPlaceholderText(tr("Click \"Decrypt Key\" to compute key")); +#endif + + GUIUtil::setupAddressWidget(ui->addressIn_ENC, this); + ui->addressIn_ENC->installEventFilter(this); + ui->passphraseIn_ENC->installEventFilter(this); + ui->encryptedKeyOut_ENC->installEventFilter(this); + ui->encryptedKeyIn_DEC->installEventFilter(this); + ui->passphraseIn_DEC->installEventFilter(this); + ui->decryptedKeyOut_DEC->installEventFilter(this); + +} + +Bip38ToolDialog::~Bip38ToolDialog() +{ + delete ui; +} + +void Bip38ToolDialog::setModel(WalletModel *model) +{ + this->model = model; +} + +void Bip38ToolDialog::setAddress_ENC(const QString &address) +{ + ui->addressIn_ENC->setText(address); + ui->passphraseIn_ENC->setFocus(); +} + +void Bip38ToolDialog::setAddress_DEC(const QString &address) +{ + ui->encryptedKeyIn_DEC->setText(address); + ui->passphraseIn_DEC->setFocus(); +} + +void Bip38ToolDialog::showTab_ENC(bool fShow) +{ + ui->tabWidget->setCurrentIndex(0); + if (fShow) + this->show(); +} + +void Bip38ToolDialog::showTab_DEC(bool fShow) +{ + ui->tabWidget->setCurrentIndex(1); + if (fShow) + this->show(); +} + +void Bip38ToolDialog::on_addressBookButton_ENC_clicked() +{ + if (model && model->getAddressTableModel()) + { + AddressBookPage dlg(AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this); + dlg.setModel(model->getAddressTableModel()); + if (dlg.exec()) + { + setAddress_ENC(dlg.getReturnValue()); + } + } +} + +void Bip38ToolDialog::on_pasteButton_ENC_clicked() +{ + setAddress_ENC(QApplication::clipboard()->text()); +} + +QString specialChar = "!#$%&'()*+,-./:;<=>?`{|}~"; +QString validChar = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + specialChar; +bool isValidPassphrase(QString strPassphrase) +{ + for(int i = 0; i < strPassphrase.size(); i++) + { + if (!validChar.contains(strPassphrase[i], Qt::CaseSensitive)) + return false; + } + + return true; +} + +void Bip38ToolDialog::on_encryptKeyButton_ENC_clicked() +{ + if (!model) + return; + + QString qstrPassphrase = ui->passphraseIn_ENC->text(); + if(!isValidPassphrase(qstrPassphrase)) + { + ui->statusLabel_ENC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_ENC->setText(tr("The entered passphrase is invalid.") + QString(" ") + tr("Allowed: 0-9,a-z,A-Z,") + specialChar); + return; + } + + CBitcoinAddress addr(ui->addressIn_ENC->text().toStdString()); + if (!addr.IsValid()) + { + ui->statusLabel_ENC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_ENC->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); + return; + } + + CKeyID keyID; + if (!addr.GetKeyID(keyID)) + { + ui->addressIn_ENC->setValid(false); + ui->statusLabel_ENC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_ENC->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); + return; + } + + WalletModel::UnlockContext ctx(model->requestUnlock(true)); + if (!ctx.isValid()) + { + ui->statusLabel_ENC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_ENC->setText(tr("Wallet unlock was cancelled.")); + return; + } + + CKey key; + if (!pwalletMain->GetKey(keyID, key)) + { + ui->statusLabel_ENC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_ENC->setText(tr("Private key for the entered address is not available.")); + return; + } + + std::string encryptedKey = BIP38_Encrypt(addr.ToString(), qstrPassphrase.toStdString(), key.GetPrivKey_256()); + ui->encryptedKeyOut_ENC->setText(QString::fromStdString(encryptedKey)); +} + +void Bip38ToolDialog::on_copyKeyButton_ENC_clicked() +{ + GUIUtil::setClipboard(ui->encryptedKeyOut_ENC->text()); +} + +void Bip38ToolDialog::on_clearButton_ENC_clicked() +{ + ui->addressIn_ENC->clear(); + ui->passphraseIn_ENC->clear(); + ui->encryptedKeyOut_ENC->clear(); + ui->statusLabel_ENC->clear(); + + ui->addressIn_ENC->setFocus(); +} + +CKey key; +void Bip38ToolDialog::on_decryptKeyButton_DEC_clicked() +{ + string strPassphrase = ui->passphraseIn_DEC->text().toStdString(); + string strKey = ui->encryptedKeyIn_DEC->text().toStdString(); + + uint256 privKey; + bool fCompressed; + if(!BIP38_Decrypt(strPassphrase, strKey, privKey, fCompressed)) + { + ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_DEC->setText(tr("Failed to decrypt.") + QString(" ") + tr("Please check the key and passphrase and try again.")); + return; + } + + key.Set(privKey.begin(), privKey.end(), fCompressed); + CPubKey pubKey = key.GetPubKey(); + CBitcoinAddress address(pubKey.GetID()); + + ui->decryptedKeyOut_DEC->setText(QString::fromStdString(HexStr(privKey))); + ui->addressOut_DEC->setText(QString::fromStdString(address.ToString())); +} + +void Bip38ToolDialog::on_importAddressButton_DEC_clicked() +{ + WalletModel::UnlockContext ctx(model->requestUnlock(true)); + if (!ctx.isValid()) + { + ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_DEC->setText(tr("Wallet unlock was cancelled.")); + return; + } + + CBitcoinAddress address(ui->addressOut_DEC->text().toStdString()); + CPubKey pubkey = key.GetPubKey(); + + if(!address.IsValid() || !key.IsValid() || CBitcoinAddress(pubkey.GetID()).ToString() != address.ToString()) + { + ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_DEC->setText(tr("Data Not Valid.") + QString(" ") + tr("Please try again.")); + return; + } + + CKeyID vchAddress = pubkey.GetID(); + { + ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_DEC->setText(tr("Please wait while key is imported")); + + pwalletMain->MarkDirty(); + pwalletMain->SetAddressBook(vchAddress, "", "receive"); + + // Don't throw error in case a key is already there + if (pwalletMain->HaveKey(vchAddress)) + { + ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_DEC->setText(tr("Key Already Held By Wallet")); + return; + } + + pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1; + + if (!pwalletMain->AddKeyPubKey(key, pubkey)) + { + ui->statusLabel_DEC->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel_DEC->setText(tr("Error Adding Key To Wallet")); + return; + } + + // whenever a key is imported, we need to scan the whole chain + pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); + } + + ui->statusLabel_DEC->setStyleSheet("QLabel { color: green; }"); + ui->statusLabel_DEC->setText(tr("Successfully Added Private Key To Wallet")); +} + +void Bip38ToolDialog::on_clearButton_DEC_clicked() +{ + ui->encryptedKeyIn_DEC->clear(); + ui->decryptedKeyOut_DEC->clear(); + ui->passphraseIn_DEC->clear(); + ui->statusLabel_DEC->clear(); + + ui->encryptedKeyIn_DEC->setFocus(); +} + +bool Bip38ToolDialog::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::FocusIn) + { + if (ui->tabWidget->currentIndex() == 0) + { + /* Clear status message on focus change */ + ui->statusLabel_ENC->clear(); + + /* Select generated signature */ + if (object == ui->encryptedKeyOut_ENC) + { + ui->encryptedKeyOut_ENC->selectAll(); + return true; + } + } + else if (ui->tabWidget->currentIndex() == 1) + { + /* Clear status message on focus change */ + ui->statusLabel_DEC->clear(); + } + } + return QDialog::eventFilter(object, event); +} diff --git a/src/qt/bip38tooldialog.h b/src/qt/bip38tooldialog.h new file mode 100644 index 0000000000000..db26c839c6954 --- /dev/null +++ b/src/qt/bip38tooldialog.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_BIP38DIALOG_H +#define BITCOIN_QT_BIP38DIALOG_H + +#include + +class WalletModel; + +namespace Ui { + class Bip38ToolDialog; +} + +class Bip38ToolDialog : public QDialog +{ + Q_OBJECT + +public: + explicit Bip38ToolDialog(QWidget *parent); + ~Bip38ToolDialog(); + + void setModel(WalletModel *model); + void setAddress_ENC(const QString &address); + void setAddress_DEC(const QString &address); + + void showTab_ENC(bool fShow); + void showTab_DEC(bool fShow); + +protected: + bool eventFilter(QObject *object, QEvent *event); + +private: + Ui::Bip38ToolDialog *ui; + WalletModel *model; + +private slots: + /* encrypt key */ + void on_addressBookButton_ENC_clicked(); + void on_pasteButton_ENC_clicked(); + void on_encryptKeyButton_ENC_clicked(); + void on_copyKeyButton_ENC_clicked(); + void on_clearButton_ENC_clicked(); + /* decrypt key */ + void on_decryptKeyButton_DEC_clicked(); + void on_importAddressButton_DEC_clicked(); + void on_clearButton_DEC_clicked(); +}; + +#endif // BITCOIN_QT_BIP38TOOLDIALOG_H diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 49e301ac1e3d8..a626ad66c6751 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -87,6 +87,7 @@ BitcoinGUI::BitcoinGUI(const NetworkStyle *networkStyle, QWidget *parent) : usedReceivingAddressesAction(0), signMessageAction(0), verifyMessageAction(0), + bip38ToolAction(0), aboutAction(0), receiveCoinsAction(0), optionsAction(0), @@ -390,6 +391,8 @@ void BitcoinGUI::createActions(const NetworkStyle *networkStyle) signMessageAction->setStatusTip(tr("Sign messages with your DarkNet addresses to prove you own them")); verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this); verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified DarkNet addresses")); + bip38ToolAction = new QAction(QIcon(":/icons/key"), tr("&BIP38 tool"), this); + bip38ToolAction->setToolTip(tr("Encrypt and decrypt private keys using a passphrase")); multiSendAction = new QAction(QIcon(":/icons/edit"), tr("&MultiSend"), this); multiSendAction->setToolTip(tr("MultiSend Settings")); multiSendAction->setCheckable(true); @@ -440,6 +443,7 @@ void BitcoinGUI::createActions(const NetworkStyle *networkStyle) connect(lockWalletAction, SIGNAL(triggered()), walletFrame, SLOT(lockWallet())); connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab())); connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab())); + connect(bip38ToolAction, SIGNAL(triggered()), this, SLOT(gotoBip38Tool())); connect(usedSendingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedSendingAddresses())); connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedReceivingAddresses())); connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked())); @@ -480,6 +484,7 @@ void BitcoinGUI::createMenuBar() settings->addAction(changePassphraseAction); settings->addAction(unlockWalletAction); settings->addAction(lockWalletAction); + settings->addAction(bip38ToolAction); settings->addAction(multiSendAction); settings->addSeparator(); } @@ -621,6 +626,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) changePassphraseAction->setEnabled(enabled); signMessageAction->setEnabled(enabled); verifyMessageAction->setEnabled(enabled); + bip38ToolAction->setEnabled(enabled); usedSendingAddressesAction->setEnabled(enabled); usedReceivingAddressesAction->setEnabled(enabled); openAction->setEnabled(enabled); @@ -666,6 +672,7 @@ void BitcoinGUI::createTrayIconMenu() trayIconMenu->addSeparator(); trayIconMenu->addAction(signMessageAction); trayIconMenu->addAction(verifyMessageAction); + trayIconMenu->addAction(bip38ToolAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(optionsAction); trayIconMenu->addAction(openTradingwindowAction); @@ -773,6 +780,11 @@ void BitcoinGUI::gotoVerifyMessageTab(QString addr) if (walletFrame) walletFrame->gotoVerifyMessageTab(addr); } +void BitcoinGUI::gotoBip38Tool() +{ + if (walletFrame) walletFrame->gotoBip38Tool(); +} + void BitcoinGUI::gotoTradingPage() { openTradingwindowAction->setChecked(true); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 8ab92c89a2902..eefe60cb499bc 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -101,6 +101,7 @@ class BitcoinGUI : public QMainWindow QAction *usedReceivingAddressesAction; QAction *signMessageAction; QAction *verifyMessageAction; + QAction *bip38ToolAction; QAction *aboutAction; QAction *receiveCoinsAction; QAction *optionsAction; @@ -212,6 +213,9 @@ private slots: /** Show MultiSend Dialog */ void gotoMultiSendDialog(); + /** Show BIP 38 tool - default to Encryption tab */ + void gotoBip38Tool(); + /** Show open dialog */ void openClicked(); diff --git a/src/qt/forms/bip38tooldialog.ui b/src/qt/forms/bip38tooldialog.ui new file mode 100644 index 0000000000000..e516d3728dee3 --- /dev/null +++ b/src/qt/forms/bip38tooldialog.ui @@ -0,0 +1,439 @@ + + + Bip38ToolDialog + + + + 0 + 0 + 700 + 334 + + + + BIP 38 Tool + + + true + + + + + + 1 + + + + &BIP 38 Encrypt + + + + + + Enter a Darknet Address that you would like to encrypt using BIP 38. Enter a passphrase in the middle box. Press encrypt to compute the encrypted private key. + + + Qt::PlainText + + + true + + + + + + + 0 + + + + + Address: + + + + + + + The DarkNet address to sign the message with + + + + + + + Choose previously used address + + + + + + + :/icons/address-book:/icons/address-book + + + Alt+A + + + false + + + + + + + Paste address from clipboard + + + + + + + :/icons/editpaste:/icons/editpaste + + + Alt+P + + + false + + + + + + + + + + + Passphrase: + + + + + + + QLineEdit::Password + + + + + + + + + 0 + + + + + Encrypted Key: + + + Qt::PlainText + + + + + + + + true + + + + true + + + + + + + Copy the current signature to the system clipboard + + + + + + + :/icons/editcopy:/icons/editcopy + + + false + + + + + + + + + + + Sign the message to prove you own this DarkNet address + + + Encrypt &Key + + + + :/icons/edit:/icons/edit + + + false + + + + + + + Reset all sign message fields + + + Clear &All + + + + :/icons/remove:/icons/remove + + + false + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + 75 + true + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + + + &BIP 38 Decrypt + + + + + + Enter the BIP 38 encrypted private key. Enter the passphrase in the middle box. Click Decrypt Key to compute the private key. After the key is decrypted, clicking 'Import Address' will add this private key to the wallet. + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + Encrypted Key: + + + + + + + The DarkNet address the message was signed with + + + + + + + + + + + Passphrase: + + + + + + + QLineEdit::Password + + + + + + + + + + + Verify the message to ensure it was signed with the specified DarkNet address + + + Decrypt &Key + + + + :/icons/transaction_0:/icons/transaction_0 + + + false + + + + + + + Reset all verify message fields + + + Clear &All + + + + :/icons/remove:/icons/remove + + + false + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + 75 + true + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 48 + + + + + + + + + + + + Decrypted Key: + + + + + + + + + + + + + + Import Address + + + + + + + Address: + + + + + + + + + + + + + + + + + QValidatedLineEdit + QLineEdit +
qvalidatedlineedit.h
+
+
+ + + + +
diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index dd34b0197f5d1..210275c08bf56 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -162,6 +162,13 @@ void WalletFrame::gotoVerifyMessageTab(QString addr) walletView->gotoVerifyMessageTab(addr); } +void WalletFrame::gotoBip38Tool() +{ + WalletView *walletView = currentWalletView(); + if (walletView) + walletView->gotoBip38Tool(); +} + void WalletFrame::gotoMultiSendDialog() { WalletView *walletView = currentWalletView(); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 6cce5c190f7fc..6629e5cab938f 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -68,6 +68,9 @@ public slots: /** Show MultiSend Dialog **/ void gotoMultiSendDialog(); + /** Show BIP 38 tool - default to Encryption tab */ + void gotoBip38Tool(); + /** Encrypt the wallet */ void encryptWallet(bool status); /** Backup the wallet */ diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index a4426303963f0..81be4fd7d7b0c 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -6,6 +6,7 @@ #include "addressbookpage.h" #include "askpassphrasedialog.h" +#include "bip38tooldialog.h" #include "bitcoingui.h" #include "clientmodel.h" #include "guiutil.h" @@ -250,6 +251,14 @@ void WalletView::gotoVerifyMessageTab(QString addr) signVerifyMessageDialog->setAddress_VM(addr); } +void WalletView::gotoBip38Tool() +{ + Bip38ToolDialog *bip38ToolDialog = new Bip38ToolDialog(this); + //bip38ToolDialog->setAttribute(Qt::WA_DeleteOnClose); + bip38ToolDialog->setModel(walletModel); + bip38ToolDialog->showTab_ENC(true); +} + void WalletView::gotoMultiSendDialog() { MultiSendDialog *multiSendDialog = new MultiSendDialog(this); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index c5847f30b9ae2..96fdc2abfccf2 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -92,6 +92,9 @@ public slots: /** Show MultiSend Dialog */ void gotoMultiSendDialog(); + /** Show BIP 38 tool - default to Encryption tab */ + void gotoBip38Tool(); + /** Show incoming transaction notification for new transactions. The new items are those between start and end inclusive, under the given parent item. diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index cba56dd90e9a1..7421e5d20877f 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -4,7 +4,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "base58.h" +#include "bip38.h" #include "rpcserver.h" #include "init.h" #include "main.h" @@ -14,12 +14,16 @@ #include "util.h" #include "utiltime.h" #include "wallet.h" +#include "utilstrencodings.h" #include #include +#include #include #include +#include +#include #include "json/json_spirit_value.h" @@ -399,3 +403,105 @@ Value dumpwallet(const Array& params, bool fHelp) file.close(); return Value::null; } + +Value bip38encrypt(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error( + "bip38encrypt \"darknetaddress\"\n" + "\nEncrypts a private key corresponding to 'darknetaddress'.\n" + "\nArguments:\n" + "1. \"darknetaddress\" (string, required) The darknet address for the private key (you must hold the key already)\n" + "2. \"passphrase\" (string, required) The passphrase you want the private key to be encrypted with - Valid special chars: !#$%&'()*+,-./:;<=>?`{|}~ \n" + "\nResult:\n" + "\"key\" (string) The encrypted private key\n" + "\nExamples:\n" + ); + + EnsureWalletIsUnlocked(); + + string strAddress = params[0].get_str(); + string strPassphrase = params[1].get_str(); + + CBitcoinAddress address; + if (!address.SetString(strAddress)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid DarkNet address"); + CKeyID keyID; + if (!address.GetKeyID(keyID)) + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); + CKey vchSecret; + if (!pwalletMain->GetKey(keyID, vchSecret)) + throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); + + uint256 privKey = vchSecret.GetPrivKey_256(); + string encryptedOut = BIP38_Encrypt(strAddress, strPassphrase, privKey); + + Object result; + result.push_back(Pair("Addess", strAddress)); + result.push_back(Pair("Encrypted Key", encryptedOut)); + + return result; +} + +Value bip38decrypt(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error( + "bip38decrypt \"darknetaddress\"\n" + "\nDecrypts and then imports password protected private key.\n" + "\nArguments:\n" + "1. \"passphrase\" (string, required) The passphrase you want the private key to be encrypted with\n" + "2. \"encryptedkey\" (string, required) The encrypted private key\n" + + "\nResult:\n" + "\"key\" (string) The decrypted private key\n" + "\nExamples:\n" + ); + + EnsureWalletIsUnlocked(); + + /** Collect private key and passphrase **/ + string strPassphrase = params[0].get_str(); + string strKey = params[1].get_str(); + + uint256 privKey; + bool fCompressed; + if(!BIP38_Decrypt(strPassphrase, strKey, privKey, fCompressed)) + throw JSONRPCError(RPC_WALLET_ERROR, "Failed To Decrypt"); + + Object result; + result.push_back(Pair("privatekey", HexStr(privKey))); + + CKey key; + key.Set(privKey.begin(), privKey.end(), fCompressed); + + if(!key.IsValid()) + throw JSONRPCError(RPC_WALLET_ERROR, "Private Key Not Valid"); + + CPubKey pubkey = key.GetPubKey(); + pubkey.IsCompressed(); + assert(key.VerifyPubKey(pubkey)); + result.push_back(Pair("Address", CBitcoinAddress(pubkey.GetID()).ToString())); + CKeyID vchAddress = pubkey.GetID(); + { + pwalletMain->MarkDirty(); + pwalletMain->SetAddressBook(vchAddress, "", "receive"); + + // Don't throw error in case a key is already there + if (pwalletMain->HaveKey(vchAddress)) + throw JSONRPCError(RPC_WALLET_ERROR, "Key already held by wallet"); + + pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1; + + if (!pwalletMain->AddKeyPubKey(key, pubkey)) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + + // whenever a key is imported, we need to scan the whole chain + pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); + } + + return result; +} + + diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index a4319e6208f6d..5b6f26c5d2d75 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -326,6 +326,8 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "backupwallet", &backupwallet, true, false, true }, { "wallet", "dumpprivkey", &dumpprivkey, true, false, true }, { "wallet", "dumpwallet", &dumpwallet, true, false, true }, + { "wallet", "bip38encrypt", &bip38encrypt, true, false, true }, + { "wallet", "bip38decrypt", &bip38decrypt, true, false, true }, { "wallet", "encryptwallet", &encryptwallet, true, false, true }, { "wallet", "getaccountaddress", &getaccountaddress, true, false, true }, { "wallet", "getaccount", &getaccount, true, false, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index f7db2a7b33d97..6df7fdb32ce9b 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -150,6 +150,8 @@ extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool f extern json_spirit::Value importaddress(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value bip38encrypt(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value bip38decrypt(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value setstakesplitthreshold(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getstakesplitthreshold(const json_spirit::Array& params, bool fHelp); diff --git a/src/uint256.cpp b/src/uint256.cpp index 79406f24757b5..dda4466bfb243 100644 --- a/src/uint256.cpp +++ b/src/uint256.cpp @@ -200,6 +200,15 @@ std::string base_uint::ToString() const return (GetHex()); } +template +std::string base_uint::ToStringReverseEndian() const +{ + char psz[sizeof(pn) * 2 + 1]; + for (unsigned int i = 0; i < sizeof(pn); i++) + sprintf(psz + i * 2, "%02x", ((unsigned char*)pn)[i]); + return std::string(psz, psz + sizeof(pn) * 2); +} + template unsigned int base_uint::bits() const { @@ -248,6 +257,15 @@ template std::string base_uint<256>::ToString() const; template void base_uint<256>::SetHex(const char*); template void base_uint<256>::SetHex(const std::string&); template unsigned int base_uint<256>::bits() const; +template std::string base_uint<256>::ToStringReverseEndian() const; + +// Explicit instantiations for base_uint<512> +template base_uint<512>::base_uint(const std::string&); +template base_uint<512>& base_uint<512>::operator<<=(unsigned int); +template base_uint<512>& base_uint<512>::operator>>=(unsigned int); +template std::string base_uint<512>::GetHex() const; +template std::string base_uint<512>::ToString() const; +template std::string base_uint<512>::ToStringReverseEndian() const; // This implementation directly uses shifts instead of going // through an intermediate MPI representation. diff --git a/src/uint256.h b/src/uint256.h index b69fb0a9ac341..d274a122e828d 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -231,6 +231,7 @@ class base_uint void SetHex(const char* psz); void SetHex(const std::string& str); std::string ToString() const; + std::string ToStringReverseEndian() const; unsigned char* begin() { @@ -379,4 +380,11 @@ class uint512 : public base_uint<512> { } }; +inline uint512 uint512S(const std::string& str) +{ + uint512 rv; + rv.SetHex(str); + return rv; +} + #endif // BITCOIN_UINT256_H diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index 358fb4f55f091..53e8659843d95 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -77,6 +77,19 @@ inline std::string HexStr(const T& vch, bool fSpaces=false) return HexStr(vch.begin(), vch.end(), fSpaces); } +/** Reverse the endianess of a string */ +inline std::string ReverseEndianString(std::string in) +{ + std::string out = ""; + unsigned int s = in.size(); + for(unsigned int i = 0; i < s; i+=2) + { + out += in.substr(s - i - 2, 2); + } + + return out; +} + /** * Format a paragraph of text to a fixed width, adding spaces for * indentation to any added line.