Auto merge of #5215 - str4d:4983-zip-244, r=str4d
ZIP 244 transaction and signature digests Closes zcash/zcash#4983.
This commit is contained in:
commit
3f50ffc3f8
|
@ -1,3 +1,10 @@
|
|||
# "Coins" view
|
||||
|
||||
TBD
|
||||
|
||||
## Notes
|
||||
|
||||
- This is the main context in which `CTxOut::IsNull()` is used. The other is a
|
||||
single spot in the mempool code. Once we've backported the
|
||||
[per-txout CoinsDB](https://github.com/bitcoin/bitcoin/pull/10195) we can
|
||||
hopefully eliminate this method.
|
||||
|
|
|
@ -36,6 +36,14 @@ TEST(CheckBlock, VersionTooLow) {
|
|||
}
|
||||
|
||||
|
||||
// Subclass of CTransaction which doesn't call UpdateHash when constructing
|
||||
// from a CMutableTransaction. This enables us to create a CTransaction
|
||||
// with bad values which normally trigger an exception during construction.
|
||||
class UNSAFE_CTransaction : public CTransaction {
|
||||
public:
|
||||
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
|
||||
};
|
||||
|
||||
// Test that a Sprout tx with negative version is still rejected
|
||||
// by CheckBlock under Sprout consensus rules.
|
||||
TEST(CheckBlock, BlockSproutRejectsBadVersion) {
|
||||
|
@ -55,7 +63,8 @@ TEST(CheckBlock, BlockSproutRejectsBadVersion) {
|
|||
mtx.nVersion = -1;
|
||||
mtx.nVersionGroupId = 0;
|
||||
|
||||
CTransaction tx {mtx};
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx {mtx};
|
||||
CBlock block;
|
||||
block.vtx.push_back(tx);
|
||||
|
||||
|
|
|
@ -11,6 +11,14 @@
|
|||
#include <librustzcash.h>
|
||||
#include <rust/ed25519.h>
|
||||
|
||||
// Subclass of CTransaction which doesn't call UpdateHash when constructing
|
||||
// from a CMutableTransaction. This enables us to create a CTransaction
|
||||
// with bad values which normally trigger an exception during construction.
|
||||
class UNSAFE_CTransaction : public CTransaction {
|
||||
public:
|
||||
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
|
||||
};
|
||||
|
||||
TEST(ChecktransactionTests, CheckVpubNotBothNonzero) {
|
||||
CMutableTransaction tx;
|
||||
tx.nVersion = 2;
|
||||
|
@ -124,7 +132,8 @@ TEST(ChecktransactionTests, BadVersionTooLow) {
|
|||
CMutableTransaction mtx = GetValidTransaction();
|
||||
mtx.nVersion = 0;
|
||||
|
||||
CTransaction tx(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false)).Times(1);
|
||||
CheckTransactionWithoutProofVerification(tx, state);
|
||||
|
@ -273,7 +282,8 @@ TEST(ChecktransactionTests, BadTxnsVoutNegative) {
|
|||
CMutableTransaction mtx = GetValidTransaction();
|
||||
mtx.vout[0].nValue = -1;
|
||||
|
||||
CTransaction tx(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative", false)).Times(1);
|
||||
|
@ -284,7 +294,8 @@ TEST(ChecktransactionTests, BadTxnsVoutToolarge) {
|
|||
CMutableTransaction mtx = GetValidTransaction();
|
||||
mtx.vout[0].nValue = MAX_MONEY + 1;
|
||||
|
||||
CTransaction tx(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge", false)).Times(1);
|
||||
|
@ -787,7 +798,8 @@ TEST(ChecktransactionTests, SproutTxVersionTooLow) {
|
|||
mtx.fOverwintered = false;
|
||||
mtx.nVersion = -1;
|
||||
|
||||
CTransaction tx(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false)).Times(1);
|
||||
CheckTransactionWithoutProofVerification(tx, state);
|
||||
|
@ -795,14 +807,6 @@ TEST(ChecktransactionTests, SproutTxVersionTooLow) {
|
|||
|
||||
|
||||
|
||||
// Subclass of CTransaction which doesn't call UpdateHash when constructing
|
||||
// from a CMutableTransaction. This enables us to create a CTransaction
|
||||
// with bad values which normally trigger an exception during construction.
|
||||
class UNSAFE_CTransaction : public CTransaction {
|
||||
public:
|
||||
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
|
||||
};
|
||||
|
||||
TEST(ChecktransactionTests, SaplingSproutInputSumsTooLarge) {
|
||||
CMutableTransaction mtx = GetValidTransaction();
|
||||
mtx.vJoinSplit.resize(0);
|
||||
|
@ -866,6 +870,7 @@ TEST(ChecktransactionTests, OverwinterVersionNumberLow) {
|
|||
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
|
||||
mtx.nExpiryHeight = 0;
|
||||
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-low", false)).Times(1);
|
||||
|
@ -884,6 +889,7 @@ TEST(ChecktransactionTests, OverwinterVersionNumberHigh) {
|
|||
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
|
||||
mtx.nExpiryHeight = 0;
|
||||
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-high", false)).Times(1);
|
||||
|
@ -903,6 +909,7 @@ TEST(ChecktransactionTests, OverwinterBadVersionGroupId) {
|
|||
mtx.nExpiryHeight = 0;
|
||||
mtx.nVersionGroupId = 0x12345678;
|
||||
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-version-group-id", false)).Times(1);
|
||||
|
@ -1250,7 +1257,8 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
|||
// out of range.
|
||||
{
|
||||
mtx.valueBalance = MAX_MONEY + 1;
|
||||
CTransaction tx(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
EXPECT_TRUE(tx.IsCoinBase());
|
||||
|
||||
MockCValidationState state;
|
||||
|
@ -1259,7 +1267,8 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
|||
}
|
||||
{
|
||||
mtx.valueBalance = -MAX_MONEY - 1;
|
||||
CTransaction tx(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx(mtx);
|
||||
EXPECT_TRUE(tx.IsCoinBase());
|
||||
|
||||
MockCValidationState state;
|
||||
|
|
|
@ -175,6 +175,14 @@ TEST(Mempool, SproutV3TxWhenOverwinterActive) {
|
|||
}
|
||||
|
||||
|
||||
// Subclass of CTransaction which doesn't call UpdateHash when constructing
|
||||
// from a CMutableTransaction. This enables us to create a CTransaction
|
||||
// with bad values which normally trigger an exception during construction.
|
||||
class UNSAFE_CTransaction : public CTransaction {
|
||||
public:
|
||||
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
|
||||
};
|
||||
|
||||
// Sprout transaction with negative version, rejected by the mempool in CheckTransaction
|
||||
// under Sprout consensus rules, should still be rejected under Overwinter consensus rules.
|
||||
// 1. fails CheckTransaction (specifically CheckTransactionWithoutProofVerification)
|
||||
|
@ -198,7 +206,8 @@ TEST(Mempool, SproutNegativeVersionTxWhenOverwinterActive) {
|
|||
mtx.nVersion = -3;
|
||||
EXPECT_EQ(mtx.nVersion, static_cast<int32_t>(0xfffffffd));
|
||||
|
||||
CTransaction tx1(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx1(mtx);
|
||||
EXPECT_EQ(tx1.nVersion, -3);
|
||||
|
||||
CValidationState state1;
|
||||
|
@ -215,7 +224,8 @@ TEST(Mempool, SproutNegativeVersionTxWhenOverwinterActive) {
|
|||
mtx.nVersion = static_cast<int32_t>((1 << 31) | 3);
|
||||
EXPECT_EQ(mtx.nVersion, static_cast<int32_t>(0x80000003));
|
||||
|
||||
CTransaction tx1(mtx);
|
||||
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
|
||||
UNSAFE_CTransaction tx1(mtx);
|
||||
EXPECT_EQ(tx1.nVersion, -2147483645);
|
||||
|
||||
CValidationState state1;
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
#include "utilstrencodings.h"
|
||||
#include "crypto/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
const unsigned char ZCASH_AUTH_DATA_HASH_PERSONALIZATION[BLAKE2bPersonalBytes] =
|
||||
{'Z','c','a','s','h','A','u','t','h','D','a','t','H','a','s','h'};
|
||||
|
||||
uint256 CBlockHeader::GetHash() const
|
||||
{
|
||||
return SerializeHash(*this);
|
||||
|
@ -109,6 +114,53 @@ uint256 CBlock::CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMer
|
|||
return hash;
|
||||
}
|
||||
|
||||
// Return 0 if x == 0, otherwise the smallest power of 2 greater than or equal to x.
|
||||
// Algorithm based on <https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2>.
|
||||
static uint64_t next_pow2(uint64_t x)
|
||||
{
|
||||
x -= 1;
|
||||
x |= (x >> 1);
|
||||
x |= (x >> 2);
|
||||
x |= (x >> 4);
|
||||
x |= (x >> 8);
|
||||
x |= (x >> 16);
|
||||
x |= (x >> 32);
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
uint256 CBlock::BuildAuthDataMerkleTree() const
|
||||
{
|
||||
std::vector<uint256> tree;
|
||||
auto perfectSize = next_pow2(vtx.size());
|
||||
assert((perfectSize & (perfectSize - 1)) == 0);
|
||||
size_t expectedSize = std::max(perfectSize*2, (uint64_t)1) - 1; // The total number of nodes.
|
||||
tree.reserve(expectedSize);
|
||||
|
||||
// Add the leaves to the tree. v1-v4 transactions will append empty leaves.
|
||||
for (auto &tx : vtx) {
|
||||
tree.push_back(tx.GetAuthDigest());
|
||||
}
|
||||
// Append empty leaves until we get a perfect tree.
|
||||
tree.insert(tree.end(), perfectSize - vtx.size(), uint256());
|
||||
assert(tree.size() == perfectSize);
|
||||
|
||||
int j = 0;
|
||||
for (int layerWidth = perfectSize; layerWidth > 1; layerWidth = layerWidth / 2) {
|
||||
for (int i = 0; i < layerWidth; i += 2) {
|
||||
CBLAKE2bWriter ss(SER_GETHASH, 0, ZCASH_AUTH_DATA_HASH_PERSONALIZATION);
|
||||
ss << tree[j + i];
|
||||
ss << tree[j + i + 1];
|
||||
tree.push_back(ss.GetHash());
|
||||
}
|
||||
|
||||
// Move to the next layer.
|
||||
j += layerWidth;
|
||||
}
|
||||
|
||||
assert(tree.size() == expectedSize);
|
||||
return (tree.empty() ? uint256() : tree.back());
|
||||
}
|
||||
|
||||
std::string CBlock::ToString() const
|
||||
{
|
||||
std::stringstream s;
|
||||
|
|
|
@ -134,6 +134,11 @@ public:
|
|||
|
||||
std::vector<uint256> GetMerkleBranch(int nIndex) const;
|
||||
static uint256 CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex);
|
||||
|
||||
// Build the authorizing data Merkle tree for this block and return its
|
||||
// root.
|
||||
uint256 BuildAuthDataMerkleTree() const;
|
||||
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "tinyformat.h"
|
||||
#include "utilstrencodings.h"
|
||||
|
||||
#include <rust/transaction.h>
|
||||
|
||||
SaplingBundle::SaplingBundle(
|
||||
const std::vector<SpendDescription>& vShieldedSpend,
|
||||
const std::vector<OutputDescription>& vShieldedOutput,
|
||||
|
@ -138,12 +140,48 @@ CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.n
|
|||
|
||||
uint256 CMutableTransaction::GetHash() const
|
||||
{
|
||||
return SerializeHash(*this);
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << *this;
|
||||
uint256 hash;
|
||||
if (!zcash_transaction_digests(
|
||||
reinterpret_cast<const unsigned char*>(ss.data()),
|
||||
ss.size(),
|
||||
hash.begin(),
|
||||
nullptr))
|
||||
{
|
||||
throw std::ios_base::failure("CMutableTransaction::GetHash: Invalid transaction format");
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint256 CMutableTransaction::GetAuthDigest() const
|
||||
{
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << *this;
|
||||
uint256 authDigest;
|
||||
if (!zcash_transaction_digests(
|
||||
reinterpret_cast<const unsigned char*>(ss.data()),
|
||||
ss.size(),
|
||||
nullptr,
|
||||
authDigest.begin()))
|
||||
{
|
||||
throw std::ios_base::failure("CMutableTransaction::GetAuthDigest: Invalid transaction format");
|
||||
}
|
||||
return authDigest;
|
||||
}
|
||||
|
||||
void CTransaction::UpdateHash() const
|
||||
{
|
||||
*const_cast<uint256*>(&hash) = SerializeHash(*this);
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << *this;
|
||||
if (!zcash_transaction_digests(
|
||||
reinterpret_cast<const unsigned char*>(ss.data()),
|
||||
ss.size(),
|
||||
const_cast<uint256*>(&hash)->begin(),
|
||||
const_cast<uint256*>(&authDigest)->begin()))
|
||||
{
|
||||
throw std::ios_base::failure("CTransaction::UpdateHash: Invalid transaction format");
|
||||
}
|
||||
}
|
||||
|
||||
CTransaction::CTransaction() : nVersion(CTransaction::SPROUT_MIN_CURRENT_VERSION),
|
||||
|
|
|
@ -714,6 +714,8 @@ private:
|
|||
|
||||
/** Memory only. */
|
||||
const uint256 hash;
|
||||
/** Memory only. */
|
||||
const uint256 authDigest;
|
||||
void UpdateHash() const;
|
||||
|
||||
protected:
|
||||
|
@ -899,6 +901,15 @@ public:
|
|||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authorizing data commitment for this transaction.
|
||||
*
|
||||
* For v1-v4 transactions, this returns the null hash (i.e. all-zeroes).
|
||||
*/
|
||||
const uint256& GetAuthDigest() const {
|
||||
return authDigest;
|
||||
}
|
||||
|
||||
uint32_t GetHeader() const {
|
||||
// When serializing v1 and v2, the 4 byte header is nVersion
|
||||
uint32_t header = this->nVersion;
|
||||
|
@ -1093,6 +1104,14 @@ struct CMutableTransaction
|
|||
* fly, as opposed to GetHash() in CTransaction, which uses a cached result.
|
||||
*/
|
||||
uint256 GetHash() const;
|
||||
|
||||
/** Compute the authentication digest of this CMutableTransaction. This is
|
||||
* computed on the fly, as opposed to GetAuthDigest() in CTransaction, which
|
||||
* uses a cached result.
|
||||
*
|
||||
* For v1-v4 transactions, this returns the null hash (i.e. all-zeroes).
|
||||
*/
|
||||
uint256 GetAuthDigest() const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_PRIMITIVES_TRANSACTION_H
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#ifndef ZCASH_RUST_INCLUDE_RUST_TEST_HARNESS_H
|
||||
#define ZCASH_RUST_INCLUDE_RUST_TEST_HARNESS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Generates a random Jubjub base field element.
|
||||
///
|
||||
/// `ret` must point to a 32-byte array.
|
||||
void zcash_test_harness_random_jubjub_base(
|
||||
unsigned char* ret);
|
||||
|
||||
/// Generates a random Jubjub point.
|
||||
///
|
||||
/// `ret` must point to a 32-byte array.
|
||||
void zcash_test_harness_random_jubjub_point(
|
||||
unsigned char* ret);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ZCASH_RUST_INCLUDE_RUST_TEST_HARNESS_H
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#ifndef ZCASH_RUST_INCLUDE_RUST_TRANSACTION_H
|
||||
#define ZCASH_RUST_INCLUDE_RUST_TRANSACTION_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct PrecomputedTxParts;
|
||||
typedef struct PrecomputedTxParts PrecomputedTxParts;
|
||||
|
||||
/// Calculates identifying and authorizing digests for the given transaction.
|
||||
///
|
||||
/// `txid_ret` and `authDigest_ret` must either be `nullptr` or point to a
|
||||
/// 32-byte array.
|
||||
///
|
||||
/// If either `txid_ret` or `authDigest_ret` is `nullptr`, the corresponding
|
||||
/// digest will not be calculated.
|
||||
///
|
||||
/// Returns `false` if the transaction is invalid.
|
||||
bool zcash_transaction_digests(
|
||||
const unsigned char* txBytes,
|
||||
size_t txBytes_len,
|
||||
unsigned char* txid_ret,
|
||||
unsigned char* authDigest_ret);
|
||||
|
||||
/// Precomputes data for calculating signature digests from the given
|
||||
/// transaction.
|
||||
///
|
||||
/// Please free this with `zcash_transaction_precomputed_free` when you are
|
||||
/// done.
|
||||
///
|
||||
/// Returns `nullptr` if the transaction is invalid, or a v1-v4 transaction format.
|
||||
PrecomputedTxParts* zcash_transaction_precomputed_init(
|
||||
const unsigned char* txBytes,
|
||||
size_t txBytes_len);
|
||||
|
||||
/// Frees a precomputed transaction from `zcash_transaction_precomputed_init`.
|
||||
void zcash_transaction_precomputed_free(PrecomputedTxParts* preTx);
|
||||
|
||||
/// Calculates a signature digest for the given transparent input.
|
||||
///
|
||||
/// `sighash_ret` must point to a 32-byte array.
|
||||
///
|
||||
/// Returns `false` if any of the parameters are invalid; in this case,
|
||||
/// `sighash_ret` will be unaltered.
|
||||
bool zcash_transaction_transparent_signature_digest(
|
||||
const PrecomputedTxParts* preTx,
|
||||
uint32_t sighashType,
|
||||
size_t index,
|
||||
const unsigned char* scriptCode,
|
||||
size_t scriptCode_len,
|
||||
int64_t value,
|
||||
unsigned char* sighash_ret);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ZCASH_RUST_INCLUDE_RUST_TRANSACTION_H
|
|
@ -70,6 +70,9 @@ mod streams_ffi;
|
|||
mod tracing_ffi;
|
||||
|
||||
mod orchard_ffi;
|
||||
mod transaction_ffi;
|
||||
|
||||
mod test_harness_ffi;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
use group::{ff::Field, Group, GroupEncoding};
|
||||
use jubjub::{ExtendedPoint, Fq};
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_test_harness_random_jubjub_base(ret: *mut [u8; 32]) {
|
||||
*unsafe { &mut *ret } = Fq::random(OsRng).to_bytes();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_test_harness_random_jubjub_point(ret: *mut [u8; 32]) {
|
||||
*unsafe { &mut *ret } = ExtendedPoint::random(OsRng).to_bytes();
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
use std::{ptr, slice};
|
||||
|
||||
use blake2b_simd::Hash;
|
||||
use libc::{c_uchar, size_t};
|
||||
use tracing::error;
|
||||
use zcash_primitives::{
|
||||
consensus::BranchId,
|
||||
legacy::Script,
|
||||
transaction::{
|
||||
components::Amount,
|
||||
sighash::{signature_hash, SignableInput},
|
||||
txid::TxIdDigester,
|
||||
Transaction, TxDigests, TxVersion,
|
||||
},
|
||||
};
|
||||
|
||||
/// Calculates identifying and authorizing digests for the given transaction.
|
||||
///
|
||||
/// If either `txid_ret` or `auth_digest_ret` is `nullptr`, the corresponding digest will
|
||||
/// not be calculated.
|
||||
///
|
||||
/// Returns `false` if the transaction is invalid.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_transaction_digests(
|
||||
tx_bytes: *const c_uchar,
|
||||
tx_bytes_len: size_t,
|
||||
txid_ret: *mut [u8; 32],
|
||||
auth_digest_ret: *mut [u8; 32],
|
||||
) -> bool {
|
||||
let tx_bytes = unsafe { slice::from_raw_parts(tx_bytes, tx_bytes_len) };
|
||||
|
||||
// We use a placeholder branch ID here, since it is not used for anything.
|
||||
let tx = match Transaction::read(tx_bytes, BranchId::Canopy) {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
error!("Failed to parse transaction: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(txid_ret) = unsafe { txid_ret.as_mut() } {
|
||||
*txid_ret = *tx.txid().as_ref();
|
||||
}
|
||||
if let Some(auth_digest_ret) = unsafe { auth_digest_ret.as_mut() } {
|
||||
match tx.version() {
|
||||
// Pre-NU5 transaction formats don't have authorizing data commitments; when
|
||||
// included in the authDataCommitment tree, they use the null hash.
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
|
||||
*auth_digest_ret = [0; 32]
|
||||
}
|
||||
_ => auth_digest_ret.copy_from_slice(tx.auth_commitment().as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub struct PrecomputedTxParts {
|
||||
tx: Transaction,
|
||||
txid_parts: TxDigests<Hash>,
|
||||
}
|
||||
|
||||
/// Precomputes the `TxDigest` struct for the given transaction.
|
||||
///
|
||||
/// Returns `nullptr` if the transaction is invalid, or a v1-v4 transaction format.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_transaction_precomputed_init(
|
||||
tx_bytes: *const c_uchar,
|
||||
tx_bytes_len: size_t,
|
||||
) -> *mut PrecomputedTxParts {
|
||||
let tx_bytes = unsafe { slice::from_raw_parts(tx_bytes, tx_bytes_len) };
|
||||
|
||||
// We use a placeholder branch ID here, since it is not used for anything.
|
||||
let tx = match Transaction::read(tx_bytes, BranchId::Canopy) {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
error!("Failed to parse transaction: {}", e);
|
||||
return ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
match tx.version() {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
|
||||
// We don't support these legacy transaction formats in this API.
|
||||
ptr::null_mut()
|
||||
}
|
||||
_ => {
|
||||
let txid_parts = tx.digest(TxIdDigester);
|
||||
Box::into_raw(Box::new(PrecomputedTxParts { tx, txid_parts }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees a precomputed transaction from `zcash_transaction_precomputed_init`.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_transaction_precomputed_free(precomputed_tx: *mut PrecomputedTxParts) {
|
||||
if !precomputed_tx.is_null() {
|
||||
drop(unsafe { Box::from_raw(precomputed_tx) });
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a signature digest for the given transaction.
|
||||
///
|
||||
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
|
||||
/// will be unaltered.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_transaction_transparent_signature_digest(
|
||||
precomputed_tx: *const PrecomputedTxParts,
|
||||
hash_type: u32,
|
||||
index: size_t,
|
||||
script_code: *const c_uchar,
|
||||
script_code_len: size_t,
|
||||
value: i64,
|
||||
sighash_ret: *mut [u8; 32],
|
||||
) -> bool {
|
||||
let precomputed_tx = if let Some(res) = unsafe { precomputed_tx.as_ref() } {
|
||||
res
|
||||
} else {
|
||||
error!("Invalid precomputed transaction");
|
||||
return false;
|
||||
};
|
||||
let script_code =
|
||||
match Script::read(unsafe { slice::from_raw_parts(script_code, script_code_len) }) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
error!("Invalid scriptCode: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let value = match Amount::from_i64(value) {
|
||||
Ok(res) => res,
|
||||
Err(()) => {
|
||||
error!("Invalid amount");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let signable_input = SignableInput::transparent(index, &script_code, value);
|
||||
|
||||
let sighash = match precomputed_tx.tx.version() {
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
|
||||
// We don't support these legacy transaction formats in this API.
|
||||
return false;
|
||||
}
|
||||
_ => signature_hash(
|
||||
&precomputed_tx.tx,
|
||||
hash_type,
|
||||
&signable_input,
|
||||
&precomputed_tx.txid_parts,
|
||||
),
|
||||
};
|
||||
|
||||
*unsafe { &mut *sighash_ret } = *sighash.as_ref();
|
||||
true
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
#include "uint256.h"
|
||||
|
||||
#include <librustzcash.h>
|
||||
#include <rust/transaction.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -1127,20 +1128,41 @@ uint256 GetShieldedOutputsHash(const CTransaction& txTo) {
|
|||
|
||||
} // anon namespace
|
||||
|
||||
PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo)
|
||||
PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo) :
|
||||
preTx(nullptr, zcash_transaction_precomputed_free)
|
||||
{
|
||||
hashPrevouts = GetPrevoutHash(txTo);
|
||||
hashSequence = GetSequenceHash(txTo);
|
||||
hashOutputs = GetOutputsHash(txTo);
|
||||
hashJoinSplits = GetJoinSplitsHash(txTo);
|
||||
hashShieldedSpends = GetShieldedSpendsHash(txTo);
|
||||
hashShieldedOutputs = GetShieldedOutputsHash(txTo);
|
||||
bool isOverwinterV3 =
|
||||
txTo.fOverwintered &&
|
||||
txTo.nVersionGroupId == OVERWINTER_VERSION_GROUP_ID &&
|
||||
txTo.nVersion == OVERWINTER_TX_VERSION;
|
||||
bool isSaplingV4 =
|
||||
txTo.fOverwintered &&
|
||||
txTo.nVersionGroupId == SAPLING_VERSION_GROUP_ID &&
|
||||
txTo.nVersion == SAPLING_TX_VERSION;
|
||||
|
||||
if (!txTo.fOverwintered || isOverwinterV3 || isSaplingV4) {
|
||||
hashPrevouts = GetPrevoutHash(txTo);
|
||||
hashSequence = GetSequenceHash(txTo);
|
||||
hashOutputs = GetOutputsHash(txTo);
|
||||
hashJoinSplits = GetJoinSplitsHash(txTo);
|
||||
hashShieldedSpends = GetShieldedSpendsHash(txTo);
|
||||
hashShieldedOutputs = GetShieldedOutputsHash(txTo);
|
||||
} else {
|
||||
// TODO: If we already have this serialized, use it.
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << txTo;
|
||||
preTx.reset(zcash_transaction_precomputed_init(
|
||||
reinterpret_cast<const unsigned char*>(ss.data()),
|
||||
ss.size()));
|
||||
}
|
||||
}
|
||||
|
||||
SigVersion SignatureHashVersion(const CTransaction& txTo)
|
||||
{
|
||||
if (txTo.fOverwintered) {
|
||||
if (txTo.nVersionGroupId == SAPLING_VERSION_GROUP_ID) {
|
||||
if (txTo.nVersionGroupId == ZIP225_VERSION_GROUP_ID) {
|
||||
return SIGVERSION_ZIP244;
|
||||
} else if (txTo.nVersionGroupId == SAPLING_VERSION_GROUP_ID) {
|
||||
return SIGVERSION_SAPLING;
|
||||
} else {
|
||||
return SIGVERSION_OVERWINTER;
|
||||
|
@ -1166,7 +1188,42 @@ uint256 SignatureHash(
|
|||
|
||||
auto sigversion = SignatureHashVersion(txTo);
|
||||
|
||||
if (sigversion == SIGVERSION_OVERWINTER || sigversion == SIGVERSION_SAPLING) {
|
||||
if (sigversion == SIGVERSION_ZIP244) {
|
||||
// The consensusBranchId parameter is ignored; we use the value stored
|
||||
// in the transaction itself.
|
||||
if (nIn == NOT_AN_INPUT) {
|
||||
// The signature digest is just the txid! No need to cross the FFI.
|
||||
return txTo.GetHash();
|
||||
} else {
|
||||
CDataStream sScriptCode(SER_NETWORK, PROTOCOL_VERSION);
|
||||
sScriptCode << *(CScriptBase*)(&scriptCode);
|
||||
|
||||
if (cache) {
|
||||
uint256 hash;
|
||||
zcash_transaction_transparent_signature_digest(
|
||||
cache->preTx.get(),
|
||||
nHashType,
|
||||
nIn,
|
||||
reinterpret_cast<const unsigned char*>(sScriptCode.data()),
|
||||
sScriptCode.size(),
|
||||
amount,
|
||||
hash.begin());
|
||||
return hash;
|
||||
} else {
|
||||
PrecomputedTransactionData local(txTo);
|
||||
uint256 hash;
|
||||
zcash_transaction_transparent_signature_digest(
|
||||
local.preTx.get(),
|
||||
nHashType,
|
||||
nIn,
|
||||
reinterpret_cast<const unsigned char*>(sScriptCode.data()),
|
||||
sScriptCode.size(),
|
||||
amount,
|
||||
hash.begin());
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
} else if (sigversion == SIGVERSION_OVERWINTER || sigversion == SIGVERSION_SAPLING) {
|
||||
uint256 hashPrevouts;
|
||||
uint256 hashSequence;
|
||||
uint256 hashOutputs;
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "script_error.h"
|
||||
#include "primitives/transaction.h"
|
||||
|
||||
#include <rust/transaction.h>
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
@ -93,6 +95,8 @@ bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned i
|
|||
struct PrecomputedTransactionData
|
||||
{
|
||||
uint256 hashPrevouts, hashSequence, hashOutputs, hashJoinSplits, hashShieldedSpends, hashShieldedOutputs;
|
||||
/** Precomputed transaction parts. */
|
||||
std::unique_ptr<PrecomputedTxParts, decltype(&zcash_transaction_precomputed_free)> preTx;
|
||||
|
||||
PrecomputedTransactionData(const CTransaction& tx);
|
||||
};
|
||||
|
@ -102,6 +106,7 @@ enum SigVersion
|
|||
SIGVERSION_SPROUT = 0,
|
||||
SIGVERSION_OVERWINTER = 1,
|
||||
SIGVERSION_SAPLING = 2,
|
||||
SIGVERSION_ZIP244 = 3,
|
||||
};
|
||||
|
||||
uint256 SignatureHash(
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -65,6 +65,10 @@ BOOST_DATA_TEST_CASE(multisig_verify, boost::unit_test::data::xrange(static_cast
|
|||
txFrom.vout[0].scriptPubKey = a_and_b;
|
||||
txFrom.vout[1].scriptPubKey = a_or_b;
|
||||
txFrom.vout[2].scriptPubKey = escrow;
|
||||
// Meaningless values, but we need them for the Rust code to parse this.
|
||||
txFrom.vout[0].nValue = 10;
|
||||
txFrom.vout[1].nValue = 10;
|
||||
txFrom.vout[2].nValue = 10;
|
||||
|
||||
CMutableTransaction txTo[3]; // Spending transaction
|
||||
for (int i = 0; i < 3; i++)
|
||||
|
@ -205,6 +209,10 @@ BOOST_DATA_TEST_CASE(multisig_Sign, boost::unit_test::data::xrange(static_cast<i
|
|||
txFrom.vout[0].scriptPubKey = a_and_b;
|
||||
txFrom.vout[1].scriptPubKey = a_or_b;
|
||||
txFrom.vout[2].scriptPubKey = escrow;
|
||||
// Meaningless values, but we need them for the Rust code to parse this.
|
||||
txFrom.vout[0].nValue = 10;
|
||||
txFrom.vout[1].nValue = 10;
|
||||
txFrom.vout[2].nValue = 10;
|
||||
|
||||
CMutableTransaction txTo[3]; // Spending transaction
|
||||
for (int i = 0; i < 3; i++)
|
||||
|
|
|
@ -36,6 +36,8 @@ Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, Scri
|
|||
CMutableTransaction txFrom;
|
||||
txFrom.vout.resize(1);
|
||||
txFrom.vout[0].scriptPubKey = scriptPubKey;
|
||||
// Meaningless value, but we need it for the Rust code to parse this.
|
||||
txFrom.vout[0].nValue = 10;
|
||||
|
||||
CMutableTransaction txTo;
|
||||
txTo.vin.resize(1);
|
||||
|
@ -336,6 +338,8 @@ BOOST_DATA_TEST_CASE(AreInputsStandard, boost::unit_test::data::xrange(static_ca
|
|||
CMutableTransaction txTo;
|
||||
txTo.vout.resize(1);
|
||||
txTo.vout[0].scriptPubKey = GetScriptForDestination(key[1].GetPubKey().GetID());
|
||||
// Meaningless value, but we need it for the Rust code to parse this.
|
||||
txTo.vout[0].nValue = 10;
|
||||
|
||||
txTo.vin.resize(5);
|
||||
for (int i = 0; i < 5; i++)
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <rust/ed25519.h>
|
||||
#include <rust/test_harness.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
|
@ -116,7 +117,7 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle, uint32_t co
|
|||
tx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
|
||||
tx.nVersion = overwinter_version_dist(rng);
|
||||
}
|
||||
tx.nExpiryHeight = (insecure_rand() % 2) ? insecure_rand() : 0;
|
||||
tx.nExpiryHeight = (insecure_rand() % 2) ? insecure_rand() % TX_EXPIRY_HEIGHT_THRESHOLD : 0;
|
||||
} else {
|
||||
tx.nVersion = insecure_rand() & 0x7FFFFFFF;
|
||||
}
|
||||
|
@ -149,18 +150,18 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle, uint32_t co
|
|||
tx.valueBalance = insecure_rand() % 100000000;
|
||||
for (int spend = 0; spend < shielded_spends; spend++) {
|
||||
SpendDescription sdesc;
|
||||
sdesc.cv = GetRandHash();
|
||||
sdesc.anchor = GetRandHash();
|
||||
zcash_test_harness_random_jubjub_point(sdesc.cv.begin());
|
||||
zcash_test_harness_random_jubjub_base(sdesc.anchor.begin());
|
||||
sdesc.nullifier = GetRandHash();
|
||||
sdesc.rk = GetRandHash();
|
||||
zcash_test_harness_random_jubjub_point(sdesc.rk.begin());
|
||||
GetRandBytes(sdesc.zkproof.begin(), sdesc.zkproof.size());
|
||||
tx.vShieldedSpend.push_back(sdesc);
|
||||
}
|
||||
for (int out = 0; out < shielded_outs; out++) {
|
||||
OutputDescription odesc;
|
||||
odesc.cv = GetRandHash();
|
||||
odesc.cmu = GetRandHash();
|
||||
odesc.ephemeralKey = GetRandHash();
|
||||
zcash_test_harness_random_jubjub_point(odesc.cv.begin());
|
||||
zcash_test_harness_random_jubjub_base(odesc.cmu.begin());
|
||||
zcash_test_harness_random_jubjub_point(odesc.ephemeralKey.begin());
|
||||
GetRandBytes(odesc.encCiphertext.begin(), odesc.encCiphertext.size());
|
||||
GetRandBytes(odesc.outCiphertext.begin(), odesc.outCiphertext.size());
|
||||
GetRandBytes(odesc.zkproof.begin(), odesc.zkproof.size());
|
||||
|
@ -228,7 +229,8 @@ BOOST_AUTO_TEST_CASE(sighash_test)
|
|||
#endif
|
||||
for (int i=0; i<nRandomTests; i++) {
|
||||
int nHashType = insecure_rand();
|
||||
uint32_t consensusBranchId = NetworkUpgradeInfo[insecure_rand() % Consensus::MAX_NETWORK_UPGRADES].nBranchId;
|
||||
// Exclude ZFUTURE as its branch ID can't be represented as a JSON int32
|
||||
uint32_t consensusBranchId = NetworkUpgradeInfo[insecure_rand() % (Consensus::MAX_NETWORK_UPGRADES - 1)].nBranchId;
|
||||
CMutableTransaction txTo;
|
||||
RandomTransaction(txTo, (nHashType & 0x1f) == SIGHASH_SINGLE, consensusBranchId);
|
||||
CScript scriptCode;
|
||||
|
@ -291,7 +293,8 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
|
|||
raw_script = test[1].get_str();
|
||||
nIn = test[2].get_int();
|
||||
nHashType = test[3].get_int();
|
||||
consensusBranchId = test[4].get_int();
|
||||
// JSON integers are signed, so parse uint32 as int64
|
||||
consensusBranchId = test[4].get_int64();
|
||||
sigHashHex = test[5].get_str();
|
||||
|
||||
uint256 sh;
|
||||
|
@ -323,6 +326,9 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
|
|||
|
||||
std::vector<unsigned char> raw = ParseHex(raw_script);
|
||||
scriptCode.insert(scriptCode.end(), raw.begin(), raw.end());
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_ERROR("Bad test, couldn't deserialize data: " << strTest << ": " << ex.what());
|
||||
continue;
|
||||
} catch (...) {
|
||||
BOOST_ERROR("Bad test, couldn't deserialize data: " << strTest);
|
||||
continue;
|
||||
|
|
|
@ -43,6 +43,14 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
// Subclass of CTransaction which doesn't call UpdateHash when constructing
|
||||
// from a CMutableTransaction. This enables us to create a CTransaction
|
||||
// with bad values which normally trigger an exception during construction.
|
||||
class UNSAFE_CTransaction : public CTransaction {
|
||||
public:
|
||||
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(transaction_tests, JoinSplitTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tx_valid)
|
||||
|
@ -188,7 +196,12 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
|
|||
string transaction = test[1].get_str();
|
||||
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
|
||||
CTransaction tx;
|
||||
stream >> tx;
|
||||
try {
|
||||
stream >> tx;
|
||||
} catch (std::ios_base::failure) {
|
||||
// Invalid transaction was caught at parse time by the Rust logic.
|
||||
continue;
|
||||
}
|
||||
|
||||
CValidationState state;
|
||||
fValid = CheckTransaction(tx, state, verifier) && state.IsValid();
|
||||
|
@ -463,23 +476,27 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
JSDescription *jsdesc = &newTx.vJoinSplit[0];
|
||||
jsdesc->vpub_old = -1;
|
||||
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-negative");
|
||||
|
||||
jsdesc->vpub_old = MAX_MONEY + 1;
|
||||
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-toolarge");
|
||||
|
||||
jsdesc->vpub_old = 0;
|
||||
jsdesc->vpub_new = -1;
|
||||
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-negative");
|
||||
|
||||
jsdesc->vpub_new = MAX_MONEY + 1;
|
||||
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-toolarge");
|
||||
|
||||
jsdesc->vpub_new = (MAX_MONEY / 2) + 10;
|
||||
|
@ -595,6 +612,8 @@ BOOST_DATA_TEST_CASE(test_Get, boost::unit_test::data::xrange(static_cast<int>(C
|
|||
t1.vout.resize(2);
|
||||
t1.vout[0].nValue = 90*CENT;
|
||||
t1.vout[0].scriptPubKey << OP_1;
|
||||
// Meaningless value, but we need it for the Rust code to parse this.
|
||||
t1.vout[1].nValue = CENT;
|
||||
|
||||
BOOST_CHECK(AreInputsStandard(t1, coins, consensusBranchId));
|
||||
BOOST_CHECK_EQUAL(coins.GetValueIn(t1), (50+21+22)*CENT);
|
||||
|
@ -765,11 +784,14 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
|
|||
|
||||
// TX_NULL_DATA w/o PUSHDATA
|
||||
t.vout.resize(1);
|
||||
t.vout[0].nValue = 0; // Needed for Rust parser
|
||||
t.vout[0].scriptPubKey = CScript() << OP_RETURN;
|
||||
BOOST_CHECK(IsStandardTx(t, reason, chainparams));
|
||||
|
||||
// Only one TX_NULL_DATA permitted in all cases
|
||||
t.vout.resize(2);
|
||||
t.vout[0].nValue = 0; // Needed for Rust parser
|
||||
t.vout[1].nValue = 0; // Needed for Rust parser
|
||||
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38");
|
||||
t.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38");
|
||||
BOOST_CHECK(!IsStandardTx(t, reason, chainparams));
|
||||
|
@ -868,6 +890,85 @@ BOOST_AUTO_TEST_CASE(TxV5)
|
|||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << tx;
|
||||
BOOST_CHECK_EQUAL(HexStr(ss.begin(), ss.end()), transaction);
|
||||
|
||||
// ZIP 244: Check the transaction digests.
|
||||
BOOST_CHECK_EQUAL(tx.GetHash().GetHex(), test[1].getValStr());
|
||||
BOOST_CHECK_EQUAL(tx.GetAuthDigest().GetHex(), test[2].getValStr());
|
||||
|
||||
// ZIP 244: Check the signature digests.
|
||||
unsigned int nIn = NOT_AN_INPUT;
|
||||
if (!test[3].isNull()) {
|
||||
nIn = test[3].get_int();
|
||||
}
|
||||
|
||||
CScript scriptCode;
|
||||
if (!test[4].isNull()) {
|
||||
auto scriptCodeBytes = ParseHex(test[4].get_str());
|
||||
scriptCode = CScript(scriptCodeBytes.begin(), scriptCodeBytes.end());
|
||||
}
|
||||
|
||||
CAmount amount;
|
||||
if (!test[5].isNull()) {
|
||||
amount = test[5].get_int64();
|
||||
}
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
SignatureHash(
|
||||
scriptCode, tx, nIn,
|
||||
SIGHASH_ALL,
|
||||
amount, tx.GetConsensusBranchId()
|
||||
).GetHex(),
|
||||
test[6].getValStr());
|
||||
|
||||
if (!test[7].isNull()) {
|
||||
BOOST_CHECK_EQUAL(
|
||||
SignatureHash(
|
||||
scriptCode, tx, nIn,
|
||||
SIGHASH_NONE,
|
||||
amount, tx.GetConsensusBranchId()
|
||||
).GetHex(),
|
||||
test[7].getValStr());
|
||||
}
|
||||
|
||||
if (!test[8].isNull()) {
|
||||
BOOST_CHECK_EQUAL(
|
||||
SignatureHash(
|
||||
scriptCode, tx, nIn,
|
||||
SIGHASH_SINGLE,
|
||||
amount, tx.GetConsensusBranchId()
|
||||
).GetHex(),
|
||||
test[8].getValStr());
|
||||
}
|
||||
|
||||
if (!test[9].isNull()) {
|
||||
BOOST_CHECK_EQUAL(
|
||||
SignatureHash(
|
||||
scriptCode, tx, nIn,
|
||||
SIGHASH_ALL | SIGHASH_ANYONECANPAY,
|
||||
amount, tx.GetConsensusBranchId()
|
||||
).GetHex(),
|
||||
test[9].getValStr());
|
||||
}
|
||||
|
||||
if (!test[10].isNull()) {
|
||||
BOOST_CHECK_EQUAL(
|
||||
SignatureHash(
|
||||
scriptCode, tx, nIn,
|
||||
SIGHASH_NONE | SIGHASH_ANYONECANPAY,
|
||||
amount, tx.GetConsensusBranchId()
|
||||
).GetHex(),
|
||||
test[10].getValStr());
|
||||
}
|
||||
|
||||
if (!test[11].isNull()) {
|
||||
BOOST_CHECK_EQUAL(
|
||||
SignatureHash(
|
||||
scriptCode, tx, nIn,
|
||||
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
|
||||
amount, tx.GetConsensusBranchId()
|
||||
).GetHex(),
|
||||
test[11].getValStr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include "zcash/Note.hpp"
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
|
||||
#include <rust/test_harness.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
using ::testing::Return;
|
||||
|
@ -1665,6 +1667,7 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
|
|||
auto wtxTmp = GetValidSproutSpend(sk2, note, 5);
|
||||
CMutableTransaction mtx {wtxTmp};
|
||||
mtx.vout[0].scriptPubKey = scriptPubKey;
|
||||
mtx.vout[0].nValue = CENT;
|
||||
CWalletTx wtxSproutTransparent {nullptr, mtx};
|
||||
wallet.AddToWallet(wtxSproutTransparent, true, nullptr);
|
||||
|
||||
|
@ -1674,7 +1677,7 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
|
|||
mtxSapling.nVersion = SAPLING_TX_VERSION;
|
||||
mtxSapling.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
|
||||
mtxSapling.vShieldedOutput.resize(1);
|
||||
mtxSapling.vShieldedOutput[0].cv = libzcash::random_uint256();
|
||||
zcash_test_harness_random_jubjub_point(mtxSapling.vShieldedOutput[0].cv.begin());
|
||||
CWalletTx wtxSapling {nullptr, mtxSapling};
|
||||
SetSaplingNoteData(wtxSapling);
|
||||
wallet.AddToWallet(wtxSapling, true, nullptr);
|
||||
|
@ -1685,7 +1688,7 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
|
|||
mtxSaplingTransparent.nVersion = SAPLING_TX_VERSION;
|
||||
mtxSaplingTransparent.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
|
||||
mtxSaplingTransparent.vShieldedOutput.resize(1);
|
||||
mtxSaplingTransparent.vShieldedOutput[0].cv = libzcash::random_uint256();
|
||||
zcash_test_harness_random_jubjub_point(mtxSaplingTransparent.vShieldedOutput[0].cv.begin());
|
||||
CWalletTx wtxSaplingTransparent {nullptr, mtxSaplingTransparent};
|
||||
wallet.AddToWallet(wtxSaplingTransparent, true, nullptr);
|
||||
|
||||
|
|
Loading…
Reference in New Issue