diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 239c2ca8..75a96890 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -10,6 +10,7 @@ #include "script/script.h" #include "serialize.h" #include "uint256.h" +#include "pubkey.h" #include @@ -302,6 +303,10 @@ public: const std::vector vout; const uint32_t nLockTime; const std::vector vpour; + // TODO: This should be an unsigned char[33] (or boost array) + const CCompressedPubKey joinSplitPubKey; + // TODO: This should be an unsigned char[64] (or boost array) + const std::vector joinSplitSig; /** Construct a CTransaction that qualifies as IsNull() */ CTransaction(); @@ -322,6 +327,10 @@ public: READWRITE(*const_cast(&nLockTime)); if (nVersion >= 2) { READWRITE(*const_cast*>(&vpour)); + if (vpour.size() > 0) { + READWRITE(*const_cast(&joinSplitPubKey)); + READWRITE(*const_cast*>(&joinSplitSig)); + } } if (ser_action.ForRead()) UpdateHash(); @@ -375,6 +384,10 @@ struct CMutableTransaction std::vector vout; uint32_t nLockTime; std::vector vpour; + // TODO: This should be an unsigned char[33] (or boost array) + CCompressedPubKey joinSplitPubKey; + // TODO: This should be an unsigned char[64] (or boost array) + std::vector joinSplitSig; CMutableTransaction(); CMutableTransaction(const CTransaction& tx); @@ -390,6 +403,10 @@ struct CMutableTransaction READWRITE(nLockTime); if (nVersion >= 2) { READWRITE(vpour); + if (vpour.size() > 0) { + READWRITE(joinSplitPubKey); + READWRITE(joinSplitSig); + } } } diff --git a/src/pubkey.h b/src/pubkey.h index cce9c826..bee4dd99 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -9,6 +9,7 @@ #include "hash.h" #include "serialize.h" #include "uint256.h" +#include "sodium.h" #include #include @@ -187,6 +188,81 @@ public: bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; }; +class CCompressedPubKey { +private: + CPubKey pubKey; +public: + + CCompressedPubKey() + { + // pubKey's 0-argument constructor invalidates it. + } + + CCompressedPubKey(const CPubKey &pubKey) + { + this->pubKey = pubKey; + // TODO: check that it's compressed and valid and throw exception if + // not. + } + + unsigned int GetSerializeSize(int nType, int nVersion) const + { + assert(pubKey.size() == 33); + return pubKey.size(); + } + + template + void Serialize(Stream& s, int nType, int nVersion) const + { + unsigned int len = pubKey.size(); + assert(len == 33); + s.write((char*)pubKey.begin(), len); + } + + template + void Unserialize(Stream& s, int nType, int nVersion) + { + unsigned int len = 33; + s.read((char*)pubKey.begin(), len); + // TODO: check that it's compressed and valid. + } + + //! Get the 256-bit hash of this public key for the Zcash protocol. + uint256 GetZcashHash() const + { + // TODO: is the thing in vch actually the right thing to hash/encode? + + const unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] + = {'Z','c','a','s','h','E','C','D','S','A','P','u','b','K','e','y'}; + + uint256 hash; + assert(pubKey[0] == 2 || pubKey[0] == 3); + assert(pubKey.size() == 33); + if (crypto_generichash_blake2b_salt_personal(hash.begin(), 32, + pubKey.begin(), pubKey.size(), + NULL, 0, // No key. + NULL, // No salt. + personalization + ) != 0) + { + throw std::logic_error("hash function failure"); + } + + return hash; + } + + // TODO: implement this to verify the shorter kind of signature + // TODO: make sure to check the s value thing etc. + // TODO: this used to have "const" at the end, what does that mean?? + bool Verify(const uint256& hash, const std::vector& vchSig) + { + // TODO implement signature verification. + return false; + } + +}; + + struct CExtPubKey { unsigned char nDepth; unsigned char vchFingerprint[4]; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index ff7f8e05..209642d9 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -14,6 +14,8 @@ #include "script/script.h" #include "uint256.h" +#include "sodium.h" + using namespace std; typedef vector valtype; @@ -1030,6 +1032,7 @@ public: // Serialize the prevout ::Serialize(s, txTo.vin[nInput].prevout, nType, nVersion); // Serialize the script + assert(nInput != NOT_AN_INPUT); if (nInput != nIn) // Blank out other inputs' signatures ::Serialize(s, CScript(), nType, nVersion); @@ -1073,22 +1076,14 @@ public: // Serialize vpour if (txTo.nVersion >= 2) { - // TODO: // // SIGHASH_* functions will hash portions of // the transaction for use in signatures. This - // keeps the pour cryptographically bound to - // the transaction from the perspective of the - // inputs (but not from the perspective of the - // pour). + // keeps the JoinSplit cryptographically bound + // to the transaction. // - // This must be rectified in the future. - // See zcash/#529 - // - // It will be necessary to change this API to - // be abstract over whether an input script is - // being skipped or a pour is being skipped. ::Serialize(s, txTo.vpour, nType, nVersion); + ::Serialize(s, txTo.joinSplitPubKey, nType, nVersion); } } }; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 35d572f0..b94916fa 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -12,12 +12,16 @@ #include #include #include +#include class CPubKey; class CScript; class CTransaction; class uint256; +/** Special case nIn for signing JoinSplits. */ +const unsigned int NOT_AN_INPUT = UINT_MAX; + /** Signature hash types/flags */ enum { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 89867768..946199a5 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -18,6 +18,8 @@ #include "walletdb.h" #include "primitives/transaction.h" #include "zcbenchmarks.h" +#include "key.h" +#include "script/interpreter.h" #include @@ -2650,22 +2652,38 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp) throw runtime_error("unsupported pour input/output counts"); } - // TODO: #808 - uint256 pubKeyHash; + CKey joinSplitPrivKey; + joinSplitPrivKey.MakeNewKey(true); + CCompressedPubKey joinSplitPubKey(joinSplitPrivKey.GetPubKey()); + + CMutableTransaction mtx(tx); + mtx.nVersion = 2; + mtx.joinSplitPubKey = joinSplitPubKey; + CPourTx pourtx(*pzcashParams, - pubKeyHash, + joinSplitPubKey.GetZcashHash(), anchor, {vpourin[0], vpourin[1]}, {vpourout[0], vpourout[1]}, vpub_old, vpub_new); + assert(pourtx.Verify(*pzcashParams, joinSplitPubKey.GetZcashHash())); - assert(pourtx.Verify(*pzcashParams, pubKeyHash)); - - CMutableTransaction mtx(tx); - mtx.nVersion = 2; mtx.vpour.push_back(pourtx); + // TODO: #966. + static const uint256 one(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); + // Empty output script. + CScript scriptCode; + CTransaction signTx(mtx); + uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL); + if (dataToBeSigned == one) { + throw runtime_error("SignatureHash failed"); + } + + // Add the signature + joinSplitPrivKey.SignCompact(dataToBeSigned, mtx.joinSplitSig); + CTransaction rawTx(mtx); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); @@ -2678,7 +2696,7 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp) ss2 << ((unsigned char) 0x00); ss2 << pourtx.ephemeralKey; ss2 << pourtx.ciphertexts[0]; - ss2 << pourtx.h_sig(*pzcashParams, pubKeyHash); + ss2 << pourtx.h_sig(*pzcashParams, joinSplitPubKey.GetZcashHash()); encryptedBucket1 = HexStr(ss2.begin(), ss2.end()); } @@ -2687,7 +2705,7 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp) ss2 << ((unsigned char) 0x01); ss2 << pourtx.ephemeralKey; ss2 << pourtx.ciphertexts[1]; - ss2 << pourtx.h_sig(*pzcashParams, pubKeyHash); + ss2 << pourtx.h_sig(*pzcashParams, joinSplitPubKey.GetZcashHash()); encryptedBucket2 = HexStr(ss2.begin(), ss2.end()); } diff --git a/src/zcash/JoinSplit.cpp b/src/zcash/JoinSplit.cpp index 6f241b73..04cb7e00 100644 --- a/src/zcash/JoinSplit.cpp +++ b/src/zcash/JoinSplit.cpp @@ -301,7 +301,7 @@ uint256 JoinSplit::h_sig( const boost::array& nullifiers, const uint256& pubKeyHash ) { - unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] + const unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {'Z','c','a','s','h','C','o','m','p','u','t','e','h','S','i','g'}; std::vector block(randomSeed.begin(), randomSeed.end()); @@ -349,4 +349,4 @@ JSInput::JSInput() : witness(ZCIncrementalMerkleTree().witness()), template class JoinSplit; -} \ No newline at end of file +}