use std::convert::TryInto;
use bellman::groth16::{prepare_verifying_key, Proof};
use group::{cofactor::CofactorGroup, GroupEncoding};
use rand_core::OsRng;
use zcash_note_encryption::EphemeralKeyBytes;
use zcash_primitives::{
merkle_tree::MerklePath,
sapling::{
redjubjub::{self, Signature},
Diversifier, Nullifier, PaymentAddress, ProofGenerationKey, Rseed,
},
transaction::{
components::{sapling, Amount},
txid::{BlockTxCommitmentDigester, TxIdDigester},
Authorized, TransactionDigest,
},
};
use zcash_proofs::{
circuit::sapling::TREE_DEPTH as SAPLING_TREE_DEPTH,
sapling::{self as sapling_proofs, SaplingProvingContext, SaplingVerificationContext},
};
use super::GROTH_PROOF_SIZE;
use super::{
de_ct, SAPLING_OUTPUT_PARAMS, SAPLING_OUTPUT_VK, SAPLING_SPEND_PARAMS, SAPLING_SPEND_VK,
};
use crate::bundlecache::{
sapling_bundle_validity_cache, sapling_bundle_validity_cache_mut, CacheEntries,
};
#[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], 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], ) -> bool;
fn finish_bundle_assembly(
assembler: Box<BundleAssembler>,
value_balance: i64,
binding_sig: [u8; 64],
) -> Box<Bundle>;
type Prover;
fn init_prover() -> Box<Prover>;
#[allow(clippy::too_many_arguments)]
fn create_spend_proof(
self: &mut Prover,
ak: &[u8; 32],
nsk: &[u8; 32],
diversifier: &[u8; 11],
rcm: &[u8; 32],
ar: &[u8; 32],
value: u64,
anchor: &[u8; 32],
merkle_path: &[u8; 1065], cv: &mut [u8; 32],
rk_out: &mut [u8; 32],
zkproof: &mut [u8; 192], ) -> bool;
fn create_output_proof(
self: &mut Prover,
esk: &[u8; 32],
payment_address: &[u8; 43],
rcm: &[u8; 32],
value: u64,
cv: &mut [u8; 32],
zkproof: &mut [u8; 192], ) -> bool;
fn binding_sig(
self: &mut Prover,
value_balance: i64,
sighash: &[u8; 32],
result: &mut [u8; 64],
) -> bool;
type Verifier;
fn init_verifier() -> Box<Verifier>;
#[allow(clippy::too_many_arguments)]
fn check_spend(
self: &mut Verifier,
cv: &[u8; 32],
anchor: &[u8; 32],
nullifier: &[u8; 32],
rk: &[u8; 32],
zkproof: &[u8; 192], spend_auth_sig: &[u8; 64],
sighash_value: &[u8; 32],
) -> bool;
fn check_output(
self: &mut Verifier,
cv: &[u8; 32],
cm: &[u8; 32],
ephemeral_key: &[u8; 32],
zkproof: &[u8; 192], ) -> bool;
fn final_check(
self: &Verifier,
value_balance: i64,
binding_sig: &[u8; 64],
sighash_value: &[u8; 32],
) -> bool;
type BatchValidator;
fn init_batch_validator(cache_store: bool) -> 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>);
impl Bundle {
fn commitment<D: TransactionDigest<Authorized>>(&self, digester: D) -> D::SaplingDigest {
digester.digest_sapling(Some(&self.0))
}
}
struct BundleAssembler {
shielded_spends: Vec<sapling::SpendDescription<sapling::Authorized>>,
shielded_outputs: Vec<sapling::OutputDescription<[u8; 192]>>, }
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], spend_auth_sig: &[u8; 64],
) -> bool {
let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) {
Some(p) => p,
None => return false,
};
let anchor = match de_ct(bls12_381::Scalar::from_bytes(anchor)) {
Some(a) => a,
None => return false,
};
let rk = match redjubjub::PublicKey::read(&rk[..]) {
Ok(p) => p,
Err(_) => return false,
};
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], ) -> bool {
let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) {
Some(p) => p,
None => return false,
};
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 Prover(SaplingProvingContext);
fn init_prover() -> Box<Prover> {
Box::new(Prover(SaplingProvingContext::new()))
}
impl Prover {
#[allow(clippy::too_many_arguments)]
fn create_spend_proof(
&mut self,
ak: &[u8; 32],
nsk: &[u8; 32],
diversifier: &[u8; 11],
rcm: &[u8; 32],
ar: &[u8; 32],
value: u64,
anchor: &[u8; 32],
merkle_path: &[u8; 1 + 33 * SAPLING_TREE_DEPTH + 8],
cv: &mut [u8; 32],
rk_out: &mut [u8; 32],
zkproof: &mut [u8; GROTH_PROOF_SIZE],
) -> bool {
let ak = match de_ct(jubjub::ExtendedPoint::from_bytes(ak)) {
Some(p) => p,
None => return false,
};
let ak = match de_ct(ak.into_subgroup()) {
Some(p) => p,
None => return false,
};
let nsk = match de_ct(jubjub::Scalar::from_bytes(nsk)) {
Some(p) => p,
None => return false,
};
let proof_generation_key = ProofGenerationKey { ak, nsk };
let diversifier = Diversifier(*diversifier);
let rseed = match de_ct(jubjub::Scalar::from_bytes(rcm)) {
Some(p) => Rseed::BeforeZip212(p),
None => return false,
};
let ar = match de_ct(jubjub::Scalar::from_bytes(ar)) {
Some(p) => p,
None => return false,
};
let anchor = match de_ct(bls12_381::Scalar::from_bytes(anchor)) {
Some(p) => p,
None => return false,
};
let merkle_path = match MerklePath::from_slice(merkle_path) {
Ok(w) => w,
Err(_) => return false,
};
let (proof, value_commitment, rk) = self
.0
.spend_proof(
proof_generation_key,
diversifier,
rseed,
ar,
value,
anchor,
merkle_path,
unsafe { SAPLING_SPEND_PARAMS.as_ref() }.unwrap(),
&prepare_verifying_key(unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap()),
)
.expect("proving should not fail");
*cv = value_commitment.to_bytes();
proof
.write(&mut zkproof[..])
.expect("should be able to serialize a proof");
rk.write(&mut rk_out[..])
.expect("should be able to write to rk_out");
true
}
fn create_output_proof(
&mut self,
esk: &[u8; 32],
payment_address: &[u8; 43],
rcm: &[u8; 32],
value: u64,
cv: &mut [u8; 32],
zkproof: &mut [u8; GROTH_PROOF_SIZE],
) -> bool {
let esk = match de_ct(jubjub::Scalar::from_bytes(esk)) {
Some(p) => p,
None => return false,
};
let payment_address = match PaymentAddress::from_bytes(payment_address) {
Some(pa) => pa,
None => return false,
};
let rcm = match de_ct(jubjub::Scalar::from_bytes(rcm)) {
Some(p) => p,
None => return false,
};
let (proof, value_commitment) = self.0.output_proof(
esk,
payment_address,
rcm,
value,
unsafe { SAPLING_OUTPUT_PARAMS.as_ref() }.unwrap(),
);
proof
.write(&mut zkproof[..])
.expect("should be able to serialize a proof");
*cv = value_commitment.to_bytes();
true
}
fn binding_sig(
&mut self,
value_balance: i64,
sighash: &[u8; 32],
result: &mut [u8; 64],
) -> bool {
let value_balance = match Amount::from_i64(value_balance) {
Ok(vb) => vb,
Err(()) => return false,
};
let sig = match self.0.binding_sig(value_balance, sighash) {
Ok(s) => s,
Err(_) => return false,
};
sig.write(&mut result[..])
.expect("result should be 64 bytes");
true
}
}
struct Verifier(SaplingVerificationContext);
fn init_verifier() -> Box<Verifier> {
Box::new(Verifier(SaplingVerificationContext::new(true)))
}
impl Verifier {
#[allow(clippy::too_many_arguments)]
fn check_spend(
&mut self,
cv: &[u8; 32],
anchor: &[u8; 32],
nullifier: &[u8; 32],
rk: &[u8; 32],
zkproof: &[u8; GROTH_PROOF_SIZE],
spend_auth_sig: &[u8; 64],
sighash_value: &[u8; 32],
) -> bool {
let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) {
Some(p) => p,
None => return false,
};
let anchor = match de_ct(bls12_381::Scalar::from_bytes(anchor)) {
Some(a) => a,
None => return false,
};
let rk = match redjubjub::PublicKey::read(&rk[..]) {
Ok(p) => p,
Err(_) => return false,
};
let spend_auth_sig = match Signature::read(&spend_auth_sig[..]) {
Ok(sig) => sig,
Err(_) => return false,
};
let zkproof = match Proof::read(&zkproof[..]) {
Ok(p) => p,
Err(_) => return false,
};
self.0.check_spend(
cv,
anchor,
nullifier,
rk,
sighash_value,
spend_auth_sig,
zkproof,
&prepare_verifying_key(unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap()),
)
}
fn check_output(
&mut self,
cv: &[u8; 32],
cm: &[u8; 32],
ephemeral_key: &[u8; 32],
zkproof: &[u8; GROTH_PROOF_SIZE],
) -> bool {
let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) {
Some(p) => p,
None => return false,
};
let cm = match de_ct(bls12_381::Scalar::from_bytes(cm)) {
Some(a) => a,
None => return false,
};
let epk = match de_ct(jubjub::ExtendedPoint::from_bytes(ephemeral_key)) {
Some(p) => p,
None => return false,
};
let zkproof = match Proof::read(&zkproof[..]) {
Ok(p) => p,
Err(_) => return false,
};
self.0.check_output(
cv,
cm,
epk,
zkproof,
&prepare_verifying_key(unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap()),
)
}
fn final_check(
&self,
value_balance: i64,
binding_sig: &[u8; 64],
sighash_value: &[u8; 32],
) -> bool {
let value_balance = match Amount::from_i64(value_balance) {
Ok(vb) => vb,
Err(()) => return false,
};
let binding_sig = match Signature::read(&binding_sig[..]) {
Ok(sig) => sig,
Err(_) => return false,
};
self.0
.final_check(value_balance, sighash_value, binding_sig)
}
}
struct BatchValidatorInner {
validator: sapling_proofs::BatchValidator,
queued_entries: CacheEntries,
}
struct BatchValidator(Option<BatchValidatorInner>);
fn init_batch_validator(cache_store: bool) -> Box<BatchValidator> {
Box::new(BatchValidator(Some(BatchValidatorInner {
validator: sapling_proofs::BatchValidator::new(),
queued_entries: CacheEntries::new(cache_store),
})))
}
impl BatchValidator {
#[allow(clippy::boxed_local)]
fn check_bundle(&mut self, bundle: Box<Bundle>, sighash: [u8; 32]) -> bool {
if let Some(inner) = &mut self.0 {
let cache = sapling_bundle_validity_cache();
let cache_entry = {
let bundle_commitment = bundle.commitment(TxIdDigester).unwrap();
let bundle_authorizing_commitment = bundle.commitment(BlockTxCommitmentDigester);
cache.compute_entry(
bundle_commitment.as_bytes().try_into().unwrap(),
bundle_authorizing_commitment.as_bytes().try_into().unwrap(),
&sighash,
)
};
if cache.contains(cache_entry, &mut inner.queued_entries) {
true
} else {
inner.validator.check_bundle(bundle.0, sighash)
}
} else {
tracing::error!("sapling::BatchValidator has already been used");
false
}
}
fn validate(&mut self) -> bool {
if let Some(inner) = self.0.take() {
if inner.validator.validate(
unsafe { SAPLING_SPEND_VK.as_ref() }.unwrap(),
unsafe { SAPLING_OUTPUT_VK.as_ref() }.unwrap(),
OsRng,
) {
sapling_bundle_validity_cache_mut().insert(inner.queued_entries);
true
} else {
false
}
} else {
tracing::error!("sapling::BatchValidator has already been used");
false
}
}
}