Auto merge of #5217 - str4d:5194-orchard-sig-checks, r=str4d
Implement Orchard signature validation consensus rules Implemented via an `AuthValidator` class that internally uses batch validation. - Currently, only RedPallas signatures are batch-validated. We can extend this validator to cover Halo 2 proofs in the future. - Signatures in a batch are not retried individually if the batch fails: - For per-transaction batching (when adding to the mempool), we don't care which signature within the transaction failed. - For per-block batching, we currently don't care which transaction failed. We might do so in future, at which point this behaviour can be easily changed. Closes zcash/zcash#5194.
This commit is contained in:
commit
6808b33eab
|
@ -473,7 +473,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
|||
[[package]]
|
||||
name = "equihash"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb#3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"byteorder",
|
||||
|
@ -612,7 +612,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "halo2"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/zcash/halo2.git?rev=32cdcfa66fbc4ca3103115518d374f4cfe6c3b7a#32cdcfa66fbc4ca3103115518d374f4cfe6c3b7a"
|
||||
source = "git+https://github.com/zcash/halo2.git?rev=d04b532368d05b505e622f8cac4c0693574fbd93#d04b532368d05b505e622f8cac4c0693574fbd93"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"crossbeam-utils 0.8.5",
|
||||
|
@ -1030,7 +1030,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||
[[package]]
|
||||
name = "orchard"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/orchard.git?rev=cd1e72bbcd9f2e873408aa365537c89824cb7430#cd1e72bbcd9f2e873408aa365537c89824cb7430"
|
||||
source = "git+https://github.com/zcash/orchard.git?rev=f7c64e0437040d831e61711cd9e5695b001cb5cb#f7c64e0437040d831e61711cd9e5695b001cb5cb"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayvec 0.7.0",
|
||||
|
@ -1040,11 +1040,13 @@ dependencies = [
|
|||
"fpe",
|
||||
"group",
|
||||
"halo2",
|
||||
"lazy_static",
|
||||
"nonempty",
|
||||
"pasta_curves",
|
||||
"rand",
|
||||
"reddsa",
|
||||
"subtle",
|
||||
"zcash_note_encryption",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1686,7 +1688,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_note_encryption"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb#3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"byteorder",
|
||||
|
@ -1700,7 +1702,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_primitives"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb#3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bitvec",
|
||||
|
@ -1730,7 +1732,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb#3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
|
||||
dependencies = [
|
||||
"bellman",
|
||||
"blake2b_simd",
|
||||
|
|
|
@ -60,6 +60,8 @@ codegen-units = 1
|
|||
|
||||
[patch.crates-io]
|
||||
ed25519-zebra = { git = "https://github.com/ZcashFoundation/ed25519-zebra.git", rev = "d3512400227a362d08367088ffaa9bd4142a69c7" }
|
||||
orchard = { git = "https://github.com/zcash/orchard.git", rev = "cd1e72bbcd9f2e873408aa365537c89824cb7430" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "3915abd0a1ee5b7e757c3fec0509d71f5e08fdfb" }
|
||||
halo2 = { git = "https://github.com/zcash/halo2.git", rev = "d04b532368d05b505e622f8cac4c0693574fbd93" }
|
||||
orchard = { git = "https://github.com/zcash/orchard.git", rev = "f7c64e0437040d831e61711cd9e5695b001cb5cb" }
|
||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include "utiltest.h"
|
||||
#include "zcash/Proof.hpp"
|
||||
|
||||
#include <rust/orchard.h>
|
||||
|
||||
class MockCValidationState : public CValidationState {
|
||||
public:
|
||||
MOCK_METHOD5(DoS, bool(int level, bool ret,
|
||||
|
@ -26,13 +28,14 @@ public:
|
|||
|
||||
TEST(CheckBlock, VersionTooLow) {
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
auto orchardAuth = orchard::AuthValidator::Batch();
|
||||
|
||||
CBlock block;
|
||||
block.nVersion = 1;
|
||||
|
||||
MockCValidationState state;
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "version-too-low", false)).Times(1);
|
||||
EXPECT_FALSE(CheckBlock(block, state, Params(), verifier, false, false, true));
|
||||
EXPECT_FALSE(CheckBlock(block, state, Params(), verifier, orchardAuth, false, false, true));
|
||||
}
|
||||
|
||||
|
||||
|
@ -72,9 +75,10 @@ TEST(CheckBlock, BlockSproutRejectsBadVersion) {
|
|||
CBlockIndex indexPrev {Params().GenesisBlock()};
|
||||
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
auto orchardAuth = orchard::AuthValidator::Batch();
|
||||
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false)).Times(1);
|
||||
EXPECT_FALSE(CheckBlock(block, state, Params(), verifier, false, false, true));
|
||||
EXPECT_FALSE(CheckBlock(block, state, Params(), verifier, orchardAuth, false, false, true));
|
||||
}
|
||||
|
||||
|
||||
|
|
66
src/main.cpp
66
src/main.cpp
|
@ -1188,7 +1188,7 @@ bool ContextualCheckTransaction(
|
|||
|
||||
|
||||
bool CheckTransaction(const CTransaction& tx, CValidationState &state,
|
||||
ProofVerifier& verifier)
|
||||
ProofVerifier& verifier, orchard::AuthValidator& orchardAuth)
|
||||
{
|
||||
// Don't count coinbase transactions because mining skews the count
|
||||
if (!tx.IsCoinBase()) {
|
||||
|
@ -1209,6 +1209,9 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state,
|
|||
// Sapling zk-SNARK proofs are checked in librustzcash_sapling_check_{spend,output},
|
||||
// called from ContextualCheckTransaction.
|
||||
|
||||
// Queue Orchard signatures to be batch-validated.
|
||||
tx.GetOrchardBundle().QueueSignatureValidation(orchardAuth, tx.GetHash());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1504,8 +1507,12 @@ bool AcceptToMemoryPool(
|
|||
return false;
|
||||
}
|
||||
|
||||
// This will be a single-transaction batch, which is still more efficient as every
|
||||
// Orchard bundle contains at least two signatures.
|
||||
auto orchardAuth = orchard::AuthValidator::Batch();
|
||||
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
if (!CheckTransaction(tx, state, verifier))
|
||||
if (!CheckTransaction(tx, state, verifier, orchardAuth))
|
||||
return error("AcceptToMemoryPool: CheckTransaction failed");
|
||||
|
||||
// Check transaction contextually against the set of consensus rules which apply in the next block to be mined.
|
||||
|
@ -1646,9 +1653,9 @@ bool AcceptToMemoryPool(
|
|||
}
|
||||
}
|
||||
|
||||
// We don't yet know if the transaction commits to consensusBranchId,
|
||||
// but if the entry gets added to the mempool, then it has passed
|
||||
// ContextualCheckInputs and therefore this is correct.
|
||||
// For v1-v4 transactions, we don't yet know if the transaction commits
|
||||
// to consensusBranchId, but if the entry gets added to the mempool, then
|
||||
// it has passed ContextualCheckInputs and therefore this is correct.
|
||||
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), mempool.HasNoInputsOf(tx), fSpendsCoinbase, consensusBranchId);
|
||||
unsigned int nSize = entry.GetTxSize();
|
||||
|
||||
|
@ -1701,6 +1708,13 @@ bool AcceptToMemoryPool(
|
|||
return state.Error("AcceptToMemoryPool: " + errmsg);
|
||||
}
|
||||
|
||||
// Check Orchard bundle authorizations.
|
||||
// This is done near the end to help prevent CPU exhaustion
|
||||
// denial-of-service attacks.
|
||||
if (!orchardAuth.Validate()) {
|
||||
return state.DoS(100, false, REJECT_INVALID, "bad-orchard-bundle-authorization");
|
||||
}
|
||||
|
||||
// Check against previous transactions
|
||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||
PrecomputedTransactionData txdata(tx);
|
||||
|
@ -2732,13 +2746,20 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
|||
// proof verification is expensive, disable if possible
|
||||
auto verifier = fExpensiveChecks ? ProofVerifier::Strict() : ProofVerifier::Disabled();
|
||||
|
||||
// Disable Orchard batch signature validation if possible.
|
||||
auto orchardAuth = fExpensiveChecks ?
|
||||
orchard::AuthValidator::Batch() : orchard::AuthValidator::Disabled();
|
||||
|
||||
// If in initial block download, and this block is an ancestor of a checkpoint,
|
||||
// and -ibdskiptxverification is set, disable all transaction checks.
|
||||
bool fCheckTransactions = ShouldCheckTransactions(chainparams, pindex);
|
||||
|
||||
// Check it again to verify JoinSplit proofs, and in case a previous version let a bad block in
|
||||
if (!CheckBlock(block, state, chainparams, verifier, !fJustCheck, !fJustCheck, fCheckTransactions))
|
||||
if (!CheckBlock(block, state, chainparams, verifier, orchardAuth,
|
||||
!fJustCheck, !fJustCheck, fCheckTransactions))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify that the view's current state corresponds to the previous block
|
||||
uint256 hashPrevBlock = pindex->pprev == NULL ? uint256() : pindex->pprev->GetBlockHash();
|
||||
|
@ -3051,6 +3072,13 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
|||
block.vtx[0].GetValueOut(), blockReward),
|
||||
REJECT_INVALID, "bad-cb-amount");
|
||||
|
||||
// Ensure Orchard signatures are valid (if we are checking them)
|
||||
if (!orchardAuth.Validate()) {
|
||||
return state.DoS(100,
|
||||
error("ConnectBlock(): an Orchard 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;
|
||||
|
@ -4142,6 +4170,7 @@ bool CheckBlock(const CBlock& block,
|
|||
CValidationState& state,
|
||||
const CChainParams& chainparams,
|
||||
ProofVerifier& verifier,
|
||||
orchard::AuthValidator& orchardAuth,
|
||||
bool fCheckPOW,
|
||||
bool fCheckMerkleRoot,
|
||||
bool fCheckTransactions)
|
||||
|
@ -4192,7 +4221,7 @@ bool CheckBlock(const CBlock& block,
|
|||
|
||||
// Check transactions
|
||||
for (const CTransaction& tx : block.vtx)
|
||||
if (!CheckTransaction(tx, state, verifier))
|
||||
if (!CheckTransaction(tx, state, verifier, orchardAuth))
|
||||
return error("CheckBlock(): CheckTransaction failed");
|
||||
|
||||
unsigned int nSigOps = 0;
|
||||
|
@ -4394,9 +4423,9 @@ static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state
|
|||
* Store block on disk.
|
||||
* If dbp is non-NULL, the file is known to already reside on disk.
|
||||
*
|
||||
* JoinSplit proofs are not verified here; the only caller of AcceptBlock
|
||||
* (ProcessNewBlock) later invokes ActivateBestChain, which ultimately calls
|
||||
* ConnectBlock in a manner that can verify the proofs
|
||||
* JoinSplit proofs and Orchard authorizations are not verified here; the only
|
||||
* caller of AcceptBlock (ProcessNewBlock) later invokes ActivateBestChain,
|
||||
* which ultimately calls ConnectBlock in a manner that can verify the proofs.
|
||||
*/
|
||||
static bool AcceptBlock(const CBlock& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, CDiskBlockPos* dbp)
|
||||
{
|
||||
|
@ -4428,10 +4457,12 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, const CCha
|
|||
if (fTooFarAhead) return true; // Block height is too high
|
||||
}
|
||||
|
||||
// See method docstring for why this is always disabled.
|
||||
// See method docstring for why these are always disabled.
|
||||
auto verifier = ProofVerifier::Disabled();
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||
|
||||
bool fCheckTransactions = ShouldCheckTransactions(chainparams, pindex);
|
||||
if ((!CheckBlock(block, state, chainparams, verifier, true, true, fCheckTransactions)) ||
|
||||
if ((!CheckBlock(block, state, chainparams, verifier, orchardAuth, true, true, fCheckTransactions)) ||
|
||||
!ContextualCheckBlock(block, state, chainparams, pindex->pprev, fCheckTransactions)) {
|
||||
if (state.IsInvalid() && !state.CorruptionPossible()) {
|
||||
pindex->nStatus |= BLOCK_FAILED_VALID;
|
||||
|
@ -4517,14 +4548,16 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams,
|
|||
CBlockIndex indexDummy(block);
|
||||
indexDummy.pprev = pindexPrev;
|
||||
indexDummy.nHeight = pindexPrev->nHeight + 1;
|
||||
// JoinSplit proofs are verified in ConnectBlock
|
||||
|
||||
// JoinSplit proofs and Orchard authorizations are verified in ConnectBlock
|
||||
auto verifier = ProofVerifier::Disabled();
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||
|
||||
// NOTE: CheckBlockHeader is called by CheckBlock
|
||||
if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev))
|
||||
return false;
|
||||
// The following may be duplicative of the `CheckBlock` call within `ConnectBlock`
|
||||
if (!CheckBlock(block, state, chainparams, verifier, false, fCheckMerkleRoot, true))
|
||||
if (!CheckBlock(block, state, chainparams, verifier, orchardAuth, false, fCheckMerkleRoot, true))
|
||||
return false;
|
||||
if (!ContextualCheckBlock(block, state, chainparams, pindexPrev, true))
|
||||
return false;
|
||||
|
@ -4919,6 +4952,9 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
|
|||
// Flags used to permit skipping checks for efficiency
|
||||
auto verifier = ProofVerifier::Disabled(); // No need to verify JoinSplits twice
|
||||
bool fCheckTransactions = true;
|
||||
// We may as well check Orchard authorizations if we are checking
|
||||
// transactions, since we can batch-validate them.
|
||||
auto orchardAuth = orchard::AuthValidator::Batch();
|
||||
|
||||
for (CBlockIndex* pindex = chainActive.Tip(); pindex && pindex->pprev; pindex = pindex->pprev)
|
||||
{
|
||||
|
@ -4934,7 +4970,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
|
|||
|
||||
// check level 1: verify block validity
|
||||
fCheckTransactions = ShouldCheckTransactions(chainparams, pindex);
|
||||
if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams, verifier, true, true, fCheckTransactions))
|
||||
if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams, verifier, orchardAuth, true, true, fCheckTransactions))
|
||||
return error("VerifyDB(): *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
|
||||
|
||||
// check level 2: verify undo validity
|
||||
|
|
|
@ -42,6 +42,8 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <rust/orchard.h>
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
|
||||
class CBlockIndex;
|
||||
|
@ -358,7 +360,8 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);
|
|||
/** Transaction validation functions */
|
||||
|
||||
/** Context-independent validity checks */
|
||||
bool CheckTransaction(const CTransaction& tx, CValidationState& state, ProofVerifier& verifier);
|
||||
bool CheckTransaction(const CTransaction& tx, CValidationState& state,
|
||||
ProofVerifier& verifier, orchard::AuthValidator& orchardAuth);
|
||||
bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidationState &state);
|
||||
|
||||
namespace Consensus {
|
||||
|
@ -464,6 +467,7 @@ bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state,
|
|||
bool CheckBlock(const CBlock& block, CValidationState& state,
|
||||
const CChainParams& chainparams,
|
||||
ProofVerifier& verifier,
|
||||
orchard::AuthValidator& orchardAuth,
|
||||
bool fCheckPOW,
|
||||
bool fCheckMerkleRoot,
|
||||
bool fCheckTransactions);
|
||||
|
|
|
@ -374,6 +374,15 @@ public:
|
|||
}
|
||||
inner.reset(bundle);
|
||||
}
|
||||
|
||||
/// Queues this bundle's signatures for validation.
|
||||
///
|
||||
/// `txid` must be for the transaction this bundle is within.
|
||||
void QueueSignatureValidation(
|
||||
orchard::AuthValidator& batch, const uint256& txid) const
|
||||
{
|
||||
batch.Queue(inner.get(), txid.begin());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Stream>
|
||||
|
|
|
@ -16,6 +16,9 @@ extern "C" {
|
|||
struct OrchardBundlePtr;
|
||||
typedef struct OrchardBundlePtr OrchardBundlePtr;
|
||||
|
||||
struct OrchardBatchValidatorPtr;
|
||||
typedef struct OrchardBatchValidatorPtr OrchardBatchValidatorPtr;
|
||||
|
||||
/// Clones the given Orchard bundle.
|
||||
///
|
||||
/// Both bundles need to be separately freed when they go out of scope.
|
||||
|
@ -41,8 +44,87 @@ bool orchard_bundle_serialize(
|
|||
void* stream,
|
||||
write_callback_t write_cb);
|
||||
|
||||
/// Initializes a new Orchard batch validator.
|
||||
///
|
||||
/// Please free this with `orchard_batch_validation_free` when you are done with
|
||||
/// it.
|
||||
OrchardBatchValidatorPtr* orchard_batch_validation_init();
|
||||
|
||||
/// Frees a batch validator returned from `orchard_batch_validation_init`.
|
||||
void orchard_batch_validation_free(OrchardBatchValidatorPtr* batch);
|
||||
|
||||
/// Adds an Orchard bundle to this batch.
|
||||
///
|
||||
/// Currently, only RedPallas signatures are batch-validated.
|
||||
void orchard_batch_add_bundle(
|
||||
OrchardBatchValidatorPtr* batch,
|
||||
const OrchardBundlePtr* bundle,
|
||||
const unsigned char* txid);
|
||||
|
||||
/// Validates this batch.
|
||||
///
|
||||
/// Returns false if any item in the batch is invalid.
|
||||
bool orchard_batch_validate(const OrchardBatchValidatorPtr* batch);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace orchard
|
||||
{
|
||||
/**
|
||||
* A validator for the Orchard authorization component of a transaction.
|
||||
*
|
||||
* Currently, only RedPallas signatures are batch-validated.
|
||||
*/
|
||||
class AuthValidator
|
||||
{
|
||||
private:
|
||||
/// An optional batch validator (with `nullptr` corresponding to `None`).
|
||||
/// Memory is allocated by Rust.
|
||||
std::unique_ptr<OrchardBatchValidatorPtr, decltype(&orchard_batch_validation_free)> inner;
|
||||
|
||||
AuthValidator() : inner(nullptr, orchard_batch_validation_free) {}
|
||||
|
||||
public:
|
||||
// AuthValidator should never be copied
|
||||
AuthValidator(const AuthValidator&) = delete;
|
||||
AuthValidator& operator=(const AuthValidator&) = delete;
|
||||
AuthValidator(AuthValidator&& bundle) : inner(std::move(bundle.inner)) {}
|
||||
AuthValidator& operator=(AuthValidator&& bundle)
|
||||
{
|
||||
if (this != &bundle) {
|
||||
inner = std::move(bundle.inner);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Creates a validation context that batch-validates Orchard signatures.
|
||||
static AuthValidator Batch() {
|
||||
auto batch = AuthValidator();
|
||||
batch.inner.reset(orchard_batch_validation_init());
|
||||
return batch;
|
||||
}
|
||||
|
||||
/// Creates a validation context that performs no validation. This can be
|
||||
/// used when avoiding duplicate effort such as during reindexing.
|
||||
static AuthValidator Disabled() {
|
||||
return AuthValidator();
|
||||
}
|
||||
|
||||
/// Queues an Orchard bundle for validation.
|
||||
void Queue(const OrchardBundlePtr* bundle, const unsigned char* txid) {
|
||||
orchard_batch_add_bundle(inner.get(), bundle, txid);
|
||||
}
|
||||
|
||||
/// Validates the queued Orchard authorizations, returning `true` if all
|
||||
/// signatures were valid and `false` otherwise.
|
||||
bool Validate() const {
|
||||
return orchard_batch_validate(inner.get());
|
||||
}
|
||||
};
|
||||
} // namespace orchard
|
||||
#endif
|
||||
|
||||
#endif // ZCASH_RUST_INCLUDE_RUST_ORCHARD_H
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
use std::ptr;
|
||||
|
||||
use orchard::{bundle::Authorized, Bundle};
|
||||
use tracing::error;
|
||||
use zcash_primitives::transaction::components::{orchard as orchard_serialization, Amount};
|
||||
use orchard::{
|
||||
bundle::Authorized,
|
||||
primitives::redpallas::{self, Binding, SpendAuth},
|
||||
Bundle,
|
||||
};
|
||||
use rand_core::OsRng;
|
||||
use tracing::{debug, error};
|
||||
use zcash_primitives::transaction::{
|
||||
components::{orchard as orchard_serialization, Amount},
|
||||
TxId,
|
||||
};
|
||||
|
||||
use crate::streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb};
|
||||
|
||||
|
@ -65,3 +73,108 @@ pub extern "C" fn orchard_bundle_serialize(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A signature within an authorized Orchard bundle.
|
||||
#[derive(Debug)]
|
||||
struct BundleSignature {
|
||||
/// The signature item for validation.
|
||||
signature: redpallas::batch::Item<SpendAuth, Binding>,
|
||||
}
|
||||
|
||||
/// Batch validation context for Orchard.
|
||||
pub struct BatchValidator {
|
||||
signatures: Vec<BundleSignature>,
|
||||
}
|
||||
|
||||
impl BatchValidator {
|
||||
fn new() -> Self {
|
||||
BatchValidator { signatures: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a RedPallas batch validation context.
|
||||
///
|
||||
/// Please free this when you're done.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_batch_validation_init() -> *mut BatchValidator {
|
||||
let ctx = Box::new(BatchValidator::new());
|
||||
Box::into_raw(ctx)
|
||||
}
|
||||
|
||||
/// Frees a RedPallas batch validation context returned from
|
||||
/// [`orchard_batch_validation_init`].
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_batch_validation_free(ctx: *mut BatchValidator) {
|
||||
if !ctx.is_null() {
|
||||
drop(unsafe { Box::from_raw(ctx) });
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an Orchard bundle to this batch.
|
||||
///
|
||||
/// Currently, only RedPallas signatures are batch-validated.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_batch_add_bundle(
|
||||
batch: *mut BatchValidator,
|
||||
bundle: *const Bundle<Authorized, Amount>,
|
||||
txid: *const [u8; 32],
|
||||
) {
|
||||
let batch = unsafe { batch.as_mut() };
|
||||
let bundle = unsafe { bundle.as_ref() };
|
||||
let txid = unsafe { txid.as_ref() }.cloned().map(TxId::from_bytes);
|
||||
|
||||
match (batch, bundle, txid) {
|
||||
(Some(batch), Some(bundle), Some(txid)) => {
|
||||
for action in bundle.actions().iter() {
|
||||
batch.signatures.push(BundleSignature {
|
||||
signature: action
|
||||
.rk()
|
||||
.create_batch_item(action.authorization().clone(), txid.as_ref()),
|
||||
});
|
||||
}
|
||||
|
||||
batch.signatures.push(BundleSignature {
|
||||
signature: bundle.binding_validating_key().create_batch_item(
|
||||
bundle.authorization().binding_signature().clone(),
|
||||
txid.as_ref(),
|
||||
),
|
||||
});
|
||||
}
|
||||
(_, _, None) => error!("orchard_batch_add_bundle() called without txid!"),
|
||||
(Some(_), None, Some(txid)) => debug!("Tx {} has no Orchard component", txid),
|
||||
(None, Some(_), _) => debug!("Orchard BatchValidator not provided, assuming disabled."),
|
||||
(None, None, _) => (), // Boring, don't bother logging.
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates this batch.
|
||||
///
|
||||
/// - Returns `true` if `batch` is null.
|
||||
/// - Returns `false` if any item in the batch is invalid.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_batch_validate(batch: *const BatchValidator) -> bool {
|
||||
if let Some(batch) = unsafe { batch.as_ref() } {
|
||||
let mut validator = redpallas::batch::Verifier::new();
|
||||
for sig in batch.signatures.iter() {
|
||||
validator.queue(sig.signature.clone());
|
||||
}
|
||||
|
||||
match validator.verify(OsRng) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
error!("RedPallas batch validation failed: {}", e);
|
||||
// TODO: Try sub-batches to figure out which signatures are invalid. We can
|
||||
// postpone this for now:
|
||||
// - For per-transaction batching (when adding to the mempool), we don't care
|
||||
// which signature within the transaction failed.
|
||||
// - For per-block batching, we currently don't care which transaction failed.
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The orchard::BatchValidator C++ class uses null to represent a disabled batch
|
||||
// validator.
|
||||
debug!("Orchard BatchValidator not provided, assuming disabled.");
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "utiltime.h"
|
||||
#include "zcash/Proof.hpp"
|
||||
|
||||
#include <rust/orchard.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
@ -57,7 +59,8 @@ BOOST_AUTO_TEST_CASE(May15)
|
|||
// After May 15'th, big blocks are OK:
|
||||
forkingBlock.nTime = tMay15; // Invalidates PoW
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
BOOST_CHECK(CheckBlock(forkingBlock, state, Params(), verifier, false, false, true));
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled(); // Block is before NU5
|
||||
BOOST_CHECK(CheckBlock(forkingBlock, state, Params(), verifier, orchardAuth, false, false, true));
|
||||
}
|
||||
|
||||
SetMockTime(0);
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <boost/test/data/test_case.hpp>
|
||||
|
||||
#include <rust/ed25519.h>
|
||||
#include <rust/orchard.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
|
@ -68,6 +69,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
|
|||
std::string comment("");
|
||||
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
auto orchardAuth = orchard::AuthValidator::Batch();
|
||||
ScriptError err;
|
||||
for (size_t idx = 0; idx < tests.size(); idx++) {
|
||||
UniValue test = tests[idx];
|
||||
|
@ -111,7 +113,7 @@ BOOST_AUTO_TEST_CASE(tx_valid)
|
|||
stream >> tx;
|
||||
|
||||
CValidationState state;
|
||||
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state, verifier), strTest + comment);
|
||||
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state, verifier, orchardAuth), strTest + comment);
|
||||
BOOST_CHECK_MESSAGE(state.IsValid(), comment);
|
||||
|
||||
PrecomputedTransactionData txdata(tx);
|
||||
|
@ -156,6 +158,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
|
|||
std::string comment("");
|
||||
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
auto orchardAuth = orchard::AuthValidator::Batch();
|
||||
ScriptError err;
|
||||
for (size_t idx = 0; idx < tests.size(); idx++) {
|
||||
UniValue test = tests[idx];
|
||||
|
@ -204,7 +207,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
|
|||
}
|
||||
|
||||
CValidationState state;
|
||||
fValid = CheckTransaction(tx, state, verifier) && state.IsValid();
|
||||
fValid = CheckTransaction(tx, state, verifier, orchardAuth) && state.IsValid();
|
||||
|
||||
PrecomputedTransactionData txdata(tx);
|
||||
for (unsigned int i = 0; i < tx.vin.size() && fValid; i++)
|
||||
|
@ -243,11 +246,12 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests)
|
|||
stream >> tx;
|
||||
CValidationState state;
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state, verifier) && state.IsValid(), "Simple deserialized transaction should be valid.");
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled(); // No Orchard component
|
||||
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state, verifier, orchardAuth) && state.IsValid(), "Simple deserialized transaction should be valid.");
|
||||
|
||||
// Check that duplicate txins fail
|
||||
tx.vin.push_back(tx.vin[0]);
|
||||
BOOST_CHECK_MESSAGE(!CheckTransaction(tx, state, verifier) || !state.IsValid(), "Transaction with duplicate txins should be invalid.");
|
||||
BOOST_CHECK_MESSAGE(!CheckTransaction(tx, state, verifier, orchardAuth) || !state.IsValid(), "Transaction with duplicate txins should be invalid.");
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -424,6 +428,7 @@ void test_simple_sapling_invalidity(uint32_t consensusBranchId, CMutableTransact
|
|||
|
||||
void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransaction tx)
|
||||
{
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled(); // No Orchard components
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
{
|
||||
// Ensure that empty vin/vout remain invalid without
|
||||
|
@ -477,26 +482,26 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
jsdesc->vpub_old = -1;
|
||||
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-negative");
|
||||
|
||||
jsdesc->vpub_old = MAX_MONEY + 1;
|
||||
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-toolarge");
|
||||
|
||||
jsdesc->vpub_old = 0;
|
||||
jsdesc->vpub_new = -1;
|
||||
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-negative");
|
||||
|
||||
jsdesc->vpub_new = MAX_MONEY + 1;
|
||||
|
||||
BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-toolarge");
|
||||
|
||||
jsdesc->vpub_new = (MAX_MONEY / 2) + 10;
|
||||
|
@ -506,7 +511,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
JSDescription *jsdesc2 = &newTx.vJoinSplit[1];
|
||||
jsdesc2->vpub_new = (MAX_MONEY / 2) + 10;
|
||||
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-txintotal-toolarge");
|
||||
}
|
||||
{
|
||||
|
@ -520,7 +525,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
jsdesc->nullifiers[0] = GetRandHash();
|
||||
jsdesc->nullifiers[1] = jsdesc->nullifiers[0];
|
||||
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-joinsplits-nullifiers-duplicate");
|
||||
|
||||
jsdesc->nullifiers[1] = GetRandHash();
|
||||
|
@ -532,7 +537,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
jsdesc2->nullifiers[0] = GetRandHash();
|
||||
jsdesc2->nullifiers[1] = jsdesc->nullifiers[0];
|
||||
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-joinsplits-nullifiers-duplicate");
|
||||
}
|
||||
{
|
||||
|
@ -551,7 +556,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
CTransaction finalNewTx(newTx);
|
||||
BOOST_CHECK(finalNewTx.IsCoinBase());
|
||||
}
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier));
|
||||
BOOST_CHECK(!CheckTransaction(newTx, state, verifier, orchardAuth));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-cb-has-joinsplits");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include "wallet/wallet.h"
|
||||
#include "zcash/Proof.hpp"
|
||||
|
||||
#include <rust/orchard.h>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <atomic>
|
||||
|
@ -487,8 +489,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
|||
ssValue >> wtx;
|
||||
CValidationState state;
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
if (!(CheckTransaction(wtx, state, verifier) && (wtx.GetHash() == hash) && state.IsValid()))
|
||||
auto orchardAuth = orchard::AuthValidator::Batch();
|
||||
if (!(
|
||||
CheckTransaction(wtx, state, verifier, orchardAuth) &&
|
||||
(wtx.GetHash() == hash) &&
|
||||
orchardAuth.Validate() &&
|
||||
state.IsValid())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Undo serialize changes in 31600
|
||||
if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
|
||||
|
|
Loading…
Reference in New Issue