Browse Source

Switch to Ed25519 for cryptographic binding of joinsplits to transactions.

pull/4/head
Sean Bowe 8 years ago
parent
commit
320f2cc7e0
  1. 17
      src/main.cpp
  2. 4
      src/primitives/transaction.cpp
  3. 19
      src/primitives/transaction.h
  4. 72
      src/pubkey.h
  5. 5
      src/script/interpreter.cpp
  6. 1002
      src/test/data/sighash.json
  7. 21
      src/test/sighash_tests.cpp
  8. 23
      src/test/transaction_tests.cpp
  9. 29
      src/wallet/rpcwallet.cpp

17
src/main.cpp

@ -5,6 +5,8 @@
#include "main.h"
#include "sodium.h"
#include "addrman.h"
#include "alert.h"
#include "arith_uint256.h"
@ -967,19 +969,20 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
REJECT_INVALID, "error-computing-signature-hash");
}
// Verify the signature
if (!tx.joinSplitPubKey.Verify(dataToBeSigned, tx.joinSplitSig)) {
return state.DoS(100, error("CheckTransaction(): JoinSplit signature does not verify"),
if (crypto_sign_verify_detached(&tx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
tx.joinSplitPubKey.begin()
) != 0) {
return state.DoS(100, error("CheckTransaction(): invalid joinsplit signature"),
REJECT_INVALID, "invalid-joinsplit-signature");
}
// Ensure that zk-SNARKs verify
uint256 pubKeyHash = tx.joinSplitPubKey.GetZcashHash();
if (state.PerformPourVerification()) {
// Ensure that zk-SNARKs verify
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
if (!pour.Verify(*pzcashParams, pubKeyHash)) {
if (!pour.Verify(*pzcashParams, tx.joinSplitPubKey)) {
return state.DoS(100, error("CheckTransaction(): pour does not verify"),
REJECT_INVALID, "bad-txns-pour-verification-failed");
REJECT_INVALID, "bad-txns-pour-verification-failed");
}
}
}

4
src/primitives/transaction.cpp

@ -134,8 +134,8 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
*const_cast<std::vector<CTxOut>*>(&vout) = tx.vout;
*const_cast<unsigned int*>(&nLockTime) = tx.nLockTime;
*const_cast<std::vector<CPourTx>*>(&vpour) = tx.vpour;
*const_cast<CCompressedPubKey*>(&joinSplitPubKey) = tx.joinSplitPubKey;
*const_cast<std::vector<unsigned char>*>(&joinSplitSig) = tx.joinSplitSig;
*const_cast<uint256*>(&joinSplitPubKey) = tx.joinSplitPubKey;
*const_cast<joinsplit_sig_t*>(&joinSplitSig) = tx.joinSplitSig;
*const_cast<uint256*>(&hash) = tx.hash;
return *this;
}

19
src/primitives/transaction.h

@ -10,7 +10,6 @@
#include "script/script.h"
#include "serialize.h"
#include "uint256.h"
#include "pubkey.h"
#include <boost/array.hpp>
@ -291,6 +290,8 @@ private:
void UpdateHash() const;
public:
typedef boost::array<unsigned char, 64> joinsplit_sig_t;
static const int32_t CURRENT_VERSION=1;
// The local variables are made const to prevent unintended modification
@ -303,10 +304,8 @@ public:
const std::vector<CTxOut> vout;
const uint32_t nLockTime;
const std::vector<CPourTx> 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<unsigned char> joinSplitSig;
const uint256 joinSplitPubKey;
const joinsplit_sig_t joinSplitSig;
/** Construct a CTransaction that qualifies as IsNull() */
CTransaction();
@ -328,8 +327,8 @@ public:
if (nVersion >= 2) {
READWRITE(*const_cast<std::vector<CPourTx>*>(&vpour));
if (vpour.size() > 0) {
READWRITE(*const_cast<CCompressedPubKey*>(&joinSplitPubKey));
READWRITE(*const_cast<std::vector<unsigned char>*>(&joinSplitSig));
READWRITE(*const_cast<uint256*>(&joinSplitPubKey));
READWRITE(*const_cast<joinsplit_sig_t*>(&joinSplitSig));
}
}
if (ser_action.ForRead())
@ -384,10 +383,8 @@ struct CMutableTransaction
std::vector<CTxOut> vout;
uint32_t nLockTime;
std::vector<CPourTx> 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<unsigned char> joinSplitSig;
uint256 joinSplitPubKey;
boost::array<unsigned char, 64> joinSplitSig;
CMutableTransaction();
CMutableTransaction(const CTransaction& tx);

72
src/pubkey.h

@ -9,7 +9,6 @@
#include "hash.h"
#include "serialize.h"
#include "uint256.h"
#include "sodium.h"
#include <stdexcept>
#include <vector>
@ -188,77 +187,6 @@ 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 <typename Stream>
void Serialize(Stream& s, int nType, int nVersion) const
{
unsigned int len = pubKey.size();
s.write((char*)pubKey.begin(), len);
}
template <typename Stream>
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;
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;
}
bool Verify(const uint256& hash, const std::vector<unsigned char>& vchSig) const
{
// TODO: make sure to check the s < 0xffff.... value thing etc.
// TODO: use compact signatures (maybe just use the secp256k1 API
// instead of these classes).
return pubKey.Verify(hash, vchSig);
}
};
struct CExtPubKey {
unsigned char nDepth;
unsigned char vchFingerprint[4];

5
src/script/interpreter.cpp

@ -14,8 +14,6 @@
#include "script/script.h"
#include "uint256.h"
#include "sodium.h"
using namespace std;
typedef vector<unsigned char> valtype;
@ -1085,7 +1083,8 @@ public:
::Serialize(s, txTo.vpour, nType, nVersion);
if (txTo.vpour.size() > 0) {
::Serialize(s, txTo.joinSplitPubKey, nType, nVersion);
std::vector<unsigned char> nullSig = {};
boost::array<unsigned char, 64> nullSig = {};
::Serialize(s, nullSig, nType, nVersion);
}
}

