diff --git a/src/Makefile.am b/src/Makefile.am index 4af45a0cd..e36cea3d6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -108,7 +108,8 @@ LIBZCASH_H = \ zcash/prf.h \ zcash/Proof.hpp \ zcash/util.h \ - zcash/Zcash.h + zcash/Zcash.h \ + zcash/zip32.h .PHONY: FORCE collate-libsnark check-symbols check-security # bitcoin core # @@ -520,6 +521,7 @@ libzcash_a_SOURCES = \ zcash/Note.cpp \ zcash/prf.cpp \ zcash/util.cpp \ + zcash/zip32.cpp \ zcash/circuit/commitment.tcc \ zcash/circuit/gadget.tcc \ zcash/circuit/merkle.tcc \ diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index 65e067b81..6b52eff36 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -44,7 +44,8 @@ zcash_gtest_SOURCES += \ gtest/test_proofs.cpp \ gtest/test_paymentdisclosure.cpp \ gtest/test_pedersen_hash.cpp \ - gtest/test_checkblock.cpp + gtest/test_checkblock.cpp \ + gtest/test_zip32.cpp if ENABLE_WALLET zcash_gtest_SOURCES += \ wallet/gtest/test_wallet.cpp diff --git a/src/gtest/test_zip32.cpp b/src/gtest/test_zip32.cpp new file mode 100644 index 000000000..d93ff2492 --- /dev/null +++ b/src/gtest/test_zip32.cpp @@ -0,0 +1,134 @@ +#include +#include + +#include + +// From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/sapling_zip32.py +// Sapling consistently uses little-endian encoding, but uint256S takes its input in +// big-endian byte order, so the test vectors below are byte-reversed. +TEST(ZIP32, TestVectors) { + std::vector> rawSeed { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + HDSeed seed(rawSeed); + + auto m = libzcash::SaplingExtendedSpendingKey::Master(seed); + EXPECT_EQ(m.depth, 0); + EXPECT_EQ(m.parentFVKTag, 0); + EXPECT_EQ(m.childIndex, 0); + EXPECT_EQ( + m.chaincode, + uint256S("8e661820750d557e8b34733ebf7ecdfdf31c6d27724fb47aa372bf034b7c94d0")); + EXPECT_EQ( + m.expsk.ask, + uint256S("06257454c907f6510ba1c1830ebf60657760a8869ee968a2b93260d3930cc0b6")); + EXPECT_EQ( + m.expsk.nsk, + uint256S("06ea21888a749fd38eb443d20a030abd2e6e997f5db4f984bd1f2f3be8ed0482")); + EXPECT_EQ( + m.expsk.ovk, + uint256S("21fb4adfa42183848306ffb27719f27d76cf9bb81d023c93d4b9230389845839")); + EXPECT_EQ( + m.dk, + uint256S("72a196f93e8abc0935280ea2a96fa57d6024c9913e0f9fb3af96775bb77cc177")); + EXPECT_THAT( + m.ToXFVK().DefaultAddress().d, + testing::ElementsAreArray({ 0xd8, 0x62, 0x1b, 0x98, 0x1c, 0xf3, 0x00, 0xe9, 0xd4, 0xcc, 0x89 })); + + auto m_1 = m.Derive(1); + EXPECT_EQ(m_1.depth, 1); + EXPECT_EQ(m_1.parentFVKTag, 0x3a71c214); + EXPECT_EQ(m_1.childIndex, 1); + EXPECT_EQ( + m_1.chaincode, + uint256S("e6bcda05678a43fad229334ef0b795a590e7c50590baf0d9b9031a690c114701")); + EXPECT_EQ( + m_1.expsk.ask, + uint256S("0c357a2655b4b8d761794095df5cb402d3ba4a428cf6a88e7c2816a597c12b28")); + EXPECT_EQ( + m_1.expsk.nsk, + uint256S("01ba6bff1018fd4eac04da7e3f2c6be9c229e662c5c4d1d6fc1ecafd8829a3e7")); + EXPECT_EQ( + m_1.expsk.ovk, + uint256S("7474a4c518551bd82f14a7f7365a8ffa403c50cfeffedf026ada8688fc81135f")); + EXPECT_EQ( + m_1.dk, + uint256S("dcb4c170d878510e96c4a74192d7eecde9c9912b00b99a12ec91d7a232e84de0")); + EXPECT_THAT( + m_1.ToXFVK().DefaultAddress().d, + testing::ElementsAreArray({ 0x8b, 0x41, 0x38, 0x32, 0x0d, 0xfa, 0xfd, 0x7b, 0x39, 0x97, 0x81 })); + + auto m_1_2h = m_1.Derive(2 | ZIP32_HARDENED_KEY_LIMIT); + EXPECT_EQ(m_1_2h.depth, 2); + EXPECT_EQ(m_1_2h.parentFVKTag, 0x079e99db); + EXPECT_EQ(m_1_2h.childIndex, 2 | ZIP32_HARDENED_KEY_LIMIT); + EXPECT_EQ( + m_1_2h.chaincode, + uint256S("35d4a883737742ca41a4baa92323bdb3c93dcb3b462a26b039971bedf415ce97")); + EXPECT_EQ( + m_1_2h.expsk.ask, + uint256S("0dc6e4fe846bda925c82e632980434e17b51dac81fc4821fa71334ee3c11e88b")); + EXPECT_EQ( + m_1_2h.expsk.nsk, + uint256S("0c99a63a275c1c66734761cfb9c62fe9bd1b953f579123d3d0e769c59d057837")); + EXPECT_EQ( + m_1_2h.expsk.ovk, + uint256S("bc1328fc5eb693e18875c5149d06953b11d39447ebd6e38c023c22962e1881cf")); + EXPECT_EQ( + m_1_2h.dk, + uint256S("377bb062dce7e0dcd8a0054d0ca4b4d1481b3710bfa1df12ca46ff9e9fa1eda3")); + EXPECT_THAT( + m_1_2h.ToXFVK().DefaultAddress().d, + testing::ElementsAreArray({ 0xe8, 0xd0, 0x37, 0x93, 0xcd, 0xd2, 0xba, 0xcc, 0x9c, 0x70, 0x41 })); + + auto m_1_2hv = m_1_2h.ToXFVK(); + EXPECT_EQ(m_1_2hv.depth, 2); + EXPECT_EQ(m_1_2hv.parentFVKTag, 0x079e99db); + EXPECT_EQ(m_1_2hv.childIndex, 2 | ZIP32_HARDENED_KEY_LIMIT); + EXPECT_EQ( + m_1_2hv.chaincode, + uint256S("35d4a883737742ca41a4baa92323bdb3c93dcb3b462a26b039971bedf415ce97")); + EXPECT_EQ( + m_1_2hv.fvk.ak, + uint256S("4138cffdf7200e52d4e9f4384481b4a4c4d070493a5e401e4ffa850f5a92c5a6")); + EXPECT_EQ( + m_1_2hv.fvk.nk, + uint256S("11eee22577304f660cc036bc84b3fc88d1ec50ae8a4d657beb6b211659304e30")); + EXPECT_EQ( + m_1_2hv.fvk.ovk, + uint256S("bc1328fc5eb693e18875c5149d06953b11d39447ebd6e38c023c22962e1881cf")); + EXPECT_EQ( + m_1_2hv.dk, + uint256S("377bb062dce7e0dcd8a0054d0ca4b4d1481b3710bfa1df12ca46ff9e9fa1eda3")); + EXPECT_EQ(m_1_2hv.DefaultAddress(), m_1_2h.ToXFVK().DefaultAddress()); + + // Hardened derivation from an xfvk fails + EXPECT_FALSE(m_1_2hv.Derive(3 | ZIP32_HARDENED_KEY_LIMIT)); + + // Non-hardened derivation succeeds + auto maybe_m_1_2hv_3 = m_1_2hv.Derive(3); + EXPECT_TRUE(maybe_m_1_2hv_3); + + auto m_1_2hv_3 = maybe_m_1_2hv_3.get(); + EXPECT_EQ(m_1_2hv_3.depth, 3); + EXPECT_EQ(m_1_2hv_3.parentFVKTag, 0x7583c148); + EXPECT_EQ(m_1_2hv_3.childIndex, 3); + EXPECT_EQ( + m_1_2hv_3.chaincode, + uint256S("e8e7d6a74a5a1c05be41baec7998d91f7b3603a4c0af495b0d43ba81cf7b938d")); + EXPECT_EQ( + m_1_2hv_3.fvk.ak, + uint256S("a3a697bdda9d648d32a97553de4754b2fac866d726d3f2c436259c507bc585b1")); + EXPECT_EQ( + m_1_2hv_3.fvk.nk, + uint256S("4f66c0814b769963f3bf1bc001270b50edabb27de042fc8a5607d2029e0488db")); + EXPECT_EQ( + m_1_2hv_3.fvk.ovk, + uint256S("f61a699934dc78441324ef628b4b4721611571e8ee3bd591eb3d4b1cfae0b969")); + EXPECT_EQ( + m_1_2hv_3.dk, + uint256S("6ee53b1261f2c9c0f7359ab236f87b52a0f1b0ce43305cdad92ebb63c350cbbe")); + EXPECT_THAT( + m_1_2hv_3.DefaultAddress().d, + testing::ElementsAreArray({ 0x03, 0x0f, 0xfb, 0x26, 0x3a, 0x93, 0x9e, 0x23, 0x0e, 0x96, 0xdd })); +} diff --git a/src/uint256.h b/src/uint256.h index b3a6cb242..f22a8bafa 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -88,6 +88,15 @@ public: } }; +/** 88-bit opaque blob. + */ +class blob88 : public base_blob<88> { +public: + blob88() {} + blob88(const base_blob<88>& b) : base_blob<88>(b) {} + explicit blob88(const std::vector& vch) : base_blob<88>(vch) {} +}; + /** 160-bit opaque blob. * @note This type is called uint160 for historical reasons only. It is an opaque * blob of 160 bits and has no integer operations. diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index f73e585cf..00b8290d2 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -19,6 +19,9 @@ const size_t SerializedSproutPaymentAddressSize = 64; const size_t SerializedSproutViewingKeySize = 64; const size_t SerializedSproutSpendingKeySize = 32; +const size_t SerializedSaplingPaymentAddressSize = 43; +const size_t SerializedSaplingFullViewingKeySize = 96; +const size_t SerializedSaplingExpandedSpendingKeySize = 96; const size_t SerializedSaplingSpendingKeySize = 32; typedef std::array diversifier_t; diff --git a/src/zcash/zip32.cpp b/src/zcash/zip32.cpp new file mode 100644 index 000000000..43529525e --- /dev/null +++ b/src/zcash/zip32.cpp @@ -0,0 +1,139 @@ +// Copyright (c) 2018 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zip32.h" + +#include "hash.h" +#include "random.h" +#include "streams.h" +#include "version.h" + +#include +#include + +const unsigned char ZCASH_HD_SEED_FP_PERSONAL[crypto_generichash_blake2b_PERSONALBYTES] = + {'Z', 'c', 'a', 's', 'h', '_', 'H', 'D', '_', 'S', 'e', 'e', 'd', '_', 'F', 'P'}; + +HDSeed HDSeed::Random(size_t len) +{ + assert(len >= 32); + RawHDSeed rawSeed(len, 0); + GetRandBytes(rawSeed.data(), len); + return HDSeed(rawSeed); +} + +uint256 HDSeed::Fingerprint() const +{ + CBLAKE2bWriter h(SER_GETHASH, 0, ZCASH_HD_SEED_FP_PERSONAL); + h << seed; + return h.GetHash(); +} + +namespace libzcash { + +boost::optional SaplingExtendedFullViewingKey::Derive(uint32_t i) const +{ + CDataStream ss_p(SER_NETWORK, PROTOCOL_VERSION); + ss_p << *this; + CSerializeData p_bytes(ss_p.begin(), ss_p.end()); + + CSerializeData i_bytes(ZIP32_XFVK_SIZE); + if (librustzcash_zip32_xfvk_derive( + reinterpret_cast(p_bytes.data()), + i, + reinterpret_cast(i_bytes.data()) + )) { + CDataStream ss_i(i_bytes, SER_NETWORK, PROTOCOL_VERSION); + SaplingExtendedFullViewingKey xfvk_i; + ss_i >> xfvk_i; + return xfvk_i; + } else { + return boost::none; + } +} + +boost::optional> + SaplingExtendedFullViewingKey::Address(diversifier_index_t j) const +{ + CDataStream ss_xfvk(SER_NETWORK, PROTOCOL_VERSION); + ss_xfvk << *this; + CSerializeData xfvk_bytes(ss_xfvk.begin(), ss_xfvk.end()); + + diversifier_index_t j_ret; + CSerializeData addr_bytes(libzcash::SerializedSaplingPaymentAddressSize); + if (librustzcash_zip32_xfvk_address( + reinterpret_cast(xfvk_bytes.data()), + j.begin(), j_ret.begin(), + reinterpret_cast(addr_bytes.data()))) { + CDataStream ss_addr(addr_bytes, SER_NETWORK, PROTOCOL_VERSION); + libzcash::SaplingPaymentAddress addr; + ss_addr >> addr; + return std::make_pair(j_ret, addr); + } else { + return boost::none; + } +} + +libzcash::SaplingPaymentAddress SaplingExtendedFullViewingKey::DefaultAddress() const +{ + diversifier_index_t j0; + auto addr = Address(j0); + // If we can't obtain a default address, we are *very* unlucky... + if (!addr) { + throw std::runtime_error("SaplingExtendedFullViewingKey::DefaultAddress(): No valid diversifiers out of 2^88!"); + } + return addr.get().second; +} + +SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Master(const HDSeed& seed) +{ + auto rawSeed = seed.RawSeed(); + CSerializeData m_bytes(ZIP32_XSK_SIZE); + librustzcash_zip32_xsk_master( + rawSeed.data(), + rawSeed.size(), + reinterpret_cast(m_bytes.data())); + + CDataStream ss(m_bytes, SER_NETWORK, PROTOCOL_VERSION); + SaplingExtendedSpendingKey xsk_m; + ss >> xsk_m; + return xsk_m; +} + +SaplingExtendedSpendingKey SaplingExtendedSpendingKey::Derive(uint32_t i) const +{ + CDataStream ss_p(SER_NETWORK, PROTOCOL_VERSION); + ss_p << *this; + CSerializeData p_bytes(ss_p.begin(), ss_p.end()); + + CSerializeData i_bytes(ZIP32_XSK_SIZE); + librustzcash_zip32_xsk_derive( + reinterpret_cast(p_bytes.data()), + i, + reinterpret_cast(i_bytes.data())); + + CDataStream ss_i(i_bytes, SER_NETWORK, PROTOCOL_VERSION); + SaplingExtendedSpendingKey xsk_i; + ss_i >> xsk_i; + return xsk_i; +} + +SaplingExtendedFullViewingKey SaplingExtendedSpendingKey::ToXFVK() const +{ + SaplingExtendedFullViewingKey ret; + ret.depth = depth; + ret.parentFVKTag = parentFVKTag; + ret.childIndex = childIndex; + ret.chaincode = chaincode; + ret.fvk = expsk.full_viewing_key(); + ret.dk = dk; + return ret; +} + +libzcash::SaplingPaymentAddress SaplingExtendedSpendingKey::DefaultAddress() const +{ + return ToXFVK().DefaultAddress(); +} + +} diff --git a/src/zcash/zip32.h b/src/zcash/zip32.h new file mode 100644 index 000000000..9dce9e203 --- /dev/null +++ b/src/zcash/zip32.h @@ -0,0 +1,121 @@ +// Copyright (c) 2018 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ZCASH_ZIP32_H +#define ZCASH_ZIP32_H + +#include "serialize.h" +#include "support/allocators/secure.h" +#include "uint256.h" +#include "zcash/Address.hpp" + +#include + +const uint32_t ZIP32_HARDENED_KEY_LIMIT = 0x80000000; +const size_t ZIP32_XFVK_SIZE = 169; +const size_t ZIP32_XSK_SIZE = 169; + +typedef std::vector> RawHDSeed; + +class HDSeed { +private: + RawHDSeed seed; + +public: + HDSeed() {} + HDSeed(RawHDSeed& seedIn) : seed(seedIn) {} + + static HDSeed Random(size_t len = 32); + bool IsNull() const { return seed.empty(); }; + uint256 Fingerprint() const; + RawHDSeed RawSeed() const { return seed; } + + friend bool operator==(const HDSeed& a, const HDSeed& b) + { + return a.seed == b.seed; + } + + friend bool operator!=(const HDSeed& a, const HDSeed& b) + { + return !(a == b); + } +}; + +namespace libzcash { + +typedef blob88 diversifier_index_t; + +struct SaplingExtendedFullViewingKey { + uint8_t depth; + uint32_t parentFVKTag; + uint32_t childIndex; + uint256 chaincode; + libzcash::SaplingFullViewingKey fvk; + uint256 dk; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(depth); + READWRITE(parentFVKTag); + READWRITE(childIndex); + READWRITE(chaincode); + READWRITE(fvk); + READWRITE(dk); + } + + boost::optional Derive(uint32_t i) const; + + // Returns the first index starting from j that generates a valid + // payment address, along with the corresponding address. Returns + // an error if the diversifier space is exhausted. + boost::optional> + Address(diversifier_index_t j) const; + + libzcash::SaplingPaymentAddress DefaultAddress() const; +}; + +struct SaplingExtendedSpendingKey { + uint8_t depth; + uint32_t parentFVKTag; + uint32_t childIndex; + uint256 chaincode; + libzcash::SaplingExpandedSpendingKey expsk; + uint256 dk; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(depth); + READWRITE(parentFVKTag); + READWRITE(childIndex); + READWRITE(chaincode); + READWRITE(expsk); + READWRITE(dk); + } + + static SaplingExtendedSpendingKey Master(const HDSeed& seed); + + SaplingExtendedSpendingKey Derive(uint32_t i) const; + + SaplingExtendedFullViewingKey ToXFVK() const; + + libzcash::SaplingPaymentAddress DefaultAddress() const; + + friend bool operator==(const SaplingExtendedSpendingKey& a, const SaplingExtendedSpendingKey& b) + { + return a.depth == b.depth && + a.parentFVKTag == b.parentFVKTag && + a.childIndex == b.childIndex && + a.chaincode == b.chaincode && + a.expsk == b.expsk && + a.dk == b.dk; + } +}; + +} + +#endif // ZCASH_ZIP32_H