Use batch validation for Sapling proofs and signatures
This commit is contained in:
parent
5d9ae0ba63
commit
90f13641b9
|
@ -120,9 +120,9 @@ checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b"
|
|||
|
||||
[[package]]
|
||||
name = "bellman"
|
||||
version = "0.13.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d96d7f4f3dc9a699bdef1d19648f6f20ef966b51892d224582a4475be669cb5"
|
||||
checksum = "a4dd656ef4fdf7debb6d87d4dd92642fcbcdb78cbf6600c13e25c87e4d1a3807"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"blake2s_simd",
|
||||
|
@ -1641,6 +1641,22 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redjubjub"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6039ff156887caf92df308cbaccdc058c9d3155a913da046add6e48c4cdbd91d"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"jubjub",
|
||||
"rand_core 0.6.3",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
|
@ -2320,9 +2336,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92c0928a33ab146a4e93e2a1141ce589de1bbe93c90219965dcccadc15f4e4e8"
|
||||
checksum = "98bf5f6af051dd929263f279b21b9c04c1f30116c70f3c190de2566677f245ef"
|
||||
dependencies = [
|
||||
"bellman",
|
||||
"blake2b_simd",
|
||||
|
@ -2334,6 +2350,8 @@ dependencies = [
|
|||
"jubjub",
|
||||
"lazy_static",
|
||||
"rand_core 0.6.3",
|
||||
"redjubjub",
|
||||
"tracing",
|
||||
"zcash_primitives",
|
||||
]
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ zcash_encoding = "0.1"
|
|||
zcash_history = "0.3"
|
||||
zcash_note_encryption = "0.1"
|
||||
zcash_primitives = { version = "0.7", features = ["transparent-inputs"] }
|
||||
zcash_proofs = "0.7"
|
||||
zcash_proofs = "0.7.1"
|
||||
ed25519-zebra = "3"
|
||||
zeroize = "1.4.2"
|
||||
|
||||
|
|
|
@ -7,6 +7,12 @@ description = "The cryptographic code in this crate has been reviewed for correc
|
|||
[criteria.license-reviewed]
|
||||
description = "The license of this crate has been reviewed for compatibility with its usage in this repository. If the crate is not available under the MIT license, `contrib/debian/copyright` has been updated with a corresponding copyright notice for files under `depends/*/vendored-sources/CRATE_NAME`."
|
||||
|
||||
[[audits.bellman]]
|
||||
who = "Jack Grigg <jack@z.cash>"
|
||||
criteria = ["crypto-reviewed", "safe-to-deploy"]
|
||||
delta = "0.13.0 -> 0.13.1"
|
||||
notes = "Adds multi-threaded batch validation, which I checked against the existing single-threaded batch validation."
|
||||
|
||||
[[audits.equihash]]
|
||||
who = "Jack Grigg <jack@z.cash>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
@ -151,3 +157,9 @@ criteria = ["crypto-reviewed", "safe-to-deploy"]
|
|||
delta = "0.6.0 -> 0.7.0"
|
||||
notes = "The ECC core team maintains this crate, and we have reviewed every line."
|
||||
|
||||
[[audits.zcash_proofs]]
|
||||
who = "Jack Grigg <jack@z.cash>"
|
||||
criteria = ["crypto-reviewed", "safe-to-deploy"]
|
||||
delta = "0.7.0 -> 0.7.1"
|
||||
notes = "The ECC core team maintains this crate, and we have reviewed every line."
|
||||
|
||||
|
|
|
@ -632,6 +632,10 @@ criteria = "safe-to-deploy"
|
|||
version = "0.3.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[unaudited.redjubjub]]
|
||||
version = "0.5.0"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
[[unaudited.redox_syscall]]
|
||||
version = "0.2.13"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <librustzcash.h>
|
||||
#include <rust/ed25519.h>
|
||||
#include <rust/sapling.h>
|
||||
#include <rust/orchard.h>
|
||||
|
||||
// Subclass of CTransaction which doesn't call UpdateHash when constructing
|
||||
|
@ -534,6 +535,7 @@ TEST(ChecktransactionTests, BadTxnsPrevoutNull) {
|
|||
TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
auto consensus = Params().GetConsensus();
|
||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||
|
||||
CMutableTransaction mtx = GetValidTransaction();
|
||||
|
@ -551,20 +553,21 @@ 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, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, 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, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, 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, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, 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, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
|
||||
}
|
||||
|
||||
TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
auto consensus = Params().GetConsensus();
|
||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||
|
||||
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
|
||||
|
@ -585,7 +588,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
|||
CCoinsViewCache view(&baseView);
|
||||
// Ensure that the transaction validates against Sapling.
|
||||
EXPECT_TRUE(ContextualCheckShieldedInputs(
|
||||
tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, false,
|
||||
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, false,
|
||||
[](const Consensus::Params&) { return false; }));
|
||||
|
||||
// Attempt to validate the inputs against Blossom. We should be notified
|
||||
|
@ -597,7 +600,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
|||
HexInt(saplingBranchId)),
|
||||
false, "")).Times(1);
|
||||
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
||||
tx, txdata, state, view, orchardAuth, consensus, blossomBranchId, false, false,
|
||||
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, blossomBranchId, false, false,
|
||||
[](const Consensus::Params&) { return false; }));
|
||||
|
||||
// Attempt to validate the inputs against Heartwood. All we should learn is
|
||||
|
@ -607,13 +610,14 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
|||
10, false, REJECT_INVALID,
|
||||
"bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
||||
tx, txdata, state, view, orchardAuth, consensus, heartwoodBranchId, false, false,
|
||||
tx, txdata, state, view, saplingAuth, orchardAuth, consensus, heartwoodBranchId, false, false,
|
||||
[](const Consensus::Params&) { return false; }));
|
||||
}
|
||||
|
||||
TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
auto consensus = Params().GetConsensus();
|
||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||
|
||||
AssumeShieldedInputsExistAndAreSpendable baseView;
|
||||
|
@ -631,7 +635,7 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
|
|||
CTransaction tx(mtx);
|
||||
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
||||
MockCValidationState state;
|
||||
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, true));
|
||||
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true));
|
||||
}
|
||||
|
||||
// Copied from libsodium/crypto_sign/ed25519/ref10/open.c
|
||||
|
@ -655,15 +659,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, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, 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, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, 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, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, 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, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
|
||||
ContextualCheckShieldedInputs(tx, txdata, state, view, saplingAuth, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
|
||||
}
|
||||
|
||||
TEST(ChecktransactionTests, OverwinterConstructors) {
|
||||
|
@ -1321,20 +1325,23 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
|||
// Coinbase transaction should pass contextual checks.
|
||||
EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 10, 57));
|
||||
|
||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator();
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||
auto heartwoodBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_HEARTWOOD].nBranchId;
|
||||
|
||||
// Coinbase transaction does not pass shielded input checks, as bindingSig
|
||||
// consensus rule is enforced.
|
||||
// consensus rule is enforced. ContextualCheckShieldedInputs passes because
|
||||
// the rest of the input checks pass, but saplingAuth fails when it attempts
|
||||
// to validate the batch of signatures that includes bindingSig.
|
||||
// - 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, {});
|
||||
AssumeShieldedInputsExistAndAreSpendable baseView;
|
||||
CCoinsViewCache view(&baseView);
|
||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid", false, "")).Times(1);
|
||||
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
||||
tx, txdata, state, view, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
|
||||
EXPECT_TRUE(ContextualCheckShieldedInputs(
|
||||
tx, txdata, state, view, saplingAuth, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
|
||||
EXPECT_FALSE(saplingAuth.value()->validate());
|
||||
|
||||
RegtestDeactivateHeartwood();
|
||||
}
|
||||
|
|
57
src/main.cpp
57
src/main.cpp
|
@ -47,7 +47,6 @@
|
|||
|
||||
#include <rust/ed25519.h>
|
||||
#include <rust/metrics.h>
|
||||
#include <rust/sapling.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -1246,6 +1245,7 @@ bool ContextualCheckShieldedInputs(
|
|||
const PrecomputedTransactionData& txdata,
|
||||
CValidationState &state,
|
||||
const CCoinsViewCache &view,
|
||||
std::optional<rust::Box<sapling::BatchValidator>>& saplingAuth,
|
||||
std::optional<orchard::AuthValidator>& orchardAuth,
|
||||
const Consensus::Params& consensus,
|
||||
uint32_t consensusBranchId,
|
||||
|
@ -1318,17 +1318,16 @@ bool ContextualCheckShieldedInputs(
|
|||
if (!tx.vShieldedSpend.empty() ||
|
||||
!tx.vShieldedOutput.empty())
|
||||
{
|
||||
auto ctx = sapling::init_verifier();
|
||||
auto assembler = sapling::new_bundle_assembler();
|
||||
|
||||
for (const SpendDescription &spend : tx.vShieldedSpend) {
|
||||
if (!ctx->check_spend(
|
||||
if (!assembler->add_spend(
|
||||
spend.cv.GetRawBytes(),
|
||||
spend.anchor.GetRawBytes(),
|
||||
spend.nullifier.GetRawBytes(),
|
||||
spend.rk.GetRawBytes(),
|
||||
spend.zkproof,
|
||||
spend.spendAuthSig,
|
||||
dataToBeSigned.GetRawBytes()
|
||||
spend.spendAuthSig
|
||||
)) {
|
||||
return state.DoS(
|
||||
dosLevelPotentiallyRelaxing,
|
||||
|
@ -1338,10 +1337,12 @@ bool ContextualCheckShieldedInputs(
|
|||
}
|
||||
|
||||
for (const OutputDescription &output : tx.vShieldedOutput) {
|
||||
if (!ctx->check_output(
|
||||
if (!assembler->add_output(
|
||||
output.cv.GetRawBytes(),
|
||||
output.cmu.GetRawBytes(),
|
||||
output.ephemeralKey.GetRawBytes(),
|
||||
output.encCiphertext,
|
||||
output.outCiphertext,
|
||||
output.zkproof
|
||||
)) {
|
||||
// This should be a non-contextual check, but we check it here
|
||||
|
@ -1352,15 +1353,19 @@ bool ContextualCheckShieldedInputs(
|
|||
}
|
||||
}
|
||||
|
||||
if (!ctx->final_check(
|
||||
auto bundle = sapling::finish_bundle_assembly(
|
||||
std::move(assembler),
|
||||
tx.GetValueBalanceSapling(),
|
||||
tx.bindingSig,
|
||||
dataToBeSigned.GetRawBytes()
|
||||
)) {
|
||||
return state.DoS(
|
||||
dosLevelPotentiallyRelaxing,
|
||||
error("ContextualCheckShieldedInputs(): Sapling binding signature invalid"),
|
||||
REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid");
|
||||
tx.bindingSig);
|
||||
|
||||
// Queue Sapling bundle to be batch-validated. This also checks some consensus rules.
|
||||
if (saplingAuth.has_value()) {
|
||||
if (!saplingAuth.value()->check_bundle(std::move(bundle), dataToBeSigned.GetRawBytes())) {
|
||||
return state.DoS(
|
||||
dosLevelPotentiallyRelaxing,
|
||||
error("ContextualCheckShieldedInputs(): Sapling bundle invalid"),
|
||||
REJECT_INVALID, "bad-txns-sapling-bundle-invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1986,6 +1991,11 @@ bool AcceptToMemoryPool(
|
|||
__func__, hash.ToString(), FormatStateMessage(state));
|
||||
}
|
||||
|
||||
// This will be a single-transaction batch, which will be more efficient
|
||||
// than unbatched if the transaction contains at least one Sapling Spend
|
||||
// or at least two Sapling Outputs.
|
||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = sapling::init_batch_validator();
|
||||
|
||||
// This will be a single-transaction batch, which is still more efficient as every
|
||||
// Orchard bundle contains at least two signatures.
|
||||
std::optional<orchard::AuthValidator> orchardAuth = orchard::AuthValidator::Batch();
|
||||
|
@ -1996,6 +2006,7 @@ bool AcceptToMemoryPool(
|
|||
txdata,
|
||||
state,
|
||||
view,
|
||||
saplingAuth,
|
||||
orchardAuth,
|
||||
chainparams.GetConsensus(),
|
||||
consensusBranchId,
|
||||
|
@ -2005,7 +2016,11 @@ bool AcceptToMemoryPool(
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check Orchard bundle authorizations. `orchardAuth` here is known to be non-null
|
||||
// Check Sapling and Orchard 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");
|
||||
}
|
||||
|
@ -3059,7 +3074,9 @@ 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.
|
||||
// Disable Sapling and Orchard batch validation if possible.
|
||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = fExpensiveChecks ?
|
||||
std::optional(sapling::init_batch_validator()) : std::nullopt;
|
||||
std::optional<orchard::AuthValidator> orchardAuth = fExpensiveChecks ?
|
||||
orchard::AuthValidator::Batch() : orchard::AuthValidator::Disabled();
|
||||
|
||||
|
@ -3302,6 +3319,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
|||
txdata.back(),
|
||||
state,
|
||||
view,
|
||||
saplingAuth,
|
||||
orchardAuth,
|
||||
chainparams.GetConsensus(),
|
||||
consensusBranchId,
|
||||
|
@ -3502,6 +3520,13 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
|||
block.vtx[0].GetValueOut(), blockReward),
|
||||
REJECT_INVALID, "bad-cb-amount");
|
||||
|
||||
// Ensure Sapling authorizations are valid (if we are checking them)
|
||||
if (saplingAuth.has_value() && !saplingAuth.value()->validate()) {
|
||||
return state.DoS(100,
|
||||
error("ConnectBlock(): a Sapling bundle within the block is invalid"),
|
||||
REJECT_INVALID, "bad-sapling-bundle-authorization");
|
||||
}
|
||||
|
||||
// Ensure Orchard signatures are valid (if we are checking them)
|
||||
if (orchardAuth.has_value() && !orchardAuth.value().Validate()) {
|
||||
return state.DoS(100,
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <rust/sapling.h>
|
||||
#include <rust/orchard.h>
|
||||
|
||||
#include <boost/unordered_map.hpp>
|
||||
|
@ -402,6 +403,7 @@ bool ContextualCheckShieldedInputs(
|
|||
const PrecomputedTransactionData& txdata,
|
||||
CValidationState &state,
|
||||
const CCoinsViewCache &view,
|
||||
std::optional<rust::Box<sapling::BatchValidator>>& saplingAuth,
|
||||
std::optional<orchard::AuthValidator>& orchardAuth,
|
||||
const Consensus::Params& consensus,
|
||||
uint32_t consensusBranchId,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// See https://github.com/rust-lang/rfcs/pull/2585 for more background.
|
||||
#![allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
|
||||
use bellman::groth16::{Parameters, PreparedVerifyingKey};
|
||||
use bellman::groth16::{self, prepare_verifying_key, Parameters, PreparedVerifyingKey};
|
||||
use blake2s_simd::Params as Blake2sParams;
|
||||
use bls12_381::Bls12;
|
||||
use group::{cofactor::CofactorGroup, GroupEncoding};
|
||||
|
@ -88,8 +88,8 @@ mod test_harness_ffi;
|
|||
mod tests;
|
||||
|
||||
static PROOF_PARAMETERS_LOADED: Once = Once::new();
|
||||
static mut SAPLING_SPEND_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||
static mut SAPLING_OUTPUT_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||
static mut SAPLING_SPEND_VK: Option<groth16::VerifyingKey<Bls12>> = None;
|
||||
static mut SAPLING_OUTPUT_VK: Option<groth16::VerifyingKey<Bls12>> = None;
|
||||
static mut SPROUT_GROTH16_VK: Option<PreparedVerifyingKey<Bls12>> = None;
|
||||
|
||||
static mut SAPLING_SPEND_PARAMS: Option<Parameters<Bls12>> = None;
|
||||
|
@ -179,6 +179,11 @@ pub extern "C" fn librustzcash_init_zksnark_params(
|
|||
let sapling_spend_params = params.spend_params;
|
||||
let sapling_output_params = params.output_params;
|
||||
|
||||
// We need to clone these because we aren't necessarily storing the proving
|
||||
// parameters in memory.
|
||||
let sapling_spend_vk = sapling_spend_params.vk.clone();
|
||||
let sapling_output_vk = sapling_output_params.vk.clone();
|
||||
|
||||
// Generate Orchard parameters.
|
||||
info!(target: "main", "Loading Orchard parameters");
|
||||
let orchard_pk = load_proving_keys.then(orchard::circuit::ProvingKey::build);
|
||||
|
@ -191,8 +196,8 @@ pub extern "C" fn librustzcash_init_zksnark_params(
|
|||
SAPLING_OUTPUT_PARAMS = load_proving_keys.then(|| sapling_output_params);
|
||||
SPROUT_GROTH16_PARAMS_PATH = sprout_path.map(|p| p.to_owned());
|
||||
|
||||
SAPLING_SPEND_VK = Some(params.spend_vk);
|
||||
SAPLING_OUTPUT_VK = Some(params.output_vk);
|
||||
SAPLING_SPEND_VK = Some(sapling_spend_vk);
|
||||
SAPLING_OUTPUT_VK = Some(sapling_output_vk);
|
||||
SPROUT_GROTH16_VK = params.sprout_vk;
|
||||
|
||||
ORCHARD_PK = orchard_pk;
|
||||
|
@ -845,7 +850,7 @@ pub extern "C" fn librustzcash_sapling_spend_proof(
|
|||
anchor,
|
||||
merkle_path,
|
||||
unsafe { SAPLING_SPEND_PARAMS.as_ref() }.unwrap(),
|
||||
unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap(),
|
||||
&prepare_verifying_key(unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap()),
|
||||
)
|
||||
.expect("proving should not fail");
|
||||
|
||||
|
|
|
@ -7,14 +7,19 @@
|
|||
// on the entire module.
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use bellman::groth16::Proof;
|
||||
use bellman::groth16::{prepare_verifying_key, Proof};
|
||||
use group::GroupEncoding;
|
||||
|
||||
use rand_core::OsRng;
|
||||
use zcash_note_encryption::EphemeralKeyBytes;
|
||||
use zcash_primitives::{
|
||||
sapling::redjubjub::{self, Signature},
|
||||
transaction::components::Amount,
|
||||
sapling::{
|
||||
redjubjub::{self, Signature},
|
||||
Nullifier,
|
||||
},
|
||||
transaction::components::{sapling, Amount},
|
||||
};
|
||||
use zcash_proofs::sapling::SaplingVerificationContext;
|
||||
use zcash_proofs::sapling::{self as sapling_proofs, SaplingVerificationContext};
|
||||
|
||||
use super::GROTH_PROOF_SIZE;
|
||||
use super::{de_ct, SAPLING_OUTPUT_VK, SAPLING_SPEND_VK};
|
||||
|
@ -22,11 +27,38 @@ use super::{de_ct, SAPLING_OUTPUT_VK, SAPLING_SPEND_VK};
|
|||
#[cxx::bridge(namespace = "sapling")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
type Bundle;
|
||||
type BundleAssembler;
|
||||
fn new_bundle_assembler() -> Box<BundleAssembler>;
|
||||
fn add_spend(
|
||||
self: &mut BundleAssembler,
|
||||
cv: &[u8; 32],
|
||||
anchor: &[u8; 32],
|
||||
nullifier: [u8; 32],
|
||||
rk: &[u8; 32],
|
||||
zkproof: [u8; 192], // GROTH_PROOF_SIZE
|
||||
spend_auth_sig: &[u8; 64],
|
||||
) -> bool;
|
||||
fn add_output(
|
||||
self: &mut BundleAssembler,
|
||||
cv: &[u8; 32],
|
||||
cmu: &[u8; 32],
|
||||
ephemeral_key: [u8; 32],
|
||||
enc_ciphertext: [u8; 580],
|
||||
out_ciphertext: [u8; 80],
|
||||
zkproof: [u8; 192], // GROTH_PROOF_SIZE
|
||||
) -> bool;
|
||||
fn finish_bundle_assembly(
|
||||
assembler: Box<BundleAssembler>,
|
||||
value_balance: i64,
|
||||
binding_sig: [u8; 64],
|
||||
) -> Box<Bundle>;
|
||||
|
||||
type Verifier;
|
||||
|
||||
fn init_verifier() -> Box<Verifier>;
|
||||
fn check_spend(
|
||||
&mut self,
|
||||
self: &mut Verifier,
|
||||
cv: &[u8; 32],
|
||||
anchor: &[u8; 32],
|
||||
nullifier: &[u8; 32],
|
||||
|
@ -36,21 +68,139 @@ mod ffi {
|
|||
sighash_value: &[u8; 32],
|
||||
) -> bool;
|
||||
fn check_output(
|
||||
&mut self,
|
||||
self: &mut Verifier,
|
||||
cv: &[u8; 32],
|
||||
cm: &[u8; 32],
|
||||
ephemeral_key: &[u8; 32],
|
||||
zkproof: &[u8; 192], // GROTH_PROOF_SIZE
|
||||
) -> bool;
|
||||
fn final_check(
|
||||
&self,
|
||||
self: &Verifier,
|
||||
value_balance: i64,
|
||||
binding_sig: &[u8; 64],
|
||||
sighash_value: &[u8; 32],
|
||||
) -> bool;
|
||||
|
||||
type BatchValidator;
|
||||
fn init_batch_validator() -> Box<BatchValidator>;
|
||||
fn check_bundle(self: &mut BatchValidator, bundle: Box<Bundle>, sighash: [u8; 32]) -> bool;
|
||||
fn validate(self: &mut BatchValidator) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
struct Bundle(sapling::Bundle<sapling::Authorized>);
|
||||
|
||||
struct BundleAssembler {
|
||||
shielded_spends: Vec<sapling::SpendDescription<sapling::Authorized>>,
|
||||
shielded_outputs: Vec<sapling::OutputDescription<[u8; 192]>>, // GROTH_PROOF_SIZE
|
||||
}
|
||||
|
||||
fn new_bundle_assembler() -> Box<BundleAssembler> {
|
||||
Box::new(BundleAssembler {
|
||||
shielded_spends: vec![],
|
||||
shielded_outputs: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
impl BundleAssembler {
|
||||
fn add_spend(
|
||||
self: &mut BundleAssembler,
|
||||
cv: &[u8; 32],
|
||||
anchor: &[u8; 32],
|
||||
nullifier: [u8; 32],
|
||||
rk: &[u8; 32],
|
||||
zkproof: [u8; 192], // GROTH_PROOF_SIZE
|
||||
spend_auth_sig: &[u8; 64],
|
||||
) -> bool {
|
||||
// Deserialize the value commitment
|
||||
let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) {
|
||||
Some(p) => p,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Deserialize the anchor, which should be an element
|
||||
// of Fr.
|
||||
let anchor = match de_ct(bls12_381::Scalar::from_bytes(anchor)) {
|
||||
Some(a) => a,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Deserialize rk
|
||||
let rk = match redjubjub::PublicKey::read(&rk[..]) {
|
||||
Ok(p) => p,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
// Deserialize the signature
|
||||
let spend_auth_sig = match Signature::read(&spend_auth_sig[..]) {
|
||||
Ok(sig) => sig,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
self.shielded_spends.push(sapling::SpendDescription {
|
||||
cv,
|
||||
anchor,
|
||||
nullifier: Nullifier(nullifier),
|
||||
rk,
|
||||
zkproof,
|
||||
spend_auth_sig,
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn add_output(
|
||||
self: &mut BundleAssembler,
|
||||
cv: &[u8; 32],
|
||||
cm: &[u8; 32],
|
||||
ephemeral_key: [u8; 32],
|
||||
enc_ciphertext: [u8; 580],
|
||||
out_ciphertext: [u8; 80],
|
||||
zkproof: [u8; 192], // GROTH_PROOF_SIZE
|
||||
) -> bool {
|
||||
// Deserialize the value commitment
|
||||
let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) {
|
||||
Some(p) => p,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Deserialize the commitment, which should be an element
|
||||
// of Fr.
|
||||
let cmu = match de_ct(bls12_381::Scalar::from_bytes(cm)) {
|
||||
Some(a) => a,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
self.shielded_outputs.push(sapling::OutputDescription {
|
||||
cv,
|
||||
cmu,
|
||||
ephemeral_key: EphemeralKeyBytes(ephemeral_key),
|
||||
enc_ciphertext,
|
||||
out_ciphertext,
|
||||
zkproof,
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::boxed_local)]
|
||||
fn finish_bundle_assembly(
|
||||
assembler: Box<BundleAssembler>,
|
||||
value_balance: i64,
|
||||
binding_sig: [u8; 64],
|
||||
) -> Box<Bundle> {
|
||||
let value_balance = Amount::from_i64(value_balance).expect("parsed elsewhere");
|
||||
let binding_sig = redjubjub::Signature::read(&binding_sig[..]).expect("parsed elsewhere");
|
||||
|
||||
Box::new(Bundle(sapling::Bundle {
|
||||
shielded_spends: assembler.shielded_spends,
|
||||
shielded_outputs: assembler.shielded_outputs,
|
||||
value_balance,
|
||||
authorization: sapling::Authorized { binding_sig },
|
||||
}))
|
||||
}
|
||||
|
||||
struct Verifier(SaplingVerificationContext);
|
||||
|
||||
fn init_verifier() -> Box<Verifier> {
|
||||
|
@ -110,7 +260,7 @@ impl Verifier {
|
|||
sighash_value,
|
||||
spend_auth_sig,
|
||||
zkproof,
|
||||
unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap(),
|
||||
&prepare_verifying_key(unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap()),
|
||||
)
|
||||
}
|
||||
fn check_output(
|
||||
|
@ -134,7 +284,7 @@ impl Verifier {
|
|||
};
|
||||
|
||||
// Deserialize the ephemeral key
|
||||
let ephemeral_key = match de_ct(jubjub::ExtendedPoint::from_bytes(ephemeral_key)) {
|
||||
let epk = match de_ct(jubjub::ExtendedPoint::from_bytes(ephemeral_key)) {
|
||||
Some(p) => p,
|
||||
None => return false,
|
||||
};
|
||||
|
@ -148,9 +298,9 @@ impl Verifier {
|
|||
self.0.check_output(
|
||||
cv,
|
||||
cm,
|
||||
ephemeral_key,
|
||||
epk,
|
||||
zkproof,
|
||||
unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap(),
|
||||
&prepare_verifying_key(unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap()),
|
||||
)
|
||||
}
|
||||
fn final_check(
|
||||
|
@ -174,3 +324,43 @@ impl Verifier {
|
|||
.final_check(value_balance, sighash_value, binding_sig)
|
||||
}
|
||||
}
|
||||
|
||||
struct BatchValidator(Option<sapling_proofs::BatchValidator>);
|
||||
|
||||
fn init_batch_validator() -> Box<BatchValidator> {
|
||||
Box::new(BatchValidator(Some(sapling_proofs::BatchValidator::new())))
|
||||
}
|
||||
|
||||
impl BatchValidator {
|
||||
/// Checks the bundle against Sapling-specific consensus rules, and queues its
|
||||
/// authorization for validation.
|
||||
///
|
||||
/// Returns `false` if the bundle doesn't satisfy the checked consensus rules. This
|
||||
/// `BatchValidator` can continue to be used regardless, but some or all of the proofs
|
||||
/// and signatures from this bundle may have already been added to the batch even if
|
||||
/// it fails other consensus rules.
|
||||
///
|
||||
/// `sighash` must be for the transaction this bundle is within.
|
||||
#[allow(clippy::boxed_local)]
|
||||
fn check_bundle(&mut self, bundle: Box<Bundle>, sighash: [u8; 32]) -> bool {
|
||||
if let Some(validator) = &mut self.0 {
|
||||
validator.check_bundle(bundle.0, sighash)
|
||||
} else {
|
||||
tracing::error!("sapling::BatchValidator has already been used");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&mut self) -> bool {
|
||||
if let Some(validator) = self.0.take() {
|
||||
validator.validate(
|
||||
unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap(),
|
||||
unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap(),
|
||||
OsRng,
|
||||
)
|
||||
} else {
|
||||
tracing::error!("sapling::BatchValidator has already been used");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <boost/test/data/test_case.hpp>
|
||||
|
||||
#include <rust/ed25519.h>
|
||||
#include <rust/sapling.h>
|
||||
#include <rust/orchard.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
@ -356,6 +357,7 @@ void test_simple_sapling_invalidity(uint32_t consensusBranchId, CMutableTransact
|
|||
void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransaction tx)
|
||||
{
|
||||
auto verifier = ProofVerifier::Strict();
|
||||
std::optional<rust::Box<sapling::BatchValidator>> saplingAuth = std::nullopt;
|
||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||
{
|
||||
// Ensure that empty vin/vout remain invalid without
|
||||
|
@ -390,7 +392,13 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
|
||||
BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state));
|
||||
BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true));
|
||||
BOOST_CHECK(!ContextualCheckShieldedInputs(newTx, txdata, state, view, orchardAuth, Params().GetConsensus(), consensusBranchId, false, true));
|
||||
BOOST_CHECK(!ContextualCheckShieldedInputs(
|
||||
newTx, txdata,
|
||||
state, view,
|
||||
saplingAuth, orchardAuth,
|
||||
Params().GetConsensus(),
|
||||
consensusBranchId,
|
||||
false, true));
|
||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-invalid-joinsplit-signature");
|
||||
|
||||
// Empty output script.
|
||||
|
@ -406,7 +414,13 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
|||
state = CValidationState();
|
||||
BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state));
|
||||
BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true));
|
||||
BOOST_CHECK(ContextualCheckShieldedInputs(newTx, txdata, state, view, orchardAuth, Params().GetConsensus(), consensusBranchId, false, true));
|
||||
BOOST_CHECK(ContextualCheckShieldedInputs(
|
||||
newTx, txdata,
|
||||
state, view,
|
||||
saplingAuth, orchardAuth,
|
||||
Params().GetConsensus(),
|
||||
consensusBranchId,
|
||||
false, true));
|
||||
BOOST_CHECK_EQUAL(state.GetRejectReason(), "");
|
||||
}
|
||||
{
|
||||
|
|
|
@ -92,7 +92,7 @@ public:
|
|||
std::array<uint8_t, WIDTH> GetRawBytes() const
|
||||
{
|
||||
std::array<uint8_t, WIDTH> buf = {};
|
||||
memcpy(buf.data(), this->begin(), WIDTH);
|
||||
std::memcpy(buf.data(), this->begin(), WIDTH);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue