Merge pull request #649 from sipa/comprpubkey
authorPieter Wuille <pieter.wuille@gmail.com>
Tue, 10 Jan 2012 21:23:51 +0000 (13:23 -0800)
committerPieter Wuille <pieter.wuille@gmail.com>
Tue, 10 Jan 2012 21:23:51 +0000 (13:23 -0800)
Compressed pubkeys

src/base58.h
src/bitcoinrpc.cpp
src/db.cpp
src/key.h
src/keystore.cpp
src/keystore.h
src/rpcdump.cpp
src/test/key_tests.cpp [new file with mode: 0644]

index ef6493d..dce932b 100644 (file)
@@ -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()
index 09be73a..ce29840 100644 (file)
@@ -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()))
         {
index f43b2a5..b3bbaca 100644 (file)
@@ -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))
index 94ec552..c28222a 100644 (file)
--- 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;
     }
 
@@ -210,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())
                     {
@@ -221,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;
@@ -238,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);
@@ -246,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);
index 21fb0f9..6c3ed34 100644 (file)
@@ -29,8 +29,10 @@ bool CKeyStore::GetPubKey(const CBitcoinAddress &address, std::vector<unsigned c
 
 bool CBasicKeyStore::AddKey(const CKey& key)
 {
+    bool fCompressed = false;
+    CSecret secret = key.GetSecret(fCompressed);
     CRITICAL_BLOCK(cs_KeyStore)
-        mapKeys[CBitcoinAddress(key.GetPubKey())] = key.GetSecret();
+        mapKeys[CBitcoinAddress(key.GetPubKey())] = make_pair(secret, fCompressed);
     return true;
 }
 
@@ -77,16 +79,6 @@ bool CCryptoKeyStore::SetCrypted()
     return true;
 }
 
-std::vector<unsigned char> 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<unsigned char> vchCryptedSecret;
         std::vector<unsigned char> 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<unsigned char> &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<unsigned char> &vchPubKey = (*mi).second.first;
             const std::vector<unsigned char> &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<unsigned char> vchPubKey = key.GetPubKey();
             std::vector<unsigned char> 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;
index 669bf90..801afbf 100644 (file)
@@ -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<CBitcoinAddress> &setAddress) const =0;
     virtual bool GetPubKey(const CBitcoinAddress &address, std::vector<unsigned char>& vchPubKeyOut) const;
 
@@ -39,17 +31,17 @@ public:
 
     // Generate a new key, and add it to the store
     virtual std::vector<unsigned char> 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<CBitcoinAddress, CSecret> KeyMap;
+typedef std::map<CBitcoinAddress, std::pair<CSecret, bool> > KeyMap;
 typedef std::map<uint160, CScript > 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<unsigned char> &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
-    std::vector<unsigned char> 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<unsigned char>& vchPubKeyOut) const;
     void GetKeys(std::set<CBitcoinAddress> &setAddress) const
     {
index f3978fb..471421e 100644 (file)
@@ -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();
 }
diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp
new file mode 100644 (file)
index 0000000..bc8759b
--- /dev/null
@@ -0,0 +1,138 @@
+#include <boost/test/unit_test.hpp>
+
+#include <string>
+#include <vector>
+
+#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<unsigned char> 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<unsigned char> 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<unsigned char> 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<unsigned char> 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()