From eb2c999050cc17f8926bfa7a3dad74801c7ac4af Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 15 Jul 2013 01:05:25 +0200 Subject: [PATCH] BIP32 derivation implementation --- src/base58.h | 26 +++++++ src/chainparams.cpp | 4 + src/chainparams.h | 2 + src/key.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++++ src/key.h | 46 +++++++++++ 5 files changed, 258 insertions(+) diff --git a/src/base58.h b/src/base58.h index 1d4c3ee2940..aabae8de88e 100644 --- a/src/base58.h +++ b/src/base58.h @@ -390,4 +390,30 @@ public: } }; + +template class CBitcoinExtKeyBase : public CBase58Data +{ +public: + void SetKey(const K &key) { + unsigned char vch[Size]; + key.Encode(vch); + SetData(Params().Base58Prefix(Type), vch, vch+Size); + } + + K GetKey() { + K ret; + ret.Decode(&vchData[0], &vchData[Size]); + return ret; + } + + CBitcoinExtKeyBase(const K &key) { + SetKey(key); + } + + CBitcoinExtKeyBase() {} +}; + +typedef CBitcoinExtKeyBase CBitcoinExtKey; +typedef CBitcoinExtKeyBase CBitcoinExtPubKey; + #endif // BITCOIN_BASE58_H diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 69636e5be57..49873023ae8 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -147,6 +147,8 @@ public: base58Prefixes[PUBKEY_ADDRESS] = list_of(0); base58Prefixes[SCRIPT_ADDRESS] = list_of(5); base58Prefixes[SECRET_KEY] = list_of(128); + base58Prefixes[EXT_PUBLIC_KEY] = list_of(0x04)(0x88)(0xB2)(0x1E); + base58Prefixes[EXT_SECRET_KEY] = list_of(0x04)(0x88)(0xAD)(0xE4); // Convert the pnSeeds array into usable address objects. for (unsigned int i = 0; i < ARRAYLEN(pnSeed); i++) @@ -209,6 +211,8 @@ public: base58Prefixes[PUBKEY_ADDRESS] = list_of(111); base58Prefixes[SCRIPT_ADDRESS] = list_of(196); base58Prefixes[SECRET_KEY] = list_of(239); + base58Prefixes[EXT_PUBLIC_KEY] = list_of(0x04)(0x35)(0x87)(0xCF); + base58Prefixes[EXT_SECRET_KEY] = list_of(0x04)(0x35)(0x83)(0x94); } virtual Network NetworkID() const { return CChainParams::TESTNET; } }; diff --git a/src/chainparams.h b/src/chainparams.h index 9f4c079ae28..1930e49afb9 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -45,6 +45,8 @@ public: PUBKEY_ADDRESS, SCRIPT_ADDRESS, SECRET_KEY, + EXT_PUBLIC_KEY, + EXT_SECRET_KEY, MAX_BASE58_TYPES }; diff --git a/src/key.cpp b/src/key.cpp index f73708199ac..7092b0e0c81 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include #include @@ -253,6 +254,57 @@ public: ECDSA_SIG_free(sig); return ret; } + + static bool TweakSecret(unsigned char vchSecretOut[32], const unsigned char vchSecretIn[32], const unsigned char vchTweak[32]) + { + bool ret = true; + BN_CTX *ctx = BN_CTX_new(); + BN_CTX_start(ctx); + BIGNUM *bnSecret = BN_CTX_get(ctx); + BIGNUM *bnTweak = BN_CTX_get(ctx); + BIGNUM *bnOrder = BN_CTX_get(ctx); + EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp256k1); + EC_GROUP_get_order(group, bnOrder, ctx); // what a grossly inefficient way to get the (constant) group order... + BN_bin2bn(vchTweak, 32, bnTweak); + if (BN_cmp(bnTweak, bnOrder) >= 0) + ret = false; // extremely unlikely + BN_bin2bn(vchSecretIn, 32, bnSecret); + BN_add(bnSecret, bnSecret, bnTweak); + BN_nnmod(bnSecret, bnSecret, bnOrder, ctx); + if (BN_is_zero(bnSecret)) + ret = false; // ridiculously unlikely + int nBits = BN_num_bits(bnSecret); + memset(vchSecretOut, 0, 32); + BN_bn2bin(bnSecret, &vchSecretOut[32-(nBits+7)/8]); + EC_GROUP_free(group); + BN_CTX_end(ctx); + BN_CTX_free(ctx); + return ret; + } + + bool TweakPublic(const unsigned char vchTweak[32]) { + bool ret = true; + BN_CTX *ctx = BN_CTX_new(); + BN_CTX_start(ctx); + BIGNUM *bnTweak = BN_CTX_get(ctx); + BIGNUM *bnOrder = BN_CTX_get(ctx); + BIGNUM *bnOne = BN_CTX_get(ctx); + const EC_GROUP *group = EC_KEY_get0_group(pkey); + EC_GROUP_get_order(group, bnOrder, ctx); // what a grossly inefficient way to get the (constant) group order... + BN_bin2bn(vchTweak, 32, bnTweak); + if (BN_cmp(bnTweak, bnOrder) >= 0) + ret = false; // extremely unlikely + EC_POINT *point = EC_POINT_dup(EC_KEY_get0_public_key(pkey), group); + BN_one(bnOne); + EC_POINT_mul(group, point, bnTweak, point, bnOne, ctx); + if (EC_POINT_is_at_infinity(group, point)) + ret = false; // ridiculously unlikely + EC_KEY_set_public_key(pkey, point); + EC_POINT_free(point); + BN_CTX_end(ctx); + BN_CTX_free(ctx); + return ret; + } }; }; // end of anonymous namespace @@ -393,3 +445,131 @@ bool CPubKey::Decompress() { key.GetPubKey(*this, false); return true; } + +void static BIP32Hash(const unsigned char chainCode[32], unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]) { + unsigned char num[4]; + num[0] = (nChild >> 24) & 0xFF; + num[1] = (nChild >> 16) & 0xFF; + num[2] = (nChild >> 8) & 0xFF; + num[3] = (nChild >> 0) & 0xFF; + HMAC_SHA512_CTX ctx; + HMAC_SHA512_Init(&ctx, chainCode, 32); + HMAC_SHA512_Update(&ctx, &header, 1); + HMAC_SHA512_Update(&ctx, data, 32); + HMAC_SHA512_Update(&ctx, num, 4); + HMAC_SHA512_Final(output, &ctx); +} + +bool CKey::Derive(CKey& keyChild, unsigned char ccChild[32], unsigned int nChild, const unsigned char cc[32]) const { + assert(IsValid()); + assert(IsCompressed()); + unsigned char out[64]; + LockObject(out); + if ((nChild >> 31) == 0) { + CPubKey pubkey = GetPubKey(); + assert(pubkey.begin() + 33 == pubkey.end()); + BIP32Hash(cc, nChild, *pubkey.begin(), pubkey.begin()+1, out); + } else { + assert(begin() + 32 == end()); + BIP32Hash(cc, nChild, 0, begin(), out); + } + memcpy(ccChild, out+32, 32); + bool ret = CECKey::TweakSecret((unsigned char*)keyChild.begin(), begin(), out); + UnlockObject(out); + keyChild.fCompressed = true; + keyChild.fValid = ret; + return ret; +} + +bool CPubKey::Derive(CPubKey& pubkeyChild, unsigned char ccChild[32], unsigned int nChild, const unsigned char cc[32]) const { + assert(IsValid()); + assert((nChild >> 31) == 0); + assert(begin() + 33 == end()); + unsigned char out[64]; + BIP32Hash(cc, nChild, *begin(), begin()+1, out); + memcpy(ccChild, out+32, 32); + CECKey key; + bool ret = key.SetPubKey(*this); + ret &= key.TweakPublic(out); + key.GetPubKey(pubkeyChild, true); + return ret; +} + +bool CExtKey::Derive(CExtKey &out, unsigned int nChild) const { + out.nDepth = nDepth + 1; + CKeyID id = key.GetPubKey().GetID(); + memcpy(&out.vchFingerprint[0], &id, 4); + out.nChild = nChild; + return key.Derive(out.key, out.vchChainCode, nChild, vchChainCode); +} + +void CExtKey::SetMaster(const unsigned char *seed, unsigned int nSeedLen) { + static const char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; + HMAC_SHA512_CTX ctx; + HMAC_SHA512_Init(&ctx, hashkey, sizeof(hashkey)); + HMAC_SHA512_Update(&ctx, seed, nSeedLen); + unsigned char out[64]; + LockObject(out); + HMAC_SHA512_Final(out, &ctx); + key.Set(&out[0], &out[32], true); + memcpy(vchChainCode, &out[32], 32); + UnlockObject(out); + nDepth = 0; + nChild = 0; + memset(vchFingerprint, 0, sizeof(vchFingerprint)); +} + +CExtPubKey CExtKey::Neuter() const { + CExtPubKey ret; + ret.nDepth = nDepth; + memcpy(&ret.vchFingerprint[0], &vchFingerprint[0], 4); + ret.nChild = nChild; + ret.pubkey = key.GetPubKey(); + memcpy(&ret.vchChainCode[0], &vchChainCode[0], 32); + return ret; +} + +void CExtKey::Encode(unsigned char code[74]) const { + code[0] = nDepth; + memcpy(code+1, vchFingerprint, 4); + code[5] = (nChild >> 24) & 0xFF; code[6] = (nChild >> 16) & 0xFF; + code[7] = (nChild >> 8) & 0xFF; code[8] = (nChild >> 0) & 0xFF; + memcpy(code+9, vchChainCode, 32); + code[41] = 0; + assert(key.size() == 32); + memcpy(code+42, key.begin(), 32); +} + +void CExtKey::Decode(const unsigned char code[74]) { + nDepth = code[0]; + memcpy(vchFingerprint, code+1, 4); + nChild = (code[5] << 24) | (code[6] << 16) | (code[7] << 8) | code[8]; + memcpy(vchChainCode, code+9, 32); + key.Set(code+42, code+74, true); +} + +void CExtPubKey::Encode(unsigned char code[74]) const { + code[0] = nDepth; + memcpy(code+1, vchFingerprint, 4); + code[5] = (nChild >> 24) & 0xFF; code[6] = (nChild >> 16) & 0xFF; + code[7] = (nChild >> 8) & 0xFF; code[8] = (nChild >> 0) & 0xFF; + memcpy(code+9, vchChainCode, 32); + assert(pubkey.size() == 33); + memcpy(code+41, pubkey.begin(), 33); +} + +void CExtPubKey::Decode(const unsigned char code[74]) { + nDepth = code[0]; + memcpy(vchFingerprint, code+1, 4); + nChild = (code[5] << 24) | (code[6] << 16) | (code[7] << 8) | code[8]; + memcpy(vchChainCode, code+9, 32); + pubkey.Set(code+41, code+74); +} + +bool CExtPubKey::Derive(CExtPubKey &out, unsigned int nChild) const { + out.nDepth = nDepth + 1; + CKeyID id = pubkey.GetID(); + memcpy(&out.vchFingerprint[0], &id, 4); + out.nChild = nChild; + return pubkey.Derive(out.pubkey, out.vchChainCode, nChild, vchChainCode); +} diff --git a/src/key.h b/src/key.h index ce469ad298e..75431e944f2 100644 --- a/src/key.h +++ b/src/key.h @@ -161,6 +161,9 @@ public: // Turn this public key into an uncompressed public key. bool Decompress(); + + // Derive BIP32 child pubkey. + bool Derive(CPubKey& pubkeyChild, unsigned char ccChild[32], unsigned int nChild, const unsigned char cc[32]) const; }; @@ -201,6 +204,10 @@ public: UnlockObject(vch); } + friend bool operator==(const CKey &a, const CKey &b) { + return a.fCompressed == b.fCompressed && memcmp(&a.vch[0], &b.vch[0], 32); + } + // Initialize using begin and end iterators to byte data. template void Set(const T pbegin, const T pend, bool fCompressedIn) { @@ -251,6 +258,45 @@ public: // 0x1D = second key with even y, 0x1E = second key with odd y, // add 0x04 for compressed keys. bool SignCompact(const uint256 &hash, std::vector& vchSig) const; + + // Derive BIP32 child key. + bool Derive(CKey& keyChild, unsigned char ccChild[32], unsigned int nChild, const unsigned char cc[32]) const; +}; + +struct CExtPubKey { + unsigned char nDepth; + unsigned char vchFingerprint[4]; + unsigned int nChild; + unsigned char vchChainCode[32]; + CPubKey pubkey; + + friend bool operator==(const CExtPubKey &a, const CExtPubKey &b) { + return a.nDepth == b.nDepth && memcmp(&a.vchFingerprint[0], &b.vchFingerprint[0], 4) == 0 && a.nChild == b.nChild && + memcmp(&a.vchChainCode[0], &b.vchChainCode[0], 32) == 0 && a.pubkey == b.pubkey; + } + + void Encode(unsigned char code[74]) const; + void Decode(const unsigned char code[74]); + bool Derive(CExtPubKey &out, unsigned int nChild) const; +}; + +struct CExtKey { + unsigned char nDepth; + unsigned char vchFingerprint[4]; + unsigned int nChild; + unsigned char vchChainCode[32]; + CKey key; + + friend bool operator==(const CExtKey &a, const CExtKey &b) { + return a.nDepth == b.nDepth && memcmp(&a.vchFingerprint[0], &b.vchFingerprint[0], 4) == 0 && a.nChild == b.nChild && + memcmp(&a.vchChainCode[0], &b.vchChainCode[0], 32) == 0 && a.key == b.key; + } + + void Encode(unsigned char code[74]) const; + void Decode(const unsigned char code[74]); + bool Derive(CExtKey &out, unsigned int nChild) const; + CExtPubKey Neuter() const; + void SetMaster(const unsigned char *seed, unsigned int nSeedLen); }; #endif