From 11529c6e4f7288d8a64c488a726ee3821c7adefe Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 21 Nov 2011 02:46:28 +0100 Subject: [PATCH 1/3] Compressed pubkeys This patch enabled compressed pubkeys when -compressedpubkeys is passed. These are 33 bytes instead of 65, and require only marginally more CPU power when verifying. Compressed pubkeys have a different corresponding address, so it is determined at generation. When -compressedpubkeys is given, all newly generated addresses will use a compressed key, while older/other addresses keep using normal keys. Unpatched clients will relay and verify these transactions. --- src/base58.h | 21 ++++++++++++--------- src/bitcoinrpc.cpp | 3 +++ src/db.cpp | 2 ++ src/key.h | 34 ++++++++++++++++++++++++++++++---- src/keystore.cpp | 36 ++++++++++++++++++------------------ src/keystore.h | 24 ++++++++---------------- src/rpcdump.cpp | 9 ++++++--- 7 files changed, 79 insertions(+), 50 deletions(-) diff --git a/src/base58.h b/src/base58.h index ef6493d4..dce932b4 100644 --- a/src/base58.h +++ b/src/base58.h @@ -359,22 +359,25 @@ public: class CBitcoinSecret : public CBase58Data { public: - void SetSecret(const CSecret& vchSecret) - { + void SetSecret(const CSecret& vchSecret, bool fCompressed) + { + assert(vchSecret.size() == 32); SetData(fTestNet ? 239 : 128, &vchSecret[0], vchSecret.size()); + if (fCompressed) + vchData.push_back(1); } - CSecret GetSecret() + CSecret GetSecret(bool &fCompressedOut) { CSecret vchSecret; - vchSecret.resize(vchData.size()); - memcpy(&vchSecret[0], &vchData[0], vchData.size()); + vchSecret.resize(32); + memcpy(&vchSecret[0], &vchData[0], 32); + fCompressedOut = vchData.size() == 33; return vchSecret; } bool IsValid() const { - int nExpectedSize = 32; bool fExpectTestNet = false; switch(nVersion) { @@ -388,12 +391,12 @@ public: default: return false; } - return fExpectTestNet == fTestNet && vchData.size() == nExpectedSize; + return fExpectTestNet == fTestNet && (vchData.size() == 32 || (vchData.size() == 33 && vchData[32] == 1)); } - CBitcoinSecret(const CSecret& vchSecret) + CBitcoinSecret(const CSecret& vchSecret, bool fCompressed) { - SetSecret(vchSecret); + SetSecret(vchSecret, fCompressed); } CBitcoinSecret() diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 09be73af..ce298401 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1701,6 +1701,9 @@ Value validateaddress(const Array& params, bool fHelp) ret.push_back(Pair("pubkey", HexStr(vchPubKey))); std::string strPubKey(vchPubKey.begin(), vchPubKey.end()); ret.push_back(Pair("pubkey58", EncodeBase58(vchPubKey))); + CKey key; + key.SetPubKey(vchPubKey); + ret.push_back(Pair("iscompressed", key.IsCompressed())); } else if (pwalletMain->HaveCScript(address.GetHash160())) { diff --git a/src/db.cpp b/src/db.cpp index f43b2a56..b3bbaca1 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -860,12 +860,14 @@ int CWalletDB::LoadWallet(CWallet* pwallet) { CPrivKey pkey; ssValue >> pkey; + key.SetPubKey(vchPubKey); key.SetPrivKey(pkey); } else { CWalletKey wkey; ssValue >> wkey; + key.SetPubKey(vchPubKey); key.SetPrivKey(wkey.vchPrivKey); } if (!pwallet->LoadKey(key)) diff --git a/src/key.h b/src/key.h index 94ec5522..b6d805c0 100644 --- a/src/key.h +++ b/src/key.h @@ -59,16 +59,30 @@ class CKey protected: EC_KEY* pkey; bool fSet; + bool fCompressedPubKey; + + void SetCompressedPubKey() + { + EC_KEY_set_conv_form(pkey, POINT_CONVERSION_COMPRESSED); + fCompressedPubKey = true; + } public: - CKey() + + void Reset() { + fCompressedPubKey = false; pkey = EC_KEY_new_by_curve_name(NID_secp256k1); if (pkey == NULL) throw key_error("CKey::CKey() : EC_KEY_new_by_curve_name failed"); fSet = false; } + CKey() + { + Reset(); + } + CKey(const CKey& b) { pkey = EC_KEY_dup(b.pkey); @@ -95,10 +109,17 @@ public: return !fSet; } - void MakeNewKey() + bool IsCompressed() const + { + return fCompressedPubKey; + } + + void MakeNewKey(bool fCompressed = true) { if (!EC_KEY_generate_key(pkey)) throw key_error("CKey::MakeNewKey() : EC_KEY_generate_key failed"); + if (fCompressed) + SetCompressedPubKey(); fSet = true; } @@ -111,7 +132,7 @@ public: return true; } - bool SetSecret(const CSecret& vchSecret) + bool SetSecret(const CSecret& vchSecret, bool fCompressed = false) { EC_KEY_free(pkey); pkey = EC_KEY_new_by_curve_name(NID_secp256k1); @@ -126,10 +147,12 @@ public: throw key_error("CKey::SetSecret() : EC_KEY_regenerate_key failed"); BN_clear_free(bn); fSet = true; + if (fCompressed || fCompressedPubKey) + SetCompressedPubKey(); return true; } - CSecret GetSecret() const + CSecret GetSecret(bool &fCompressed) const { CSecret vchRet; vchRet.resize(32); @@ -140,6 +163,7 @@ public: int n=BN_bn2bin(bn,&vchRet[32 - nBytes]); if (n != nBytes) throw key_error("CKey::GetSecret(): BN_bn2bin failed"); + fCompressed = fCompressedPubKey; return vchRet; } @@ -161,6 +185,8 @@ public: if (!o2i_ECPublicKey(&pkey, &pbegin, vchPubKey.size())) return false; fSet = true; + if (vchPubKey.size() == 33) + SetCompressedPubKey(); return true; } diff --git a/src/keystore.cpp b/src/keystore.cpp index 21fb0f91..6c3ed349 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -29,8 +29,10 @@ bool CKeyStore::GetPubKey(const CBitcoinAddress &address, std::vector CCryptoKeyStore::GenerateNewKey() -{ - RandAddSeedPerfmon(); - CKey key; - key.MakeNewKey(); - if (!AddKey(key)) - throw std::runtime_error("CCryptoKeyStore::GenerateNewKey() : AddKey failed"); - return key.GetPubKey(); -} - bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) { CRITICAL_BLOCK(cs_KeyStore) @@ -103,6 +95,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) if(!DecryptSecret(vMasterKeyIn, vchCryptedSecret, Hash(vchPubKey.begin(), vchPubKey.end()), vchSecret)) return false; CKey key; + key.SetPubKey(vchPubKey); key.SetSecret(vchSecret); if (key.GetPubKey() == vchPubKey) break; @@ -125,7 +118,8 @@ bool CCryptoKeyStore::AddKey(const CKey& key) std::vector vchCryptedSecret; std::vector vchPubKey = key.GetPubKey(); - if (!EncryptSecret(vMasterKey, key.GetSecret(), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) + bool fCompressed; + if (!EncryptSecret(vMasterKey, key.GetSecret(fCompressed), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) return false; if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret)) @@ -147,19 +141,24 @@ bool CCryptoKeyStore::AddCryptedKey(const std::vector &vchPubKey, return true; } -bool CCryptoKeyStore::GetSecret(const CBitcoinAddress &address, CSecret& vchSecretOut) const +bool CCryptoKeyStore::GetKey(const CBitcoinAddress &address, CKey& keyOut) const { CRITICAL_BLOCK(cs_KeyStore) { if (!IsCrypted()) - return CBasicKeyStore::GetSecret(address, vchSecretOut); + return CBasicKeyStore::GetKey(address, keyOut); CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); if (mi != mapCryptedKeys.end()) { const std::vector &vchPubKey = (*mi).second.first; const std::vector &vchCryptedSecret = (*mi).second.second; - return DecryptSecret(vMasterKey, vchCryptedSecret, Hash(vchPubKey.begin(), vchPubKey.end()), vchSecretOut); + CSecret vchSecret; + if (!DecryptSecret(vMasterKey, vchCryptedSecret, Hash(vchPubKey.begin(), vchPubKey.end()), vchSecret)) + return false; + keyOut.SetPubKey(vchPubKey); + keyOut.SetSecret(vchSecret); + return true; } } return false; @@ -190,14 +189,15 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) return false; fUseCrypto = true; - CKey key; BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys) { - if (!key.SetSecret(mKey.second)) + CKey key; + if (!key.SetSecret(mKey.second.first, false)) return false; const std::vector vchPubKey = key.GetPubKey(); std::vector vchCryptedSecret; - if (!EncryptSecret(vMasterKeyIn, key.GetSecret(), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) + bool fCompressed; + if (!EncryptSecret(vMasterKeyIn, key.GetSecret(fCompressed), Hash(vchPubKey.begin(), vchPubKey.end()), vchCryptedSecret)) return false; if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; diff --git a/src/keystore.h b/src/keystore.h index 669bf901..801afbf9 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -20,15 +20,7 @@ public: // Check whether a key corresponding to a given address is present in the store. virtual bool HaveKey(const CBitcoinAddress &address) const =0; - virtual bool GetKey(const CBitcoinAddress &address, CKey& keyOut) const - { - CSecret vchSecret; - if (!GetSecret(address, vchSecret)) - return false; - if (!keyOut.SetSecret(vchSecret)) - return false; - return true; - } + virtual bool GetKey(const CBitcoinAddress &address, CKey& keyOut) const =0; virtual void GetKeys(std::set &setAddress) const =0; virtual bool GetPubKey(const CBitcoinAddress &address, std::vector& vchPubKeyOut) const; @@ -39,17 +31,17 @@ public: // Generate a new key, and add it to the store virtual std::vector GenerateNewKey(); - virtual bool GetSecret(const CBitcoinAddress &address, CSecret& vchSecret) const + virtual bool GetSecret(const CBitcoinAddress &address, CSecret& vchSecret, bool &fCompressed) const { CKey key; if (!GetKey(address, key)) return false; - vchSecret = key.GetSecret(); + vchSecret = key.GetSecret(fCompressed); return true; } }; -typedef std::map KeyMap; +typedef std::map > KeyMap; typedef std::map ScriptMap; // Basic key store, that keeps keys in an address->secret map @@ -81,14 +73,15 @@ public: } } } - bool GetSecret(const CBitcoinAddress &address, CSecret &vchSecret) const + bool GetKey(const CBitcoinAddress &address, CKey &keyOut) const { CRITICAL_BLOCK(cs_KeyStore) { KeyMap::const_iterator mi = mapKeys.find(address); if (mi != mapKeys.end()) { - vchSecret = (*mi).second; + keyOut.Reset(); + keyOut.SetSecret((*mi).second.first, (*mi).second.second); return true; } } @@ -154,7 +147,6 @@ public: } virtual bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); - std::vector GenerateNewKey(); bool AddKey(const CKey& key); bool HaveKey(const CBitcoinAddress &address) const { @@ -166,7 +158,7 @@ public: } return false; } - bool GetSecret(const CBitcoinAddress &address, CSecret& vchSecret) const; + bool GetKey(const CBitcoinAddress &address, CKey& keyOut) const; bool GetPubKey(const CBitcoinAddress &address, std::vector& vchPubKeyOut) const; void GetKeys(std::set &setAddress) const { diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index f3978fbc..471421e7 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -62,7 +62,9 @@ Value importprivkey(const Array& params, bool fHelp) if (!fGood) throw JSONRPCError(-5,"Invalid private key"); CKey key; - key.SetSecret(vchSecret.GetSecret()); + bool fCompressed; + CSecret secret = vchSecret.GetSecret(fCompressed); + key.SetSecret(secret, fCompressed); CBitcoinAddress vchAddress = CBitcoinAddress(key.GetPubKey()); CRITICAL_BLOCK(cs_main) @@ -95,7 +97,8 @@ Value dumpprivkey(const Array& params, bool fHelp) if (!address.SetString(strAddress)) throw JSONRPCError(-5, "Invalid bitcoin address"); CSecret vchSecret; - if (!pwalletMain->GetSecret(address, vchSecret)) + bool fCompressed; + if (!pwalletMain->GetSecret(address, vchSecret, fCompressed)) throw JSONRPCError(-4,"Private key for address " + strAddress + " is not known"); - return CBitcoinSecret(vchSecret).ToString(); + return CBitcoinSecret(vchSecret, fCompressed).ToString(); } From d4d9c734c315e99136fe245c5733ca75cab9f8bf Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 25 Dec 2011 15:02:31 +0100 Subject: [PATCH 2/3] Compact signatures with compressed pubkeys --- src/key.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/key.h b/src/key.h index b6d805c0..c28222a9 100644 --- a/src/key.h +++ b/src/key.h @@ -236,6 +236,8 @@ public: { CKey keyRec; keyRec.fSet = true; + if (fCompressedPubKey) + keyRec.SetCompressedPubKey(); if (ECDSA_SIG_recover_key_GFp(keyRec.pkey, sig, (unsigned char*)&hash, sizeof(hash), i, 1) == 1) if (keyRec.GetPubKey() == this->GetPubKey()) { @@ -247,7 +249,7 @@ public: if (nRecId == -1) throw key_error("CKey::SignCompact() : unable to construct recoverable key"); - vchSig[0] = nRecId+27; + vchSig[0] = nRecId+27+(fCompressedPubKey ? 4 : 0); BN_bn2bin(sig->r,&vchSig[33-(nBitsR+7)/8]); BN_bn2bin(sig->s,&vchSig[65-(nBitsS+7)/8]); fOk = true; @@ -264,7 +266,8 @@ public: { if (vchSig.size() != 65) return false; - if (vchSig[0]<27 || vchSig[0]>=31) + int nV = vchSig[0]; + if (nV<27 || nV>=35) return false; ECDSA_SIG *sig = ECDSA_SIG_new(); BN_bin2bn(&vchSig[1],32,sig->r); @@ -272,7 +275,12 @@ public: EC_KEY_free(pkey); pkey = EC_KEY_new_by_curve_name(NID_secp256k1); - if (ECDSA_SIG_recover_key_GFp(pkey, sig, (unsigned char*)&hash, sizeof(hash), vchSig[0] - 27, 0) == 1) + if (nV >= 31) + { + SetCompressedPubKey(); + nV -= 4; + } + if (ECDSA_SIG_recover_key_GFp(pkey, sig, (unsigned char*)&hash, sizeof(hash), nV - 27, 0) == 1) { fSet = true; ECDSA_SIG_free(sig); From b3974ec9d49e20e72e502e901b6c5aab5ac33b03 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 8 Jan 2012 16:59:34 +0100 Subject: [PATCH 3/3] Unit tests for EC key routines This tests: * creation of keys from base58-encoded strings * extracting public keys and addresses * compressed public keys * compact signatures and key recovery --- src/test/key_tests.cpp | 138 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/test/key_tests.cpp diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp new file mode 100644 index 00000000..bc8759b6 --- /dev/null +++ b/src/test/key_tests.cpp @@ -0,0 +1,138 @@ +#include + +#include +#include + +#include "key.h" +#include "base58.h" +#include "uint256.h" +#include "util.h" + +using namespace std; + +static const string strSecret1 ("5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"); +static const string strSecret2 ("5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"); +static const string strSecret1C("Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"); +static const string strSecret2C("L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"); + +#ifdef KEY_TESTS_DUMPINFO +void dumpKeyInfo(uint256 privkey) +{ + CSecret secret; + secret.resize(32); + memcpy(&secret[0], &privkey, 32); + vector sec; + sec.resize(32); + memcpy(&sec[0], &secret[0], 32); + printf(" * secret (hex): %s\n", HexStr(sec).c_str()); + + for (int nCompressed=0; nCompressed<2; nCompressed++) + { + bool fCompressed = nCompressed == 1; + printf(" * %s:\n", fCompressed ? "compressed" : "uncompressed"); + CBitcoinSecret bsecret; + bsecret.SetSecret(secret, fCompressed); + printf(" * secret (base58): %s\n", bsecret.ToString().c_str()); + CKey key; + key.SetSecret(secret, fCompressed); + vector vchPubKey = key.GetPubKey(); + printf(" * pubkey (hex): %s\n", HexStr(vchPubKey).c_str()); + printf(" * address (base58): %s\n", CBitcoinAddress(vchPubKey).ToString().c_str()); + } +} +#endif + + +BOOST_AUTO_TEST_SUITE(key_tests) + +BOOST_AUTO_TEST_CASE(key_test1) +{ + CBitcoinSecret bsecret1, bsecret2, bsecret1C, bsecret2C; + bsecret1.SetString (strSecret1); + bsecret2.SetString (strSecret2); + bsecret1C.SetString(strSecret1C); + bsecret2C.SetString(strSecret2C); + + bool fCompressed; + CSecret secret1 = bsecret1.GetSecret (fCompressed); + BOOST_CHECK(fCompressed == false); + CSecret secret2 = bsecret2.GetSecret (fCompressed); + BOOST_CHECK(fCompressed == false); + CSecret secret1C = bsecret1C.GetSecret(fCompressed); + BOOST_CHECK(fCompressed == true); + CSecret secret2C = bsecret2C.GetSecret(fCompressed); + BOOST_CHECK(fCompressed == true); + + BOOST_CHECK(secret1 == secret1C); + BOOST_CHECK(secret2 == secret2C); + + CKey key1, key2, key1C, key2C; + key1.SetSecret(secret1, false); + key2.SetSecret(secret2, false); + key1C.SetSecret(secret1, true); + key2C.SetSecret(secret2, true); + + BOOST_CHECK(CBitcoinAddress(key1.GetPubKey ()).ToString() == "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"); + BOOST_CHECK(CBitcoinAddress(key2.GetPubKey ()).ToString() == "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"); + BOOST_CHECK(CBitcoinAddress(key1C.GetPubKey()).ToString() == "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"); + BOOST_CHECK(CBitcoinAddress(key2C.GetPubKey()).ToString() == "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"); + + for (int n=0; n<16; n++) + { + string strMsg = strprintf("Very secret message %i: 11", n); + uint256 hashMsg = Hash(strMsg.begin(), strMsg.end()); + + // normal signatures + + vector sign1, sign2, sign1C, sign2C; + + BOOST_CHECK(key1.Sign (hashMsg, sign1)); + BOOST_CHECK(key2.Sign (hashMsg, sign2)); + BOOST_CHECK(key1C.Sign(hashMsg, sign1C)); + BOOST_CHECK(key2C.Sign(hashMsg, sign2C)); + + BOOST_CHECK( key1.Verify(hashMsg, sign1)); + BOOST_CHECK(!key1.Verify(hashMsg, sign2)); + BOOST_CHECK( key1.Verify(hashMsg, sign1C)); + BOOST_CHECK(!key1.Verify(hashMsg, sign2C)); + + BOOST_CHECK(!key2.Verify(hashMsg, sign1)); + BOOST_CHECK( key2.Verify(hashMsg, sign2)); + BOOST_CHECK(!key2.Verify(hashMsg, sign1C)); + BOOST_CHECK( key2.Verify(hashMsg, sign2C)); + + BOOST_CHECK( key1C.Verify(hashMsg, sign1)); + BOOST_CHECK(!key1C.Verify(hashMsg, sign2)); + BOOST_CHECK( key1C.Verify(hashMsg, sign1C)); + BOOST_CHECK(!key1C.Verify(hashMsg, sign2C)); + + BOOST_CHECK(!key2C.Verify(hashMsg, sign1)); + BOOST_CHECK( key2C.Verify(hashMsg, sign2)); + BOOST_CHECK(!key2C.Verify(hashMsg, sign1C)); + BOOST_CHECK( key2C.Verify(hashMsg, sign2C)); + + // compact signatures (with key recovery) + + vector csign1, csign2, csign1C, csign2C; + + BOOST_CHECK(key1.SignCompact (hashMsg, csign1)); + BOOST_CHECK(key2.SignCompact (hashMsg, csign2)); + BOOST_CHECK(key1C.SignCompact(hashMsg, csign1C)); + BOOST_CHECK(key2C.SignCompact(hashMsg, csign2C)); + + CKey rkey1, rkey2, rkey1C, rkey2C; + + BOOST_CHECK(rkey1.SetCompactSignature (hashMsg, csign1)); + BOOST_CHECK(rkey2.SetCompactSignature (hashMsg, csign2)); + BOOST_CHECK(rkey1C.SetCompactSignature(hashMsg, csign1C)); + BOOST_CHECK(rkey2C.SetCompactSignature(hashMsg, csign2C)); + + + BOOST_CHECK(rkey1.GetPubKey() == key1.GetPubKey()); + BOOST_CHECK(rkey2.GetPubKey() == key2.GetPubKey()); + BOOST_CHECK(rkey1C.GetPubKey() == key1C.GetPubKey()); + BOOST_CHECK(rkey2C.GetPubKey() == key2C.GetPubKey()); + } +} + +BOOST_AUTO_TEST_SUITE_END()