From 62b7cd61735d483ae7072824f9229d10c4cfff79 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 15 Jun 2016 14:58:30 -0600 Subject: [PATCH 1/8] Do not encode leading bytes in `PaymentAddress` serialization; this is a task for a higher-level API. --- src/zcash/Address.hpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 91e35bd03..86e351cf7 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -19,13 +19,6 @@ public: template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - unsigned char leadingByte = 0x92; - READWRITE(leadingByte); - - if (leadingByte != 0x92) { - throw std::ios_base::failure("unrecognized payment address lead byte"); - } - READWRITE(a_pk); READWRITE(pk_enc); } @@ -51,4 +44,4 @@ public: } -#endif // _ZCADDRESS_H_ \ No newline at end of file +#endif // _ZCADDRESS_H_ From e104fcddf8113ae97626731bf701b1318157e2d6 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 15 Jun 2016 14:59:05 -0600 Subject: [PATCH 2/8] Use base58check to encode Zcash payment addresses, such that the first two bytes are "zc". --- src/base58.cpp | 38 ++++++++++++++++++++++++++++++++++++++ src/base58.h | 12 ++++++++++++ src/chainparams.cpp | 3 +++ src/chainparams.h | 2 ++ src/wallet/rpcwallet.cpp | 13 ++++--------- 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index c80918505..9649a6811 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -7,6 +7,9 @@ #include "hash.h" #include "uint256.h" +#include "version.h" +#include "streams.h" + #include #include #include @@ -309,3 +312,38 @@ bool CBitcoinSecret::SetString(const std::string& strSecret) { return SetString(strSecret.c_str()); } + +const size_t serializedPaymentAddressSize = 64; + +bool CZCPaymentAddress::Set(const libzcash::PaymentAddress& addr) +{ + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << addr; + std::vector addrSerialized(ss.begin(), ss.end()); + assert(addrSerialized.size() == serializedPaymentAddressSize); + SetData(Params().Base58Prefix(CChainParams::ZCPAYMENT_ADDRRESS), &addrSerialized[0], serializedPaymentAddressSize); + return true; +} + +libzcash::PaymentAddress CZCPaymentAddress::Get() const +{ + if (vchData.size() != serializedPaymentAddressSize) { + throw std::runtime_error( + "payment address is invalid" + ); + } + + if (vchVersion != Params().Base58Prefix(CChainParams::ZCPAYMENT_ADDRRESS)) { + throw std::runtime_error( + "payment address is for wrong network type" + ); + } + + std::vector serialized(vchData.begin(), vchData.end()); + + CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION); + libzcash::PaymentAddress ret; + ss >> ret; + return ret; +} + diff --git a/src/base58.h b/src/base58.h index 787979c82..27f564cc6 100644 --- a/src/base58.h +++ b/src/base58.h @@ -20,6 +20,7 @@ #include "script/script.h" #include "script/standard.h" #include "support/allocators/zeroafterfree.h" +#include "zcash/Address.hpp" #include #include @@ -95,6 +96,17 @@ public: bool operator> (const CBase58Data& b58) const { return CompareTo(b58) > 0; } }; +class CZCPaymentAddress : public CBase58Data { +public: + bool Set(const libzcash::PaymentAddress& addr); + CZCPaymentAddress() {} + + CZCPaymentAddress(const std::string& strAddress) { SetString(strAddress.c_str(), 2); } + CZCPaymentAddress(const libzcash::PaymentAddress& addr) { Set(addr); } + + libzcash::PaymentAddress Get() const; +}; + /** base58-encoded Bitcoin addresses. * Public-key-hash-addresses have version 0 (or 111 testnet). * The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 1e45243ae..c73c68ce4 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -105,6 +105,8 @@ public: base58Prefixes[SECRET_KEY] = std::vector(1,128); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x88)(0xB2)(0x1E).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container >(); + // guarantees the first two characters, when base58 encoded, are "zc" + base58Prefixes[ZCPAYMENT_ADDRRESS] = {22,154}; vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -182,6 +184,7 @@ public: base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); + base58Prefixes[ZCPAYMENT_ADDRRESS] = {22,155}; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); diff --git a/src/chainparams.h b/src/chainparams.h index 7169c3241..86d2dfb93 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -42,6 +42,8 @@ public: EXT_PUBLIC_KEY, EXT_SECRET_KEY, + ZCPAYMENT_ADDRRESS, + MAX_BASE58_TYPES }; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0c7db2570..a2027aeca 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2634,11 +2634,8 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp) BOOST_FOREACH(const Pair& s, outputs) { - PaymentAddress addrTo; - { - CDataStream ssData(ParseHexV(s.name_, "to_address"), SER_NETWORK, PROTOCOL_VERSION); - ssData >> addrTo; - } + CZCPaymentAddress pubaddr(s.name_); + PaymentAddress addrTo = pubaddr.Get(); CAmount nAmount = AmountFromValue(s.value_); vpourout.push_back(JSOutput(addrTo, nAmount)); @@ -2751,20 +2748,18 @@ Value zc_raw_keygen(const json_spirit::Array& params, bool fHelp) auto addr = k.address(); auto viewing_key = k.viewing_key(); - CDataStream pub(SER_NETWORK, PROTOCOL_VERSION); CDataStream priv(SER_NETWORK, PROTOCOL_VERSION); CDataStream viewing(SER_NETWORK, PROTOCOL_VERSION); - pub << addr; priv << k; viewing << viewing_key; - std::string pub_hex = HexStr(pub.begin(), pub.end()); + CZCPaymentAddress pubaddr(addr); std::string priv_hex = HexStr(priv.begin(), priv.end()); std::string viewing_hex = HexStr(viewing.begin(), viewing.end()); Object result; - result.push_back(Pair("zcaddress", pub_hex)); + result.push_back(Pair("zcaddress", pubaddr.ToString())); result.push_back(Pair("zcsecretkey", priv_hex)); result.push_back(Pair("zcviewingkey", viewing_hex)); return result; From 83ee1903fc7818361d64b96fc4e61f344bcdff90 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 15 Jun 2016 15:06:07 -0600 Subject: [PATCH 3/8] Add tests for `CZCPaymentAddress`. --- src/test/key_tests.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 13ca94946..d6f976968 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -11,12 +11,15 @@ #include "utilstrencodings.h" #include "test/test_bitcoin.h" +#include "zcash/Address.hpp" + #include #include #include using namespace std; +using namespace libzcash; static const string strSecret1 ("5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"); static const string strSecret2 ("5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"); @@ -188,4 +191,24 @@ BOOST_AUTO_TEST_CASE(key_test1) BOOST_CHECK(detsigc == ParseHex("2052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d")); } +BOOST_AUTO_TEST_CASE(zc_address_test) +{ + for (size_t i = 0; i < 1000; i++) { + auto sk = SpendingKey::random(); + auto addr = sk.address(); + + CZCPaymentAddress paymentaddr(addr); + string addr_string = paymentaddr.ToString(); + + BOOST_CHECK(addr_string[0] == 'z'); + BOOST_CHECK(addr_string[1] == 'c'); + + CZCPaymentAddress paymentaddr2(addr_string); + + PaymentAddress addr2 = paymentaddr2.Get(); + BOOST_CHECK(addr.a_pk == addr2.a_pk); + BOOST_CHECK(addr.pk_enc == addr2.pk_enc); + } +} + BOOST_AUTO_TEST_SUITE_END() From 0d6864e4657aec8f113f21b9af91e6035a927ab2 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 15 Jun 2016 16:03:57 -0600 Subject: [PATCH 4/8] Added encoding for Zcash spending keys. --- src/base58.cpp | 34 ++++++++++++++++++++++++++++++++++ src/base58.h | 11 +++++++++++ src/chainparams.cpp | 2 ++ src/chainparams.h | 1 + src/test/key_tests.cpp | 27 ++++++++++++++++++--------- src/wallet/rpcwallet.cpp | 34 ++++++---------------------------- 6 files changed, 72 insertions(+), 37 deletions(-) diff --git a/src/base58.cpp b/src/base58.cpp index 9649a6811..efce9186f 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -347,3 +347,37 @@ libzcash::PaymentAddress CZCPaymentAddress::Get() const return ret; } +const size_t serializedSpendingKeySize = 32; + +bool CZCSpendingKey::Set(const libzcash::SpendingKey& addr) +{ + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << addr; + std::vector addrSerialized(ss.begin(), ss.end()); + assert(addrSerialized.size() == serializedSpendingKeySize); + SetData(Params().Base58Prefix(CChainParams::ZCSPENDING_KEY), &addrSerialized[0], serializedSpendingKeySize); + return true; +} + +libzcash::SpendingKey CZCSpendingKey::Get() const +{ + if (vchData.size() != serializedSpendingKeySize) { + throw std::runtime_error( + "spending key is invalid" + ); + } + + if (vchVersion != Params().Base58Prefix(CChainParams::ZCSPENDING_KEY)) { + throw std::runtime_error( + "spending key is for wrong network type" + ); + } + + std::vector serialized(vchData.begin(), vchData.end()); + + CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION); + libzcash::SpendingKey ret; + ss >> ret; + return ret; +} + diff --git a/src/base58.h b/src/base58.h index 27f564cc6..8a442b1bc 100644 --- a/src/base58.h +++ b/src/base58.h @@ -107,6 +107,17 @@ public: libzcash::PaymentAddress Get() const; }; +class CZCSpendingKey : public CBase58Data { +public: + bool Set(const libzcash::SpendingKey& addr); + CZCSpendingKey() {} + + CZCSpendingKey(const std::string& strAddress) { SetString(strAddress.c_str(), 2); } + CZCSpendingKey(const libzcash::SpendingKey& addr) { Set(addr); } + + libzcash::SpendingKey Get() const; +}; + /** base58-encoded Bitcoin addresses. * Public-key-hash-addresses have version 0 (or 111 testnet). * The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. diff --git a/src/chainparams.cpp b/src/chainparams.cpp index c73c68ce4..8246a566f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -107,6 +107,7 @@ public: base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container >(); // guarantees the first two characters, when base58 encoded, are "zc" base58Prefixes[ZCPAYMENT_ADDRRESS] = {22,154}; + base58Prefixes[ZCSPENDING_KEY] = {22,180}; vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -185,6 +186,7 @@ public: base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); base58Prefixes[ZCPAYMENT_ADDRRESS] = {22,155}; + base58Prefixes[ZCSPENDING_KEY] = {22,181}; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); diff --git a/src/chainparams.h b/src/chainparams.h index 86d2dfb93..de89fa0ef 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -43,6 +43,7 @@ public: EXT_SECRET_KEY, ZCPAYMENT_ADDRRESS, + ZCSPENDING_KEY, MAX_BASE58_TYPES }; diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index d6f976968..f15226ea5 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -195,19 +195,28 @@ BOOST_AUTO_TEST_CASE(zc_address_test) { for (size_t i = 0; i < 1000; i++) { auto sk = SpendingKey::random(); - auto addr = sk.address(); + { + CZCSpendingKey spendingkey(sk); + string sk_string = spendingkey.ToString(); + CZCSpendingKey spendingkey2(sk_string); + SpendingKey sk2 = spendingkey2.Get(); + BOOST_CHECK(sk.inner() == sk2.inner()); + } + { + auto addr = sk.address(); - CZCPaymentAddress paymentaddr(addr); - string addr_string = paymentaddr.ToString(); + CZCPaymentAddress paymentaddr(addr); + string addr_string = paymentaddr.ToString(); - BOOST_CHECK(addr_string[0] == 'z'); - BOOST_CHECK(addr_string[1] == 'c'); + BOOST_CHECK(addr_string[0] == 'z'); + BOOST_CHECK(addr_string[1] == 'c'); - CZCPaymentAddress paymentaddr2(addr_string); + CZCPaymentAddress paymentaddr2(addr_string); - PaymentAddress addr2 = paymentaddr2.Get(); - BOOST_CHECK(addr.a_pk == addr2.a_pk); - BOOST_CHECK(addr.pk_enc == addr2.pk_enc); + PaymentAddress addr2 = paymentaddr2.Get(); + BOOST_CHECK(addr.a_pk == addr2.a_pk); + BOOST_CHECK(addr.pk_enc == addr2.pk_enc); + } } } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a2027aeca..ae3354c2c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2459,18 +2459,8 @@ Value zc_raw_receive(const json_spirit::Array& params, bool fHelp) LOCK(cs_main); - SpendingKey k; - - { - CDataStream ssData(ParseHexV(params[0], "zcsecretkey"), SER_NETWORK, PROTOCOL_VERSION); - try { - ssData >> k; - } catch(const std::exception &) { - throw runtime_error( - "zcsecretkey could not be decoded" - ); - } - } + CZCSpendingKey spendingkey(params[0].get_str()); + SpendingKey k = spendingkey.Get(); uint256 epk; unsigned char nonce; @@ -2581,18 +2571,8 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp) BOOST_FOREACH(const Pair& s, inputs) { - SpendingKey k; - - { - CDataStream ssData(ParseHexV(s.value_, "zcsecretkey"), SER_NETWORK, PROTOCOL_VERSION); - try { - ssData >> k; - } catch(const std::exception &) { - throw runtime_error( - "zcsecretkey could not be decoded" - ); - } - } + CZCSpendingKey spendingkey(s.value_.get_str()); + SpendingKey k = spendingkey.Get(); keys.push_back(k); @@ -2748,19 +2728,17 @@ Value zc_raw_keygen(const json_spirit::Array& params, bool fHelp) auto addr = k.address(); auto viewing_key = k.viewing_key(); - CDataStream priv(SER_NETWORK, PROTOCOL_VERSION); CDataStream viewing(SER_NETWORK, PROTOCOL_VERSION); - priv << k; viewing << viewing_key; CZCPaymentAddress pubaddr(addr); - std::string priv_hex = HexStr(priv.begin(), priv.end()); + CZCSpendingKey spendingkey(k); std::string viewing_hex = HexStr(viewing.begin(), viewing.end()); Object result; result.push_back(Pair("zcaddress", pubaddr.ToString())); - result.push_back(Pair("zcsecretkey", priv_hex)); + result.push_back(Pair("zcsecretkey", spendingkey.ToString())); result.push_back(Pair("zcviewingkey", viewing_hex)); return result; } From d5d2fb71cfe83e54a79d388bed783fc30fa7eda7 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 15 Jun 2016 16:15:54 -0600 Subject: [PATCH 5/8] Guarantee first two bytes of spending key are SK --- src/chainparams.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 8246a566f..ac0c71adf 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -107,7 +107,8 @@ public: base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container >(); // guarantees the first two characters, when base58 encoded, are "zc" base58Prefixes[ZCPAYMENT_ADDRRESS] = {22,154}; - base58Prefixes[ZCSPENDING_KEY] = {22,180}; + // guarantees the first two characters, when base58 encoded, are "SK" + base58Prefixes[ZCSPENDING_KEY] = {171,54}; vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -186,7 +187,7 @@ public: base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); base58Prefixes[ZCPAYMENT_ADDRRESS] = {22,155}; - base58Prefixes[ZCSPENDING_KEY] = {22,181}; + base58Prefixes[ZCSPENDING_KEY] = {171,55}; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); From 13039c5362f0c0070dbab914d6f84c3369fd89c9 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 15 Jun 2016 16:22:36 -0600 Subject: [PATCH 6/8] Make testnet addresses always start with 'tn'. --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ac0c71adf..ed935218f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -186,7 +186,7 @@ public: base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); - base58Prefixes[ZCPAYMENT_ADDRRESS] = {22,155}; + base58Prefixes[ZCPAYMENT_ADDRRESS] = {20,81}; base58Prefixes[ZCSPENDING_KEY] = {171,55}; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); From f0ae0b11fe5600fcd8677b3b6d2f83179fb48a50 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 15 Jun 2016 16:25:21 -0600 Subject: [PATCH 7/8] Add test to ensure spending keys always encode with 'SK' at beginning. --- src/test/key_tests.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index f15226ea5..329883b29 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -198,6 +198,10 @@ BOOST_AUTO_TEST_CASE(zc_address_test) { CZCSpendingKey spendingkey(sk); string sk_string = spendingkey.ToString(); + + BOOST_CHECK(sk_string[0] == 'S'); + BOOST_CHECK(sk_string[1] == 'K'); + CZCSpendingKey spendingkey2(sk_string); SpendingKey sk2 = spendingkey2.Get(); BOOST_CHECK(sk.inner() == sk2.inner()); From 05a6d19a543af45dd780ce15ea6090bb9cadd135 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 16 Jun 2016 12:16:49 -0600 Subject: [PATCH 8/8] Testnet spending keys should start with 'TK'. --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ed935218f..0e457c18c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -187,7 +187,7 @@ public: base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); base58Prefixes[ZCPAYMENT_ADDRRESS] = {20,81}; - base58Prefixes[ZCSPENDING_KEY] = {171,55}; + base58Prefixes[ZCSPENDING_KEY] = {177,235}; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));