Update ZIP 244 implementation

This brings in the changes that align the transparent parts of ZIP 244
with BIP 341.
This commit is contained in:
Jack Grigg 2022-01-23 02:37:10 +00:00
parent 067b3b3032
commit 96d6ee0b8f
31 changed files with 455 additions and 131 deletions

View File

@ -3,7 +3,7 @@ replace-with = "vendored-sources"
[source."https://github.com/zcash/librustzcash.git"]
git = "https://github.com/zcash/librustzcash.git"
rev = "5622b060b1f57de7afc3d0b4e425b9b4b22482a0"
rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7"
replace-with = "vendored-sources"
[source.vendored-sources]

17
Cargo.lock generated
View File

@ -501,7 +501,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "equihash"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"blake2b_simd 1.0.0",
"byteorder",
@ -510,7 +510,7 @@ dependencies = [
[[package]]
name = "f4jumble"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"blake2b_simd 1.0.0",
]
@ -858,6 +858,7 @@ dependencies = [
"tracing-core",
"tracing-subscriber",
"zcash_address",
"zcash_encoding",
"zcash_history",
"zcash_note_encryption",
"zcash_primitives",
@ -1917,7 +1918,7 @@ dependencies = [
[[package]]
name = "zcash_address"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"bech32",
"bs58",
@ -1928,7 +1929,7 @@ dependencies = [
[[package]]
name = "zcash_encoding"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"byteorder",
"nonempty",
@ -1937,7 +1938,7 @@ dependencies = [
[[package]]
name = "zcash_history"
version = "0.2.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"bigint",
"blake2b_simd 1.0.0",
@ -1947,7 +1948,7 @@ dependencies = [
[[package]]
name = "zcash_note_encryption"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"chacha20",
"chacha20poly1305",
@ -1958,7 +1959,7 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"aes",
"bip0039",
@ -1994,7 +1995,7 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=ff243b4f0055d89d3abb526e234688a09c3d8cb7#ff243b4f0055d89d3abb526e234688a09c3d8cb7"
dependencies = [
"bellman",
"blake2b_simd 1.0.0",

View File

@ -45,6 +45,7 @@ tracing = "0.1"
tracing-core = "0.1"
tracing-appender = "0.2"
zcash_address = "0.0"
zcash_encoding = "0.0"
zcash_history = "0.2"
zcash_note_encryption = "0.1"
zcash_primitives = { version = "0.5", features = ["transparent-inputs"] }
@ -72,8 +73,9 @@ codegen-units = 1
[patch.crates-io]
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "ff243b4f0055d89d3abb526e234688a09c3d8cb7" }

View File

@ -48,7 +48,10 @@ static void ECDSA(benchmark::State& state)
mtx.vout[i].scriptPubKey = CScript() << OP_1;
}
const PrecomputedTransactionData txdata(mtx);
std::vector<CTxOut> allPrevOutputs;
// for benchmarking we simply use dummy inputs
allPrevOutputs.resize(mtx.vin.size());
const PrecomputedTransactionData txdata(mtx, allPrevOutputs);
// sign all inputs
for(uint32_t i = 0; i < mtx.vin.size(); i++) {

View File

@ -468,7 +468,11 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& strInput)
}
const CKeyStore& keystore = tempKeystore;
const PrecomputedTransactionData txdata(mergedTx);
// We don't support v5 transactions via this API yet.
if (mergedTx.nVersion >= ZIP225_TX_VERSION) {
throw std::runtime_error("v5+ transactions not supported yet");
}
const PrecomputedTransactionData txdata(mergedTx, {});
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);

View File

@ -113,7 +113,10 @@ void CreateJoinSplitSignature(CMutableTransaction& mtx, uint32_t consensusBranch
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
const PrecomputedTransactionData txdata(signTx);
// Fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(signTx.vin.size());
const PrecomputedTransactionData txdata(signTx, allPrevOutputs);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);
if (dataToBeSigned == one) {
throw std::runtime_error("SignatureHash failed");
@ -535,7 +538,11 @@ TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) {
CMutableTransaction mtx = GetValidTransaction();
mtx.joinSplitSig.bytes[0] += 1;
CTransaction tx(mtx);
const PrecomputedTransactionData txdata(tx);
// Recreate the fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(tx.vin.size());
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
// during initial block download, for transactions being accepted into the
@ -564,7 +571,11 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
// Create a valid transaction for the Sapling epoch.
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
CTransaction tx(mtx);
const PrecomputedTransactionData txdata(tx);
// Recreate the fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(tx.vin.size());
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
// Ensure that the transaction validates against Sapling.
@ -603,10 +614,14 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
// Recreate the fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(mtx.vin.size());
// Check that the signature is valid before we add L
{
CTransaction tx(mtx);
const PrecomputedTransactionData txdata(tx);
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, saplingBranchId, false, true));
}
@ -626,7 +641,7 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
}
CTransaction tx(mtx);
const PrecomputedTransactionData txdata(tx);
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
// during initial block download, for transactions being accepted into the
@ -1299,7 +1314,10 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
// Coinbase transaction does not pass shielded input checks, as bindingSig
// consensus rule is enforced.
PrecomputedTransactionData txdata(tx);
// - Note that coinbase txs don't have a previous output corresponding to
// their transparent input; ZIP 244 handles this by making the coinbase
// sighash the txid.
PrecomputedTransactionData txdata(tx, {});
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid", false, "")).Times(1);
ContextualCheckShieldedInputs(
tx, txdata, state, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true);

View File