1002
src/test/data/sighash.json

File diff suppressed because one or more lines are too long

21
src/test/sighash_tests.cpp

@ -13,7 +13,6 @@
#include "util.h"
#include "version.h"
#include "sodium.h"
#include "key.h"
#include <iostream>
@ -82,7 +81,8 @@ uint256 static SignatureHashOld(CScript scriptCode, const CTransaction& txTo, un
txTmp.vin.resize(1);
}
txTmp.joinSplitSig = {};
// Blank out the joinsplit signature.
memset(&txTmp.joinSplitSig[0], 0, 64);
// Serialize and hash
CHashWriter ss(SER_GETHASH, 0);
@ -143,20 +143,21 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle) {
tx.vpour.push_back(pourtx);
}
CKey joinSplitPrivKey;
joinSplitPrivKey.MakeNewKey(true);
CCompressedPubKey joinSplitPubKey(joinSplitPrivKey.GetPubKey());
tx.joinSplitPubKey = joinSplitPubKey;
CTransaction signTx(tx);
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(tx.joinSplitPubKey.begin(), joinSplitPrivKey);
// TODO: #966
// TODO: #966.
static const uint256 one(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
// Empty output script.
CScript scriptCode;
CTransaction signTx(tx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
BOOST_CHECK(dataToBeSigned != one);
// Add the signature
joinSplitPrivKey.Sign(dataToBeSigned, tx.joinSplitSig);
assert(crypto_sign_detached(&tx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey
) == 0);
}
}

23
src/test/transaction_tests.cpp

@ -16,6 +16,8 @@
#include "script/script_error.h"
#include "primitives/transaction.h"
#include "sodium.h"
#include <map>
#include <string>
@ -379,6 +381,9 @@ BOOST_AUTO_TEST_CASE(test_simple_pour_invalidity)
CMutableTransaction newTx(tx);
CValidationState state;
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(newTx.joinSplitPubKey.begin(), joinSplitPrivKey);
state.SetPerformPourVerification(false); // don't verify the snark
// No pours, vin and vout, means it should be invalid.
@ -399,22 +404,18 @@ BOOST_AUTO_TEST_CASE(test_simple_pour_invalidity)
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "invalid-joinsplit-signature");
CKey joinSplitPrivKey;
joinSplitPrivKey.MakeNewKey(true);
CCompressedPubKey joinSplitPubKey(joinSplitPrivKey.GetPubKey());
newTx.joinSplitPubKey = joinSplitPubKey;
CTransaction signTx(newTx);
// TODO: #966
// TODO: #966.
static const uint256 one(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
// Empty output script.
CScript scriptCode;
CTransaction signTx(newTx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
BOOST_CHECK(dataToBeSigned != one);
// Add the signature
joinSplitPrivKey.Sign(dataToBeSigned, newTx.joinSplitSig);
assert(crypto_sign_detached(&newTx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey
) == 0);
BOOST_CHECK(CheckTransaction(newTx, state));
}

29
src/wallet/rpcwallet.cpp

@ -18,9 +18,10 @@
#include "walletdb.h"
#include "primitives/transaction.h"
#include "zcbenchmarks.h"
#include "key.h"
#include "script/interpreter.h"
#include "sodium.h"
#include <stdint.h>
#include <boost/assign/list_of.hpp>
@ -2652,22 +2653,23 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp)
throw runtime_error("unsupported pour input/output counts");
}
CKey joinSplitPrivKey;
joinSplitPrivKey.MakeNewKey(true);
CCompressedPubKey joinSplitPubKey(joinSplitPrivKey.GetPubKey());
uint256 joinSplitPubKey;
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey);
CMutableTransaction mtx(tx);
mtx.nVersion = 2;
mtx.joinSplitPubKey = joinSplitPubKey;
CPourTx pourtx(*pzcashParams,
joinSplitPubKey.GetZcashHash(),
joinSplitPubKey,
anchor,
{vpourin[0], vpourin[1]},
{vpourout[0], vpourout[1]},
vpub_old,
vpub_new);
assert(pourtx.Verify(*pzcashParams, joinSplitPubKey.GetZcashHash()));
assert(pourtx.Verify(*pzcashParams, joinSplitPubKey));
mtx.vpour.push_back(pourtx);
@ -2682,7 +2684,16 @@ Value zc_raw_pour(const json_spirit::Array& params, bool fHelp)
}
// Add the signature
joinSplitPrivKey.Sign(dataToBeSigned, mtx.joinSplitSig);
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey
) == 0);
// Sanity check
assert(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
mtx.joinSplitPubKey.begin()
) == 0);
CTransaction rawTx(mtx);
@ -2696,7 +2707,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, joinSplitPubKey.GetZcashHash());
ss2 << pourtx.h_sig(*pzcashParams, joinSplitPubKey);
encryptedBucket1 = HexStr(ss2.begin(), ss2.end());
}
@ -2705,7 +2716,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, joinSplitPubKey.GetZcashHash());
ss2 << pourtx.h_sig(*pzcashParams, joinSplitPubKey);
encryptedBucket2 = HexStr(ss2.begin(), ss2.end());
}

Loading…
Cancel
Save