Skip to content

Commit

Permalink
Implement batch validator for IssueBundle
Browse files Browse the repository at this point in the history
  • Loading branch information
dmidem committed Aug 20, 2023
1 parent b2419c3 commit e3ce7f9
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 17 deletions.
32 changes: 19 additions & 13 deletions src/gtest/test_checktransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) {
auto consensus = Params().GetConsensus();
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
std::optional<rust::Box<issue_bundle::BatchValidator>> issueAuth = std::nullopt;

CMutableTransaction mtx = GetValidTransaction();
mtx.joinSplitSig.bytes[0] += 1;
Expand All @@ -530,22 +531,25 @@ TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) {
// during initial block download, for transactions being accepted into the
// mempool (and thus not mined), DoS ban score should be zero, else 10
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; });
// for transactions that have been mined in a block, DoS ban score should
// always be 100.
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
}

// FIXME: add tests for issueAuth like orchardAuth

TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
SelectParams(CBaseChainParams::REGTEST);
auto consensus = Params().GetConsensus();
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
std::optional<rust::Box<issue_bundle::BatchValidator>> issueAuth = std::nullopt;

auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
auto blossomBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_BLOSSOM].nBranchId;
Expand All @@ -565,7 +569,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
CCoinsViewCache view(&baseView);
// Ensure that the transaction validates against Sapling.
EXPECT_TRUE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false,
tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, false,
[](const Consensus::Params&) { return false; }));

// Attempt to validate the inputs against Blossom. We should be notified
Expand All @@ -577,7 +581,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
HexInt(saplingBranchId)),
false, "")).Times(1);
EXPECT_FALSE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, blossomBranchId, false, false,
tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, blossomBranchId, false, false,
[](const Consensus::Params&) { return false; }));

// Attempt to validate the inputs against Heartwood. All we should learn is
Expand All @@ -587,7 +591,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
10, false, REJECT_INVALID,
"bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
EXPECT_FALSE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, heartwoodBranchId, false, false,
tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, heartwoodBranchId, false, false,
[](const Consensus::Params&) { return false; }));
}

Expand All @@ -596,6 +600,7 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
auto consensus = Params().GetConsensus();
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
std::optional<rust::Box<issue_bundle::BatchValidator>> issueAuth = std::nullopt;

AssumeShieldedInputsExistAndAreSpendable baseView;
CCoinsViewCache view(&baseView);
Expand All @@ -612,7 +617,7 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
CTransaction tx(mtx);
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
MockCValidationState state;
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true));
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, true));
}

// Copied from libsodium/crypto_sign/ed25519/ref10/open.c
Expand All @@ -636,15 +641,15 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
// during initial block download, for transactions being accepted into the
// mempool (and thus not mined), DoS ban score should be zero, else 10
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; });
// for transactions that have been mined in a block, DoS ban score should
// always be 100.
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
}