@ -129,7 +129,8 @@ TEST(Validation, ContextualCheckInputsPassesWithCoinbase) {
for (int idx = Consensus::BASE_SPROUT; idx < Consensus::MAX_NETWORK_UPGRADES; idx++) {
auto consensusBranchId = NetworkUpgradeInfo[idx].nBranchId;
CValidationState state;
PrecomputedTransactionData txdata(tx);
// Coinbase transactions have one synthetic input with no prevout.
PrecomputedTransactionData txdata(tx, {});
EXPECT_TRUE(ContextualCheckInputs(tx, state, view, false, 0, false, txdata, Params(CBaseChainParams::MAIN).GetConsensus(), consensusBranchId));
}
}
@ -180,7 +181,7 @@ TEST(Validation, ContextualCheckInputsDetectsOldBranchId) {
// Ensure that the inputs validate against Overwinter.
CValidationState state;
PrecomputedTransactionData txdata(tx);
PrecomputedTransactionData txdata(tx, {CTxOut(coinValue, scriptPubKey)});
EXPECT_TRUE(ContextualCheckInputs(
tx, state, view, true, 0, false, txdata,
consensusParams, overwinterBranchId));

View File

@ -1974,7 +1974,11 @@ bool AcceptToMemoryPool(
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
PrecomputedTransactionData txdata(tx);
std::vector<CTxOut> allPrevOutputs;
for (const auto& input : tx.vin) {
allPrevOutputs.push_back(view.GetOutputFor(input));
}
PrecomputedTransactionData txdata(tx, allPrevOutputs);
if (!ContextualCheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true, txdata, chainparams.GetConsensus(), consensusBranchId))
{
return false;
@ -3190,6 +3194,13 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
return state.DoS(100, error("ConnectBlock(): too many sigops"),
REJECT_INVALID, "bad-blk-sigops");
// Coinbase transactions are the only case where this vector will not be the same
// length as `tx.vin` (since coinbase transactions have a single synthetic input).
// Only shielded coinbase transactions will need to produce sighashes for coinbase
// transactions; this is handled in ZIP 244 by having the coinbase sighash be the
// txid.
std::vector<CTxOut> allPrevOutputs;
if (!tx.IsCoinBase())
{
if (!view.HaveInputs(tx))
@ -3209,13 +3220,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
return state.DoS(100, false, rejectCode, rejectReason);
}
for (const auto& input : tx.vin) {
allPrevOutputs.push_back(view.GetOutputFor(input));
}
// insightexplorer
// https://github.com/bitpay/bitcoin/commit/017f548ea6d89423ef568117447e61dd5707ec42#diff-7ec3c68a81efff79b6ca22ac1f1eabbaR2597
if (fAddressIndex || fSpentIndex) {
for (size_t j = 0; j < tx.vin.size(); j++) {
const CTxIn input = tx.vin[j];
const CTxOut &prevout = view.GetOutputFor(tx.vin[j]);
const CTxOut &prevout = allPrevOutputs[j];
CScript::ScriptType scriptType = prevout.scriptPubKey.GetType();
const uint160 addrHash = prevout.scriptPubKey.AddressHash();
if (fAddressIndex && scriptType != CScript::UNKNOWN) {
@ -3250,7 +3265,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
REJECT_INVALID, "bad-blk-sigops");
}
txdata.emplace_back(tx);
txdata.emplace_back(tx, allPrevOutputs);
if (!tx.IsCoinBase())
{

View File

@ -219,7 +219,11 @@ public:
uint256 dataToBeSigned;
CScript scriptCode;
try {
PrecomputedTransactionData txdata(mtx);
// This is a shielded coinbase transaction, so the sighash is either pre-v5
// and doesn't use the allPrevOutputs field of PrecomputedTransactionData), or
// v5+ and S.2 of ZIP 244 defers to T.2, causing allPrevOutputs to be ignored.
// We therefore can set it to the empty list here.
PrecomputedTransactionData txdata(mtx, {});
dataToBeSigned = SignatureHash(
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()),
@ -532,11 +536,16 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const MinerAddre
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue;
std::vector<CTxOut> allPrevOutputs;
for (const auto& input : tx.vin) {
allPrevOutputs.push_back(view.GetOutputFor(input));
}
// Note that flags: we don't want to set mempool/IsStandard()
// policy here, but we still have to ensure that the block we
// create only contains transactions that are valid in new blocks.
CValidationState state;
PrecomputedTransactionData txdata(tx);
PrecomputedTransactionData txdata(tx, allPrevOutputs);
if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, chainparams.GetConsensus(), consensusBranchId))
continue;

View File

@ -1037,13 +1037,25 @@ UniValue signrawtransaction(const UniValue& params, bool fHelp)
}
}
std::vector<CTxOut> allPrevOutputs;
// We do not need to know the inputs for pre-v5 transactions.
// We can't sign v5+ transactions without knowing all inputs.
if (mergedTx.nVersion >= ZIP225_TX_VERSION) {
if (!view.HaveInputs(mergedTx)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot sign v5 transactions without knowing all inputs");
}
for (const auto& input : mergedTx.vin) {
allPrevOutputs.push_back(view.GetOutputFor(input));
}
}
// Script verification errors
UniValue vErrors(UniValue::VARR);
// Use CTransaction for the constant parts of the
// transaction to avoid rehashing.
const CTransaction txConst(mergedTx);
const PrecomputedTransactionData txdata(txConst);
const PrecomputedTransactionData txdata(txConst, allPrevOutputs);
// Sign what we can:
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
CTxIn& txin = mergedTx.vin[i];

View File

@ -39,24 +39,26 @@ bool zcash_transaction_digests(
/// 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);
size_t txBytes_len,
const unsigned char* allPrevOutputs,
size_t allPrevOutputsLen);
/// 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.
/// Calculates a ZIP 244 signature digest for the given transaction.
///
/// `index` must be an index into the transaction's `vin`, or `NOT_AN_INPUT` for
/// calculating the signature digest for shielded signatures.
///
/// `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(
bool zcash_transaction_zip244_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

View File

@ -1,16 +1,20 @@
use std::convert::TryInto;
use std::io::Cursor;
use std::{ptr, slice};
use blake2b_simd::Hash;
use libc::{c_uchar, size_t};
use tracing::error;
use zcash_encoding::Vector;
use zcash_primitives::{
consensus::BranchId,
legacy::Script,
transaction::{
components::Amount,
sighash::{signature_hash, SignableInput},
components::{orchard as orchard_serialization, sapling, transparent, Amount},
sighash::{SignableInput, TransparentAuthorizingContext},
sighash_v5::v5_signature_hash,
txid::TxIdDigester,
Transaction, TxDigests, TxVersion,
Authorization, Transaction, TransactionData, TxDigests, TxVersion,
},
};
@ -55,8 +59,97 @@ pub extern "C" fn zcash_transaction_digests(
true
}
#[derive(Clone, Debug)]
struct TransparentAuth {
all_prev_outputs: Vec<transparent::TxOut>,
}
impl transparent::Authorization for TransparentAuth {
type ScriptSig = Script;
}
impl TransparentAuthorizingContext for TransparentAuth {
fn input_amounts(&self) -> Vec<Amount> {
self.all_prev_outputs
.iter()
.map(|prevout| prevout.value)
.collect()
}
fn input_scriptpubkeys(&self) -> Vec<Script> {
self.all_prev_outputs
.iter()
.map(|prevout| prevout.script_pubkey.clone())
.collect()
}
}
struct MapTransparent {
auth: TransparentAuth,
}
impl transparent::MapAuth<transparent::Authorized, TransparentAuth> for MapTransparent {
fn map_script_sig(
&self,
s: <transparent::Authorized as transparent::Authorization>::ScriptSig,
) -> <TransparentAuth as transparent::Authorization>::ScriptSig {
s
}
fn map_authorization(&self, _: transparent::Authorized) -> TransparentAuth {
// TODO: This map should consume self, so we can move self.auth
self.auth.clone()
}
}
// TODO: Move these trait impls into `zcash_primitives` so they can be on `()`.
struct IdentityMap;
impl sapling::MapAuth<sapling::Authorized, sapling::Authorized> for IdentityMap {
fn map_proof(
&self,
p: <sapling::Authorized as sapling::Authorization>::Proof,
) -> <sapling::Authorized as sapling::Authorization>::Proof {
p
}
fn map_auth_sig(
&self,
s: <sapling::Authorized as sapling::Authorization>::AuthSig,
) -> <sapling::Authorized as sapling::Authorization>::AuthSig {
s
}
fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized {
a
}
}
impl orchard_serialization::MapAuth<orchard::bundle::Authorized, orchard::bundle::Authorized>
for IdentityMap
{
fn map_spend_auth(
&self,
s: <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth,
) -> <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth {
s
}
fn map_authorization(&self, a: orchard::bundle::Authorized) -> orchard::bundle::Authorized {
a
}
}
struct PrecomputedAuth;
impl Authorization for PrecomputedAuth {
type TransparentAuth = TransparentAuth;
type SaplingAuth = sapling::Authorized;
type OrchardAuth = orchard::bundle::Authorized;
}
pub struct PrecomputedTxParts {
tx: Transaction,
tx: TransactionData<PrecomputedAuth>,
txid_parts: TxDigests<Hash>,
}
@ -67,10 +160,25 @@ pub struct PrecomputedTxParts {
pub extern "C" fn zcash_transaction_precomputed_init(
tx_bytes: *const c_uchar,
tx_bytes_len: size_t,
all_prev_outputs: *const c_uchar,
all_prev_outputs_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.
//
// TODO: This is also parsing a transaction that may have partially-filled fields.
// This doesn't matter for transparent components (the only such field would be the
// scriptSig fields of transparent inputs, which would serialize as empty Scripts),
// but is ill-defined for shielded components (we'll be serializing 64 bytes of zeroes
// for each signature). This is an internal FFI so it's fine for now, but we should
// refactor the transaction builder (which is the only source of partially-created
// shielded components) to use a different FFI for obtaining the sighash, that passes
// across the transaction components and then constructs the TransactionData. This is
// already being done as part of the Orchard changes to the transaction builder, since
// the Orchard bundle will already be built on the Rust side, and we can avoid passing
// it back and forward across the FFI with this change. We should similarly refactor
// the Sapling code to do the same.
let tx = match Transaction::read(tx_bytes, BranchId::Canopy) {
Ok(tx) => tx,
Err(e) => {
@ -85,6 +193,28 @@ pub extern "C" fn zcash_transaction_precomputed_init(
ptr::null_mut()
}
_ => {
let all_prev_outputs =
unsafe { slice::from_raw_parts(all_prev_outputs, all_prev_outputs_len) };
let mut cursor = Cursor::new(all_prev_outputs);
let f_transparent = match Vector::read(&mut cursor, transparent::TxOut::read) {
Err(e) => {
error!("Invalid all_prev_outputs field: {}", e);
return ptr::null_mut();
}
Ok(_) if (cursor.position() as usize) != all_prev_outputs.len() => {
error!("all_prev_outputs had trailing data");
return ptr::null_mut();
}
Ok(all_prev_outputs) => MapTransparent {
auth: TransparentAuth { all_prev_outputs },
},
};
let tx = tx
.into_data()
.map_authorization(f_transparent, IdentityMap, IdentityMap);
let txid_parts = tx.digest(TxIdDigester);
Box::into_raw(Box::new(PrecomputedTxParts { tx, txid_parts }))
}
@ -99,18 +229,23 @@ pub extern "C" fn zcash_transaction_precomputed_free(precomputed_tx: *mut Precom
}
}
/// Calculates a signature digest for the given transaction.
/// This MUST match `NOT_AN_INPUT` in `src/script/interpreter.h`.
const NOT_AN_INPUT: usize = 0xffffffff;
/// Calculates a ZIP 244 signature digest for the given transaction.
///
/// `index` must be an index into the transaction's `vin`, or `NOT_AN_INPUT` for
/// calculating the signature digest for shielded signatures.
///
/// `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.
#[no_mangle]
pub extern "C" fn zcash_transaction_transparent_signature_digest(
pub extern "C" fn zcash_transaction_zip244_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() } {
@ -119,37 +254,56 @@ pub extern "C" fn zcash_transaction_transparent_signature_digest(
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);
if matches!(
precomputed_tx.tx.version(),
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling,
) {
error!("Cannot calculate ZIP 244 digest for pre-v5 transaction");
return false;
}
let signable_input = if index == NOT_AN_INPUT {
SignableInput::Shielded
} else {
let prevout = match precomputed_tx.tx.transparent_bundle() {
Some(bundle) => match bundle.authorization.all_prev_outputs.get(index) {
Some(prevout) => prevout,
None => {
error!("nIn out of range");
return false;
}
},
None => {
error!("Tried to create a transparent sighash for a tx without transparent data");
return false;
}
};
let value = match Amount::from_i64(value) {
Ok(res) => res,
Err(()) => {
error!("Invalid amount");
return false;
SignableInput::Transparent {
// This conversion to `u8` is always fine:
// - We only call this FFI method once we already know we are using ZIP 244.
// - Even if we weren't, `hash_type` is one byte tacked onto the end of a
// signature, so it always fits into a `u8` (and TBH I don't know why we
// ever set it to `u32`).
hash_type: hash_type.try_into().unwrap(),
index,
// `script_code` is unused by `v5_signature_hash`, so instead of passing the
// real `script_code` across the FFI (and paying the serialization and parsing
// cost for no benefit), we set it to the prevout's `script_pubkey`. This
// happens to be correct anyway for every output script kind except P2SH.
script_code: &prevout.script_pubkey,
script_pubkey: &prevout.script_pubkey,
value: prevout.value,
}
};
let signable_input = SignableInput::transparent(index, &script_code, value);
let sighash = v5_signature_hash(
&precomputed_tx.tx,
&signable_input,
&precomputed_tx.txid_parts,
);
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();
// `v5_signature_hash` output is always 32 bytes.
*unsafe { &mut *sighash_ret } = sighash.as_ref().try_into().unwrap();
true
}

View File

@ -1128,8 +1128,32 @@ uint256 GetShieldedOutputsHash(const CTransaction& txTo) {
} // anon namespace
PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo) :
PrecomputedTransactionData::PrecomputedTransactionData(
const CTransaction& txTo,
const std::vector<CTxOut>& allPrevOutputs) :
preTx(nullptr, zcash_transaction_precomputed_free)
{
CDataStream sAllPrevOutputs(SER_NETWORK, PROTOCOL_VERSION);
sAllPrevOutputs << allPrevOutputs;
SetPrecomputed(
txTo,
reinterpret_cast<const unsigned char*>(sAllPrevOutputs.data()),
sAllPrevOutputs.size());
}
PrecomputedTransactionData::PrecomputedTransactionData(
const CTransaction& txTo,
const unsigned char* allPrevOutputs,
size_t allPrevOutputsLen) :
preTx(nullptr, zcash_transaction_precomputed_free)
{
SetPrecomputed(txTo, allPrevOutputs, allPrevOutputsLen);
}
void PrecomputedTransactionData::SetPrecomputed(
const CTransaction& txTo,
const unsigned char* allPrevOutputs,
size_t allPrevOutputsLen)
{
bool isOverwinterV3 =
txTo.fOverwintered &&
@ -1153,7 +1177,10 @@ PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo)
ss << txTo;
preTx.reset(zcash_transaction_precomputed_init(
reinterpret_cast<const unsigned char*>(ss.data()),
ss.size()));
ss.size(), allPrevOutputs, allPrevOutputsLen));
if (preTx == nullptr) {
throw std::ios_base::failure("Invalid arguments to PrecomputedTransactionData");
}
}
}
@ -1189,24 +1216,36 @@ uint256 SignatureHash(
auto sigversion = SignatureHashVersion(txTo);
if (sigversion == SIGVERSION_ZIP244) {
// The consensusBranchId parameter is ignored; we use the value stored
// in the transaction itself.
if (nIn == NOT_AN_INPUT) {
// ZIP 244, S.2: transparent_sig_digest
//
// If we are producing a hash for either a coinbase transaction, or a
// non-coinbase transaction that has no transparent inputs, the value of
// transparent_sig_digest is identical to the value specified in section
// T.2.
//
// https://zips.z.cash/zip-0244#s-2-transparent-sig-digest
if (txTo.IsCoinBase() || txTo.vin.empty()) {
// This situation only applies to shielded signatures.
assert(nIn == NOT_AN_INPUT);
assert(nHashType == SIGHASH_ALL);
// 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);
// The amount parameter is ignored; we extract it from allPrevOutputs.
// - Note to future developers: if we ever replace the C++ logic for
// pre-v5 transactions with Rust logic, make sure signrawtransaction
// is updated to know about it!
// The consensusBranchId parameter is ignored; we use the value stored
// in the transaction itself.
uint256 hash;
zcash_transaction_transparent_signature_digest(
if (!zcash_transaction_zip244_signature_digest(
txdata.preTx.get(),
nHashType,
nIn,
reinterpret_cast<const unsigned char*>(sScriptCode.data()),
sScriptCode.size(),
amount,
hash.begin());
hash.begin()))
{
throw std::logic_error("We should not reach here.");
}
return hash;
}
} else if (sigversion == SIGVERSION_OVERWINTER || sigversion == SIGVERSION_SAPLING) {

View File

@ -98,7 +98,20 @@ struct PrecomputedTransactionData
/** Precomputed transaction parts. */
std::unique_ptr<PrecomputedTxParts, decltype(&zcash_transaction_precomputed_free)> preTx;
PrecomputedTransactionData(const CTransaction& tx);
PrecomputedTransactionData(
const CTransaction& tx,
const std::vector<CTxOut>& allPrevOutputs);
PrecomputedTransactionData(
const CTransaction& tx,
const unsigned char* allPrevOutputs,
size_t allPrevOutputsLen);
private:
void SetPrecomputed(
const CTransaction& tx,
const unsigned char* allPrevOutputs,
size_t allPrevOutputsLen);
};
enum SigVersion

View File

@ -91,7 +91,10 @@ struct PrecomputedTransaction {
const CTransaction tx;
const PrecomputedTransactionData txdata;
PrecomputedTransaction(CTransaction txIn) : tx(txIn), txdata(txIn) {}
PrecomputedTransaction(
CTransaction txIn,
const unsigned char* allPrevOutputs,
size_t allPrevOutputsLen) : tx(txIn), txdata(txIn, allPrevOutputs, allPrevOutputsLen) {}
};
void* zcash_script_new_precomputed_tx(
@ -107,10 +110,16 @@ void* zcash_script_new_precomputed_tx(
set_error(err, zcash_script_ERR_TX_SIZE_MISMATCH);
return nullptr;
}
if (tx.nVersion >= ZIP225_TX_VERSION) {
set_error(err, zcash_script_ERR_TX_VERSION);
return nullptr;
}
// Deserializing the tx did not error.
set_error(err, zcash_script_ERR_OK);
auto preTx = new PrecomputedTransaction(tx);
// This is a pre-v5 tx, so the PrecomputedTransactionData constructor
// field `allPrevOutputs` is not used.
auto preTx = new PrecomputedTransaction(tx, nullptr, 0);
return preTx;
} catch (const std::exception&) {
set_error(err, zcash_script_ERR_TX_DESERIALIZE); // Error deserializing
@ -166,10 +175,15 @@ int zcash_script_verify(
return set_error(err, zcash_script_ERR_TX_INDEX);
if (GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) != txToLen)
return set_error(err, zcash_script_ERR_TX_SIZE_MISMATCH);
if (tx.nVersion >= ZIP225_TX_VERSION) {
return set_error(err, zcash_script_ERR_TX_VERSION);
}
// Regardless of the verification result, the tx did not error.
set_error(err, zcash_script_ERR_OK);
PrecomputedTransactionData txdata(tx);
// This is a pre-v5 tx, so the PrecomputedTransactionData constructor
// field `allPrevOutputs` is not used.
PrecomputedTransactionData txdata(tx, {});
return VerifyScript(
tx.vin[nIn].scriptSig,
CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen),

View File

@ -41,6 +41,8 @@ typedef enum zcash_script_error_t
zcash_script_ERR_TX_INDEX,
zcash_script_ERR_TX_SIZE_MISMATCH,
zcash_script_ERR_TX_DESERIALIZE,
// Defined since API version 3.
zcash_script_ERR_TX_VERSION,
} zcash_script_error;
/** Script verification flags */
@ -54,6 +56,8 @@ enum
/// Deserializes the given transaction and precomputes values to improve
/// script verification performance.
///
/// This API cannot be used for v5+ transactions, and will return an error.
///
/// Returns a pointer to the precomputed transaction. Free this with
/// zcash_script_free_precomputed_tx once you are done.
/// The precomputed transaction is guaranteed to not keep a reference to any
@ -91,6 +95,8 @@ EXPORT_SYMBOL int zcash_script_verify_precomputed(
/// txTo correctly spends the scriptPubKey pointed to by scriptPubKey under
/// the additional constraints specified by flags.
///
/// This API cannot be used for v5+ transactions, and will return an error.
///
/// If not NULL, err will contain an error/success code for the operation.
/// Note that script verification failure is indicated by err being set to
/// zcash_script_ERR_OK and a return value of 0.

View File

@ -157,7 +157,7 @@ BOOST_DATA_TEST_CASE(DoS_mapOrphans, boost::unit_test::data::xrange(static_cast<
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID());
const PrecomputedTransactionData txdata(tx);
const PrecomputedTransactionData txdata(tx, {txPrev.vout[0]});
SignSignature(keystore, txPrev, tx, txdata, 0, SIGHASH_ALL, consensusBranchId);
AddOrphanTx(tx, i);
@ -178,7 +178,10 @@ BOOST_DATA_TEST_CASE(DoS_mapOrphans, boost::unit_test::data::xrange(static_cast<
tx.vin[j].prevout.n = j;
tx.vin[j].prevout.hash = txPrev.GetHash();
}
const PrecomputedTransactionData txdata(tx);
// Fake the coins being spent (the random orphan doesn't actually have them all).
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(tx.vin.size(), txPrev.vout[0]);
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
SignSignature(keystore, txPrev, tx, txdata, 0, SIGHASH_ALL, consensusBranchId);
// Re-use same signature for other inputs
// (they don't have to be valid for this test)

View File

@ -79,7 +79,7 @@ BOOST_DATA_TEST_CASE(multisig_verify, boost::unit_test::data::xrange(static_cast
txTo[i].vin[0].prevout.n = i;
txTo[i].vin[0].prevout.hash = txFrom.GetHash();
txTo[i].vout[0].nValue = 1;
txdata.push_back(PrecomputedTransactionData(txTo[i]));
txdata.push_back(PrecomputedTransactionData(txTo[i], {txFrom.vout[i]}));
}
vector<CKey> keys;
@ -225,7 +225,7 @@ BOOST_DATA_TEST_CASE(multisig_Sign, boost::unit_test::data::xrange(static_cast<i
txTo[i].vin[0].prevout.n = i;
txTo[i].vin[0].prevout.hash = txFrom.GetHash();
txTo[i].vout[0].nValue = 1;
txdata.push_back(PrecomputedTransactionData(txTo[i]));
txdata.push_back(PrecomputedTransactionData(txTo[i], {txFrom.vout[i]}));
}
for (int i = 0; i < 3; i++)

View File

@ -47,7 +47,7 @@ Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, Scri
txTo.vin[0].scriptSig = scriptSig;
txTo.vout[0].nValue = 1;
const PrecomputedTransactionData txdata(txTo);
const PrecomputedTransactionData txdata(txTo, txFrom.vout);
return VerifyScript(scriptSig, scriptPubKey, fStrict ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE, MutableTransactionSignatureChecker(&txTo, txdata, 0, txFrom.vout[0].nValue), consensusBranchId, &err);
}
@ -108,7 +108,7 @@ BOOST_DATA_TEST_CASE(sign, boost::unit_test::data::xrange(static_cast<int>(Conse
txTo[i].vin[0].prevout.hash = txFrom.GetHash();
txTo[i].vout[0].nValue = 1;
BOOST_CHECK_MESSAGE(IsMine(keystore, txFrom.vout[i].scriptPubKey), strprintf("IsMine %d", i));
txToData.push_back(PrecomputedTransactionData(txTo[i]));
txToData.push_back(PrecomputedTransactionData(txTo[i], {txFrom.vout[i]}));
}
for (int i = 0; i < 8; i++)
{
@ -117,7 +117,7 @@ BOOST_DATA_TEST_CASE(sign, boost::unit_test::data::xrange(static_cast<int>(Conse
// All of the above should be OK, and the txTos have valid signatures
// Check to make sure signature verification fails if we use the wrong ScriptSig:
for (int i = 0; i < 8; i++) {
PrecomputedTransactionData txdata(txTo[i]);
PrecomputedTransactionData txdata(txTo[i], {txFrom.vout[i]});
for (int j = 0; j < 8; j++)
{
CScript sigSave = txTo[i].vin[0].scriptSig;
@ -212,7 +212,7 @@ BOOST_DATA_TEST_CASE(set, boost::unit_test::data::xrange(static_cast<int>(Consen
txTo[i].vout[0].nValue = 1*CENT;
txTo[i].vout[0].scriptPubKey = inner[i];
BOOST_CHECK_MESSAGE(IsMine(keystore, txFrom.vout[i].scriptPubKey), strprintf("IsMine %d", i));
txToData.push_back(PrecomputedTransactionData(txTo[i]));
txToData.push_back(PrecomputedTransactionData(txTo[i], {txFrom.vout[i]}));
}
for (int i = 0; i < 4; i++)
{
@ -347,12 +347,14 @@ BOOST_DATA_TEST_CASE(AreInputsStandard, boost::unit_test::data::xrange(static_ca
txTo.vout[0].nValue = 10;
txTo.vin.resize(5);
std::vector<CTxOut> allPrevOutputs;
for (int i = 0; i < 5; i++)
{
txTo.vin[i].prevout.n = i;
txTo.vin[i].prevout.hash = txFrom.GetHash();
allPrevOutputs.push_back(txFrom.vout[i]);
}
const PrecomputedTransactionData txToData(txTo);
const PrecomputedTransactionData txToData(txTo, allPrevOutputs);
BOOST_CHECK(SignSignature(keystore, txFrom, txTo, txToData, 0, SIGHASH_ALL, consensusBranchId));
BOOST_CHECK(SignSignature(keystore, txFrom, txTo, txToData, 1, SIGHASH_ALL, consensusBranchId));
BOOST_CHECK(SignSignature(keystore, txFrom, txTo, txToData, 2, SIGHASH_ALL, consensusBranchId));

View File

@ -78,7 +78,7 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, int flags, ui
ScriptError err;
CMutableTransaction txCredit = BuildCreditingTransaction(scriptPubKey);
CMutableTransaction tx = BuildSpendingTransaction(scriptSig, txCredit);
const PrecomputedTransactionData txdata(tx);
const PrecomputedTransactionData txdata(tx, txCredit.vout);
CMutableTransaction tx2 = tx;
BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, flags, MutableTransactionSignatureChecker(&tx, txdata, 0, txCredit.vout[0].nValue), consensusBranchId, &err) == expect, message);
BOOST_CHECK_MESSAGE(expect == (err == SCRIPT_ERR_OK), std::string(ScriptErrorString(err)) + ": " + message);
@ -231,7 +231,7 @@ public:
TestBuilder& PushSig(const CKey& key, int nHashType = SIGHASH_ALL, unsigned int lenR = 32, unsigned int lenS = 32)
{
const PrecomputedTransactionData txdata(spendTx);
const PrecomputedTransactionData txdata(spendTx, creditTx.vout);
uint256 hash = SignatureHash(scriptPubKey, spendTx, 0, nHashType, 0, consensusBranchId, txdata);
std::vector<unsigned char> vchSig, r, s;
uint32_t iter = 0;
@ -712,7 +712,7 @@ BOOST_DATA_TEST_CASE(script_CHECKMULTISIG12, boost::unit_test::data::xrange(stat
CMutableTransaction txFrom12 = BuildCreditingTransaction(scriptPubKey12);
CMutableTransaction txTo12 = BuildSpendingTransaction(CScript(), txFrom12);
const PrecomputedTransactionData txdata12(txTo12);
const PrecomputedTransactionData txdata12(txTo12, txFrom12.vout);
CScript goodsig1 = sign_multisig(scriptPubKey12, key1, txTo12, txdata12, consensusBranchId);
BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, flags, MutableTransactionSignatureChecker(&txTo12, txdata12, 0, txFrom12.vout[0].nValue), consensusBranchId, &err));
@ -746,7 +746,7 @@ BOOST_DATA_TEST_CASE(script_CHECKMULTISIG23, boost::unit_test::data::xrange(stat
CMutableTransaction txFrom23 = BuildCreditingTransaction(scriptPubKey23);
CMutableTransaction txTo23 = BuildSpendingTransaction(CScript(), txFrom23);
const PrecomputedTransactionData txdata23(txTo23);
const PrecomputedTransactionData txdata23(txTo23, txFrom23.vout);
std::vector<CKey> keys;
keys.push_back(key1); keys.push_back(key2);
@ -822,7 +822,7 @@ BOOST_DATA_TEST_CASE(script_combineSigs, boost::unit_test::data::xrange(static_c
CMutableTransaction txFrom = BuildCreditingTransaction(GetScriptForDestination(keys[0].GetPubKey().GetID()));
CMutableTransaction txTo = BuildSpendingTransaction(CScript(), txFrom);
const PrecomputedTransactionData txToData(txTo);
const PrecomputedTransactionData txToData(txTo, txFrom.vout);
CScript& scriptPubKey = txFrom.vout[0].scriptPubKey;
CScript& scriptSig = txTo.vin[0].scriptSig;

View File

@ -202,7 +202,7 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle, uint32_t co
// Empty output script.
CScript scriptCode;
CTransaction signTx(tx);
PrecomputedTransactionData txdata(signTx);
PrecomputedTransactionData txdata(signTx, {});
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);
assert(ed25519_sign(
@ -237,7 +237,8 @@ BOOST_AUTO_TEST_CASE(sighash_test)
CScript scriptCode;
RandomScript(scriptCode);
int nIn = insecure_rand() % txTo.vin.size();
const PrecomputedTransactionData txdata(txTo);
// We don't generate v5 transactions here; we have separate ZIP 244 test vectors.
const PrecomputedTransactionData txdata(txTo, {});
uint256 sh, sho;
sho = SignatureHashOld(scriptCode, txTo, nIn, nHashType);
@ -336,7 +337,8 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
continue;
}
const PrecomputedTransactionData txdata(tx);
// These test vectors do not include v5 transactions.
const PrecomputedTransactionData txdata(tx, {});
sh = SignatureHash(scriptCode, tx, nIn, nHashType, 0, consensusBranchId, txdata);
BOOST_CHECK_MESSAGE(sh.GetHex() == sigHashHex, strTest);
}

View File

@ -115,7 +115,9 @@ BOOST_AUTO_TEST_CASE(tx_valid)
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state, verifier), strTest + comment);
BOOST_CHECK_MESSAGE(state.IsValid(), comment);
PrecomputedTransactionData txdata(tx);
// None of these test vectors use ZIP 244.
assert(tx.nVersion < ZIP225_TX_VERSION);
PrecomputedTransactionData txdata(tx, {});
for (unsigned int i = 0; i < tx.vin.size(); i++)
{
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
@ -207,7 +209,9 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
CValidationState state;
fValid = CheckTransaction(tx, state, verifier) && state.IsValid();
PrecomputedTransactionData txdata(tx);
// None of these test vectors use ZIP 244.
assert(tx.nVersion < ZIP225_TX_VERSION);
PrecomputedTransactionData txdata(tx, {});
for (unsigned int i = 0; i < tx.vin.size() && fValid; i++)
{
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
@ -451,7 +455,10 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
jsdesc->nullifiers[0] = GetRandHash();
jsdesc->nullifiers[1] = GetRandHash();
const PrecomputedTransactionData txdata(newTx);
// Fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(newTx.vin.size());
const PrecomputedTransactionData txdata(newTx, allPrevOutputs);
BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state));
BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true));
@ -672,7 +679,10 @@ BOOST_AUTO_TEST_CASE(test_big_overwinter_transaction) {
mtx.vout[i].scriptPubKey = CScript() << OP_1;
}
PrecomputedTransactionData txdata(mtx);
// Fake coins being spent.
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(mtx.vin.size());
PrecomputedTransactionData txdata(mtx, allPrevOutputs);
// sign all inputs
for(uint32_t i = 0; i < mtx.vin.size(); i++) {
@ -906,6 +916,7 @@ BOOST_AUTO_TEST_CASE(TxV5)
UniValue scriptCodesArr = test[4].get_array();
std::vector<CAmount> amounts;
std::vector<CScript> scriptCodes;
std::vector<CTxOut> allPrevOutputs;
if (tx.IsCoinBase()) {
BOOST_CHECK(amountsArr.empty());
BOOST_CHECK(scriptCodesArr.empty());
@ -917,6 +928,7 @@ BOOST_AUTO_TEST_CASE(TxV5)
amounts.push_back(amountsArr[inpIdx].get_int64());
auto scriptCodeBytes = ParseHex(scriptCodesArr[inpIdx].get_str());
scriptCodes.push_back(CScript(scriptCodeBytes.begin(), scriptCodeBytes.end()));
allPrevOutputs.emplace_back(amounts[inpIdx], scriptCodes[inpIdx]);
}
}
@ -933,7 +945,7 @@ BOOST_AUTO_TEST_CASE(TxV5)
amount = amounts[nIn];
}
const PrecomputedTransactionData txdata(tx);
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
BOOST_CHECK_EQUAL(
SignatureHash(

View File

@ -48,7 +48,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
spends[i].vout[0].scriptPubKey = scriptPubKey;
// Sign:
const PrecomputedTransactionData txdata(spends[i]);
const PrecomputedTransactionData txdata(spends[i], {coinbaseTxns[0].vout[0]});
std::vector<unsigned char> vchSig;
uint256 hash = SignatureHash(scriptPubKey, spends[i], 0, SIGHASH_ALL, coinbaseTxns[0].vout[0].nValue, SPROUT_BRANCH_ID, txdata);
BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));

View File

@ -261,7 +261,7 @@ void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKe
}
mtx.vin.emplace_back(utxo);
tIns.emplace_back(scriptPubKey, value);
tIns.emplace_back(value, scriptPubKey);
}
void TransactionBuilder::AddTransparentOutput(const CTxDestination& to, CAmount value)
@ -322,7 +322,7 @@ TransactionBuilderResult TransactionBuilder::Build()
change -= jsOutput.value;
}
for (auto tIn : tIns) {
change += tIn.value;
change += tIn.nValue;
}
for (auto tOut : mtx.vout) {
change -= tOut.nValue;
@ -446,7 +446,7 @@ TransactionBuilderResult TransactionBuilder::Build()
//
auto consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams);
const PrecomputedTransactionData txdata(mtx);
const PrecomputedTransactionData txdata(mtx, tIns);
// Empty output script.
uint256 dataToBeSigned;
@ -499,7 +499,7 @@ TransactionBuilderResult TransactionBuilder::Build()
SignatureData sigdata;
bool signSuccess = ProduceSignature(
TransactionSignatureCreator(
keystore, &txNewConst, txdata, nIn, tIn.value, SIGHASH_ALL),
keystore, &txNewConst, txdata, nIn, tIn.nValue, SIGHASH_ALL),
tIn.scriptPubKey, sigdata, consensusBranchId);
if (!signSuccess) {

View File

@ -81,15 +81,6 @@ struct JSDescriptionInfo {
);
};
struct TransparentInputInfo {
CScript scriptPubKey;
CAmount value;
TransparentInputInfo(
CScript scriptPubKey,
CAmount value) : scriptPubKey(scriptPubKey), value(value) {}
};
class TransactionBuilderResult {
private:
std::optional<CTransaction> maybeTx;
@ -120,7 +111,7 @@ private:
std::vector<OutputDescriptionInfo> outputs;
std::vector<libzcash::JSInput> jsInputs;
std::vector<libzcash::JSOutput> jsOutputs;
std::vector<TransparentInputInfo> tIns;
std::vector<CTxOut> tIns;
std::optional<std::pair<uint256, libzcash::SaplingPaymentAddress>> saplingChangeAddr;
std::optional<libzcash::SproutPaymentAddress> sproutChangeAddr;

View File

@ -35,6 +35,8 @@ CMutableTransaction GetValidSproutReceiveTransaction(
}
mtx.vin[0].prevout.n = 0;
mtx.vin[1].prevout.n = 0;
std::vector<CTxOut> allPrevOutputs;
allPrevOutputs.resize(mtx.vin.size());
// Generate an ephemeral keypair.
Ed25519SigningKey joinSplitPrivKey;
@ -69,7 +71,7 @@ CMutableTransaction GetValidSproutReceiveTransaction(
uint32_t consensusBranchId = SPROUT_BRANCH_ID;
CScript scriptCode;
CTransaction signTx(mtx);
const PrecomputedTransactionData txdata(signTx);
const PrecomputedTransactionData txdata(signTx, allPrevOutputs);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);
// Add the signature
@ -185,7 +187,8 @@ CWalletTx GetValidSproutSpend(const libzcash::SproutSpendingKey& sk,
uint32_t consensusBranchId = SPROUT_BRANCH_ID;
CScript scriptCode;
CTransaction signTx(mtx);
const PrecomputedTransactionData txdata(signTx);
assert(signTx.vin.size() == 0);
const PrecomputedTransactionData txdata(signTx, {});
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);
// Add the signature

View File

@ -834,7 +834,11 @@ UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
PrecomputedTransactionData txdata(signTx);
std::vector<CTxOut> allPrevOutputs;
for (const MergeToAddressInputUTXO& t : utxoInputs_) {
allPrevOutputs.emplace_back(std::get<1>(t), std::get<2>(t));
}
PrecomputedTransactionData txdata(signTx, allPrevOutputs);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_, txdata);
// Add the signature

View File

@ -361,7 +361,11 @@ UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInf
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
PrecomputedTransactionData txdata(signTx);
std::vector<CTxOut> allPrevOutputs;
for (ShieldCoinbaseUTXO & t : inputs_) {
allPrevOutputs.emplace_back(t.amount, t.scriptPubKey);
}
PrecomputedTransactionData txdata(signTx, allPrevOutputs);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);
// Add the signature

View File

@ -2761,6 +2761,9 @@ UniValue zc_raw_joinsplit(const UniValue& params, bool fHelp)
CTransaction tx;
if (!DecodeHexTx(tx, params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
if (tx.nVersion >= ZIP225_TX_VERSION) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "v5+ transactions do not support Sprout");
}
UniValue inputs = params[1].get_obj();
UniValue outputs = params[2].get_obj();
@ -2889,7 +2892,8 @@ UniValue zc_raw_joinsplit(const UniValue& params, bool fHelp)
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
PrecomputedTransactionData txdata(signTx);
// This API will never support v5+ transactions, and can ignore ZIP 244.
PrecomputedTransactionData txdata(signTx, {});
auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId, txdata);

View File

@ -4767,7 +4767,11 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
// Sign
int nIn = 0;
CTransaction txNewConst(txNew);
const PrecomputedTransactionData txdata(txNewConst);
std::vector<CTxOut> allPrevOutputs;
for (const std::pair<const CWalletTx*, unsigned int>& coin : setCoins) {
allPrevOutputs.push_back(coin.first->vout[coin.second]);
}
const PrecomputedTransactionData txdata(txNewConst, allPrevOutputs);
for (const std::pair<const CWalletTx*, unsigned int>& coin : setCoins)
{
bool signSuccess;

View File

@ -233,12 +233,14 @@ double benchmark_large_tx(size_t nInputs)
spending_tx.nVersion = SAPLING_TX_VERSION;
auto input_hash = orig_tx.GetHash();
std::vector<CTxOut> allPrevOutputs;
// Add nInputs inputs
for (size_t i = 0; i < nInputs; i++) {
spending_tx.vin.emplace_back(input_hash, 0);
allPrevOutputs.push_back(orig_tx.vout[0]);
}
PrecomputedTransactionData txdata(spending_tx);
PrecomputedTransactionData txdata(spending_tx, allPrevOutputs);
// Sign for all the inputs
auto consensusBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;