Implement Orchard authorization batch validator

- 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.
This commit is contained in:
Jack Grigg 2021-06-14 10:27:12 +01:00
parent 3f50ffc3f8
commit af1b9c15bb
5 changed files with 233 additions and 13 deletions

30
Cargo.lock generated
View File

@ -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=63ca1f8d3aaa5bdf136f8ab8e6fab26af1d73002#63ca1f8d3aaa5bdf136f8ab8e6fab26af1d73002"
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 0.0.0 (git+https://github.com/zcash/librustzcash.git?rev=cc533a9da4f6a7209a7be05f82b12a03969152c9)",
]
[[package]]
@ -1686,7 +1688,21 @@ 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=cc533a9da4f6a7209a7be05f82b12a03969152c9#cc533a9da4f6a7209a7be05f82b12a03969152c9"
dependencies = [
"blake2b_simd",
"byteorder",
"crypto_api_chachapoly",
"ff",
"group",
"rand_core 0.6.2",
"subtle",
]
[[package]]
name = "zcash_note_encryption"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff#d88e40113c8dadea751dfcdd72ee90868f9655ff"
dependencies = [
"blake2b_simd",
"byteorder",
@ -1700,7 +1716,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",
@ -1724,13 +1740,13 @@ dependencies = [
"rand_core 0.6.2",
"sha2",
"subtle",
"zcash_note_encryption",
"zcash_note_encryption 0.0.0 (git+https://github.com/zcash/librustzcash.git?rev=d88e40113c8dadea751dfcdd72ee90868f9655ff)",
]
[[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",

View File

@ -60,6 +60,6 @@ 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" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "63ca1f8d3aaa5bdf136f8ab8e6fab26af1d73002" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "d88e40113c8dadea751dfcdd72ee90868f9655ff" }

View File

@ -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>

View File

@ -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

View File

@ -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
}
}