TEST(ChecktransactionTests, OverwinterConstructors) {
Expand Down Expand Up @@ -1290,6 +1295,7 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {

std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator(false);
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = std::nullopt;
std::optional<rust::Box<issue_bundle::BatchValidator>> issueAuth = std::nullopt;
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;

// Coinbase transaction does not pass shielded input checks, as bindingSig
Expand All @@ -1303,7 +1309,7 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
AssumeShieldedInputsExistAndAreSpendable baseView;
CCoinsViewCache view(&baseView);
EXPECT_TRUE(ContextualCheckShieldedInputs(
tx, txdata, state, view, saplingAuth, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
tx, txdata, state, view, saplingAuth, orchardAuth, issueAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
EXPECT_FALSE(saplingAuth.value()->validate());

RegtestDeactivateHeartwood();
Expand Down
30 changes: 28 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,7 @@ bool ContextualCheckShieldedInputs(
const CCoinsViewCache &view,
std::optional<rust::Box<sapling::BatchValidator>>& saplingAuth,
std::optional<rust::Box<orchard::BatchValidator>>& orchardAuth,
std::optional<rust::Box<issue_bundle::BatchValidator>>& issueAuth,
const Consensus::Params& consensus,
uint32_t consensusBranchId,
bool nu5Active,
Expand Down Expand Up @@ -1375,6 +1376,11 @@ bool ContextualCheckShieldedInputs(
tx.GetOrchardBundle().QueueAuthValidation(*orchardAuth.value(), dataToBeSigned);
}

// Queue Issue bundle to be batch-validated.
if (issueAuth.has_value()) {
tx.GetIssueBundle().QueueAuthValidation(*issueAuth.value(), dataToBeSigned);
}

return true;
}

Expand Down Expand Up @@ -1980,6 +1986,9 @@ bool AcceptToMemoryPool(
// Orchard bundle contains at least two signatures.
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = orchard::init_batch_validator(true);

// This will be a single-transaction batch for IssueAuth
std::optional<rust::Box<issue_bundle::BatchValidator>> issueAuth = issue_bundle::init_batch_validator(true);

// Check shielded input signatures.
if (!ContextualCheckShieldedInputs(
tx,
Expand All @@ -1988,6 +1997,7 @@ bool AcceptToMemoryPool(
view,
saplingAuth,
orchardAuth,
issueAuth,
chainparams.GetConsensus(),
consensusBranchId,
chainparams.GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_NU5),
Expand All @@ -1996,14 +2006,18 @@ bool AcceptToMemoryPool(
return false;
}

// Check Sapling and Orchard bundle authorizations.
// Check Sapling, Orchard and Issue bundle authorizations.
// `saplingAuth` and `orchardAuth` are known here to be non-null.
if (!saplingAuth.value()->validate()) {
return state.DoS(100, false, REJECT_INVALID, "bad-sapling-bundle-authorization");
}
if (!orchardAuth.value()->validate()) {
return state.DoS(100, false, REJECT_INVALID, "bad-orchard-bundle-authorization");
}
// FIXME: should the error text be "bad-issue-bundle" as it's not only about authorization?
if (!issueAuth.value()->validate()) {
return state.DoS(100, false, REJECT_INVALID, "bad-issue-bundle-authorization");
}

{
// Store transaction in memory
Expand Down Expand Up @@ -3103,11 +3117,13 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// proof verification is expensive, disable if possible
auto verifier = fExpensiveChecks ? ProofVerifier::Strict() : ProofVerifier::Disabled();

// Disable Sapling and Orchard batch validation if possible.
// Disable Sapling, Orchard and Issue batch validation if possible.
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = fExpensiveChecks ?
std::optional(sapling::init_batch_validator(fCacheResults)) : std::nullopt;
std::optional<rust::Box<orchard::BatchValidator>> orchardAuth = fExpensiveChecks ?
std::optional(orchard::init_batch_validator(fCacheResults)) : std::nullopt;
std::optional<rust::Box<issue_bundle::BatchValidator>> issueAuth = fExpensiveChecks ?
std::optional(issue_bundle::init_batch_validator(fCacheResults)) : std::nullopt;

// If in initial block download, and this block is an ancestor of a checkpoint,
// and -ibdskiptxverification is set, disable all transaction checks.
Expand Down Expand Up @@ -3372,6 +3388,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
view,
saplingAuth,
orchardAuth,
issueAuth,
chainparams.GetConsensus(),
consensusBranchId,
chainparams.GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_NU5),
Expand Down Expand Up @@ -3683,6 +3700,15 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
REJECT_INVALID, "bad-orchard-bundle-authorization");
}

// Ensure Issue bundle is valid (if we are checking it)
if (issueAuth.has_value() && !issueAuth.value()->validate()) {
// FIXME: should the error text be "bad-issue-bundle" as it's not only about authorization?
// FIXME: add validation errotr message ....
return state.DoS(100,
error("ConnectBlock(): an Issue bundle within the block is invalid"),
REJECT_INVALID, "bad-orchard-bundle-authorization");
}

if (!control.Wait())
return state.DoS(100, false);
int64_t nTime2 = GetTimeMicros(); nTimeVerify += nTime2 - nTimeStart;
Expand Down
1 change: 1 addition & 0 deletions src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ bool ContextualCheckShieldedInputs(
const CCoinsViewCache &view,
std::optional<rust::Box<sapling::BatchValidator>>& saplingAuth,
std::optional<rust::Box<orchard::BatchValidator>>& orchardAuth,
std::optional<rust::Box<issue_bundle::BatchValidator>>& issueAuth,
const Consensus::Params& consensus,
uint32_t consensusBranchId,
bool nu5Active,
Expand Down
9 changes: 9 additions & 0 deletions src/primitives/issue.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@
const size_t GetNumActions() const {
return inner->num_actions();
}

/// Queues this bundle's authorization for validation.
///
/// `sighash` must be for the transaction this bundle is within.
void QueueAuthValidation(
issue_bundle::BatchValidator& batch, const uint256& sighash) const
{
batch.add_bundle(inner->box_clone(), sighash.GetRawBytes());
}
};

#endif // ZCASH_PRIMITIVES_ISSUE_H
Expand Down
1 change: 1 addition & 0 deletions src/rust/bin/inspect/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ impl Authorization for PrecomputedAuth {
type TransparentAuth = TransparentAuth;
type SaplingAuth = sapling::Authorized;
type OrchardAuth = orchard::bundle::Authorized;
// FIXME: add IssueAuth?
}

pub(crate) fn inspect(tx: Transaction, context: Option<Context>) {
Expand Down
11 changes: 11 additions & 0 deletions src/rust/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
create_issue_bundle, issue_bundle_from_raw_box, none_issue_bundle, parse_issue_bundle,
IssueAction, IssueBundle, IssueNote,
},
issue_ffi::{issue_batch_validation_init, BatchValidator as IssueBatchValidator},
merkle_frontier::{new_orchard, orchard_empty_root, parse_orchard, Orchard, OrchardWallet},
note_encryption::{
try_sapling_note_decryption, try_sapling_output_recovery, DecryptedSaplingOutput,
Expand Down Expand Up @@ -355,6 +356,16 @@ pub(crate) mod ffi {
fn authorization(self: &IssueBundle) -> [u8; 64]; // Assumption is that we only need auth for IssueBundle<Signed> here, ignoring empty for Unauthorized and sighash for Prepared
}

#[namespace = "issue_bundle"]
extern "Rust" {
#[cxx_name = "BatchValidator"]
type IssueBatchValidator;
#[cxx_name = "init_batch_validator"]
fn issue_batch_validation_init(cache_store: bool) -> Box<IssueBatchValidator>;
fn add_bundle(self: &mut IssueBatchValidator, bundle: Box<IssueBundle>, sighash: [u8; 32]);
fn validate(self: &mut IssueBatchValidator) -> bool;
}

#[namespace = "orchard"]
extern "Rust" {
#[cxx_name = "BatchValidator"]
Expand Down
1 change: 1 addition & 0 deletions src/rust/src/builder_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ pub(crate) fn shielded_signature_digest(
type TransparentAuth = TransparentAuth;
type SaplingAuth = sapling::builder::Unauthorized;
type OrchardAuth = InProgress<Unproven, Unauthorized>;
// FIXME: add IssueAuth?
}

let txdata: TransactionData<Signable> = tx.into_data().map_bundles(
Expand Down
20 changes: 20 additions & 0 deletions src/rust/src/bundlecache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ impl BundleValidityCache {
static BUNDLE_CACHES_LOADED: Once = Once::new();
static mut SAPLING_BUNDLE_VALIDITY_CACHE: Option<RwLock<BundleValidityCache>> = None;
static mut ORCHARD_BUNDLE_VALIDITY_CACHE: Option<RwLock<BundleValidityCache>> = None;
static mut ISSUE_BUNDLE_VALIDITY_CACHE: Option<RwLock<BundleValidityCache>> = None;

pub(crate) fn init(cache_bytes: usize) {
BUNDLE_CACHES_LOADED.call_once(|| unsafe {
Expand All @@ -110,6 +111,11 @@ pub(crate) fn init(cache_bytes: usize) {
b"OrchardVeriCache",
cache_bytes,
)));
ISSUE_BUNDLE_VALIDITY_CACHE = Some(RwLock::new(BundleValidityCache::new(
"Issue",
b"IssueVeriCache",
cache_bytes,
)));
});
}

Expand Down Expand Up @@ -142,3 +148,17 @@ pub(crate) fn orchard_bundle_validity_cache_mut() -> RwLockWriteGuard<'static, B
.write()
.unwrap()
}

pub(crate) fn issue_bundle_validity_cache() -> RwLockReadGuard<'static, BundleValidityCache> {
unsafe { ISSUE_BUNDLE_VALIDITY_CACHE.as_ref() }
.expect("bundlecache::init() should have been called")
.read()
.unwrap()
}

pub(crate) fn issue_bundle_validity_cache_mut() -> RwLockWriteGuard<'static, BundleValidityCache> {
unsafe { ISSUE_BUNDLE_VALIDITY_CACHE.as_mut() }
.expect("bundlecache::init() should have been called")
.write()
.unwrap()
}
4 changes: 4 additions & 0 deletions src/rust/src/issue_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use std::{ptr, slice};
use tracing::error;
use zcash_primitives::transaction::components::issuance as issuance_serialization;

mod batch_validator;

pub use batch_validator::{issue_batch_validation_init, BatchValidator};

#[no_mangle]
pub extern "C" fn issuance_key_to_issuance_authorizing_key(
key: *const IssuanceKey,
Expand Down
Loading

0 comments on commit e3ce7f9

Please sign in to comment.