Implement PCZT support

This commit is contained in:
Jack Grigg 2024-11-28 09:20:24 +00:00
parent 44c4b1724c
commit 7696219bf3
15 changed files with 1310 additions and 68 deletions

View File

@ -7,12 +7,23 @@ and this library adheres to Rust's notion of
## [Unreleased]
### Added
- Support for Partially-Created Zcash Transactions:
- `sapling_crypto::builder::Builder::build_for_pczt`
- `sapling_crypto::pczt` module.
- `sapling_crypto::bundle::EffectsOnly`
- `sapling_crypto::keys`:
- `SpendAuthorizingKey::to_bytes`
- `SpendValidatingKey::to_bytes`
- `sapling_crypto::value::ValueSum::to_raw`
### Fixed
- `sapling_crypto::prover::OutputProver::prepare_circuit` now takes `esk` as an
`sapling_crypto::keys::EphemeralSecretKey`, matching the existing public APIs
that expose it.
### Changed
- `sapling_crypto::builder::Error` has a new variant `PcztRequiresZip212`.
- `sapling_crypto::bundle::SpendDescriptionV5::into_spend_description` now
supports any `Authorization` for which the `SpendDescription` itself is fully
authorized.

39
Cargo.lock generated
View File

@ -566,6 +566,18 @@ dependencies = [
"wasi",
]
[[package]]
name = "getset"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c"
dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "gimli"
version = "0.28.1"
@ -1029,6 +1041,28 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.70"
@ -1287,6 +1321,7 @@ dependencies = [
"document-features",
"ff",
"fpe",
"getset",
"group",
"hex",
"incrementalmerkletree",
@ -1369,9 +1404,9 @@ checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
[[package]]
name = "subtle"
version = "2.5.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "symbolic-common"

View File

@ -26,6 +26,9 @@ jubjub = "0.10"
redjubjub = "0.7"
zcash_spec = "0.1"
# Boilerplate
getset = "0.1"
# Circuits
bellman = { version = "0.14", default-features = false, features = ["groth16"] }

View File

@ -1,20 +1,22 @@
//! Types and functions for building Sapling transaction components.
use core::fmt;
use std::{iter, marker::PhantomData};
use std::{collections::BTreeMap, iter, marker::PhantomData};
use group::ff::Field;
use incrementalmerkletree::Position;
use rand::{seq::SliceRandom, RngCore};
use rand_core::CryptoRng;
use redjubjub::{Binding, SpendAuth};
use zcash_note_encryption::EphemeralKeyBytes;
use crate::{
bundle::{
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription,
},
circuit,
keys::{OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey},
keys::{EphemeralSecretKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey},
note::ExtractedNoteCommitment,
note_encryption::{sapling_note_encryption, Zip212Enforcement},
prover::{OutputProver, SpendProver},
util::generate_random_rseed_internal,
@ -22,8 +24,8 @@ use crate::{
CommitmentSum, NoteValue, TrapdoorSum, ValueCommitTrapdoor, ValueCommitment, ValueSum,
},
zip32::ExtendedSpendingKey,
Anchor, Diversifier, MerklePath, Node, Note, PaymentAddress, ProofGenerationKey, SaplingIvk,
NOTE_COMMITMENT_TREE_DEPTH,
Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, ProofGenerationKey,
SaplingIvk, NOTE_COMMITMENT_TREE_DEPTH,
};
/// If there are any shielded inputs, always have at least two shielded outputs, padding
@ -121,6 +123,8 @@ pub enum Error {
InvalidExternalSignature,
/// A bundle could not be built because required signatures were missing.
MissingSignatures,
/// [`Builder::build_for_pczt`] requires [`Zip212Enforcement::On`].
PcztRequiresZip212,
SpendProof,
/// The bundle being constructed violated the construction rules for the requested bundle type.
BundleTypeNotSatisfiable,
@ -138,6 +142,9 @@ impl fmt::Display for Error {
Error::InvalidAmount => write!(f, "Invalid amount"),
Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
Error::PcztRequiresZip212 => {
write!(f, "PCZTs require that ZIP 212 is enforced for outputs")
}
Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
Error::BundleTypeNotSatisfiable => {
f.write_str("Bundle structure did not conform to requested bundle type.")
@ -226,15 +233,18 @@ struct PreparedSpendInfo {
}
impl PreparedSpendInfo {
fn build<Pr: SpendProver, R: RngCore>(
self,
fn build_inner<R: RngCore>(
&self,
mut rng: R,
) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
) -> (
ValueCommitment,
Nullifier,
redjubjub::VerificationKey<SpendAuth>,
jubjub::Scalar,
) {
// Construct the value commitment.
let alpha = jubjub::Fr::random(&mut rng);
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
let node = Node::from_cmu(&self.note.cmu());
let anchor = *self.merkle_path.root(node).inner();
let ak = self.proof_generation_key.ak.clone();
@ -247,6 +257,21 @@ impl PreparedSpendInfo {
.expect("Sapling note commitment tree position must fit into a u64"),
);
(cv, nullifier, rk, alpha)
}
fn build<Pr: SpendProver, R: RngCore>(
self,
rng: R,
) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
let (cv, nullifier, rk, alpha) = self.build_inner(rng);
// Construct the value commitment.
let node = Node::from_cmu(&self.note.cmu());
let anchor = *self.merkle_path.root(node).inner();
let ak = self.proof_generation_key.ak.clone();
let zkproof = Pr::prepare_circuit(
self.proof_generation_key,
*self.note.recipient().diversifier(),
@ -271,6 +296,28 @@ impl PreparedSpendInfo {
},
))
}
fn into_pczt<R: RngCore>(self, rng: R) -> crate::pczt::Spend {
let (cv, nullifier, rk, alpha) = self.build_inner(rng);
crate::pczt::Spend {
cv,
nullifier,
rk,
zkproof: None,
spend_auth_sig: None,
recipient: Some(self.note.recipient()),
value: Some(self.note.value()),
rseed: Some(*self.note.rseed()),
rcv: Some(self.rcv),
proof_generation_key: Some(self.proof_generation_key),
witness: Some(self.merkle_path),
alpha: Some(alpha),
zip32_derivation: None,
dummy_ask: self.dummy_ask,
proprietary: BTreeMap::new(),
}
}
}
/// A struct containing the information required in order to construct a
@ -358,23 +405,24 @@ struct PreparedOutputInfo {
}
impl PreparedOutputInfo {
fn build<Pr: OutputProver, R: RngCore>(
self,
fn build_inner<P, R: RngCore>(
&self,
zkproof: impl FnOnce(&EphemeralSecretKey) -> P,
rng: &mut R,
) -> OutputDescription<circuit::Output> {
) -> (
ValueCommitment,
ExtractedNoteCommitment,
EphemeralKeyBytes,
[u8; 580],
[u8; 80],
P,
) {
let encryptor = sapling_note_encryption::<R>(self.ovk, self.note.clone(), self.memo, rng);
// Construct the value commitment.
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
// Prepare the circuit that will be used to construct the proof.
let zkproof = Pr::prepare_circuit(
encryptor.esk(),
self.note.recipient(),
self.note.rcm(),
self.note.value(),
self.rcv,
);
let zkproof = zkproof(encryptor.esk());
let cmu = self.note.cmu();
@ -383,7 +431,7 @@ impl PreparedOutputInfo {
let epk = encryptor.epk();
OutputDescription::from_parts(
(
cv,
cmu,
epk.to_bytes(),
@ -392,6 +440,62 @@ impl PreparedOutputInfo {
zkproof,
)
}
fn build<Pr: OutputProver, R: RngCore>(
self,
rng: &mut R,
) -> OutputDescription<circuit::Output> {
let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof) = self.build_inner(
|esk| {
Pr::prepare_circuit(
esk,
self.note.recipient(),
self.note.rcm(),
self.note.value(),
self.rcv.clone(),
)
},
rng,
);
OutputDescription::from_parts(
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof,
)
}
fn into_pczt<R: RngCore>(self, rng: &mut R) -> crate::pczt::Output {
let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, _) =
self.build_inner(|_| (), rng);
let rseed = match self.note.rseed() {
crate::Rseed::BeforeZip212(_) => {
panic!("Builder::build_for_pczt should prevent pre-ZIP 212 outputs")
}
crate::Rseed::AfterZip212(rseed) => Some(*rseed),
};
crate::pczt::Output {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof: None,
recipient: Some(self.note.recipient()),
value: Some(self.note.value()),
rseed,
rcv: Some(self.rcv),
// TODO: Save this?
ock: None,
zip32_derivation: None,
proprietary: BTreeMap::new(),
}
}
}
/// Metadata about a transaction created by a [`Builder`].
@ -549,18 +653,132 @@ impl Builder {
self.outputs,
)
}
/// Builds a bundle containing the given spent notes and outputs along with their
/// metadata, for inclusion in a PCZT.
pub fn build_for_pczt(
self,
rng: impl RngCore,
) -> Result<(crate::pczt::Bundle, SaplingMetadata), Error> {
match self.zip212_enforcement {
Zip212Enforcement::Off | Zip212Enforcement::GracePeriod => {
Err(Error::PcztRequiresZip212)
}
Zip212Enforcement::On => build_bundle::<_, (), (), _>(
rng,
self.bundle_type,
Zip212Enforcement::On,
self.anchor,
self.spends,
self.outputs,
|spend_infos, output_infos, value_sum, tx_metadata, mut rng| {
// Create the PCZT Spends and Outputs.
let spends = spend_infos
.into_iter()
.map(|a| a.into_pczt(&mut rng))
.collect::<Vec<_>>();
let outputs = output_infos
.into_iter()
.map(|a| a.into_pczt(&mut rng))
.collect::<Vec<_>>();
Ok((
crate::pczt::Bundle {
spends,
outputs,
value_sum,
anchor: self.anchor,
bsk: None,
},
tx_metadata,
))
},
),
}
}
}
/// Constructs a new Sapling transaction bundle of the given type from the specified set of spends
/// and outputs.
pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
mut rng: R,
rng: R,
bundle_type: BundleType,
zip212_enforcement: Zip212Enforcement,
anchor: Anchor,
spends: Vec<SpendInfo>,
outputs: Vec<OutputInfo>,
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
build_bundle::<_, SP, OP, _>(
rng,
bundle_type,
zip212_enforcement,
anchor,
spends,
outputs,
|spend_infos, output_infos, value_balance, tx_metadata, mut rng| {
let value_balance_i64 =
i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?;
// Compute the transaction binding signing key.
let bsk = {
let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
(spends - outputs).into_bsk()
};
// Create the unauthorized Spend and Output descriptions.
let shielded_spends = spend_infos
.into_iter()
.map(|a| a.build::<SP, _>(&mut rng))
.collect::<Result<Vec<_>, _>>()?;
let shielded_outputs = output_infos
.into_iter()
.map(|a| a.build::<OP, _>(&mut rng))
.collect::<Vec<_>>();
// Verify that bsk and bvk are consistent.
let bvk = {
let spends = shielded_spends
.iter()
.map(|spend| spend.cv())
.sum::<CommitmentSum>();
let outputs = shielded_outputs
.iter()
.map(|output| output.cv())
.sum::<CommitmentSum>();
(spends - outputs).into_bvk(value_balance_i64)
};
assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
Ok(Bundle::from_parts(
shielded_spends,
shielded_outputs,
V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?,
InProgress {
sigs: Unsigned { bsk },
_proof_state: PhantomData::default(),
},
)
.map(|b| (b, tx_metadata)))
},
)
}
fn build_bundle<B, SP, OP, R: RngCore>(
mut rng: R,
bundle_type: BundleType,
zip212_enforcement: Zip212Enforcement,
anchor: Anchor,
spends: Vec<SpendInfo>,
outputs: Vec<OutputInfo>,
finisher: impl FnOnce(
Vec<PreparedSpendInfo>,
Vec<PreparedOutputInfo>,
ValueSum,
SaplingMetadata,
R,
) -> Result<B, Error>,
) -> Result<B, Error> {
match bundle_type {
BundleType::Transactional { .. } => {
for spend in &spends {
@ -640,13 +858,6 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
})
.collect::<Vec<_>>();
// Compute the transaction binding signing key.
let bsk = {
let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
(spends - outputs).into_bsk()
};
// Compute the Sapling value balance of the bundle for comparison to `bvk` and `bsk`
let input_total = spend_infos
.iter()
@ -658,42 +869,8 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
.try_fold(input_total, |balance, output| {
(balance - output.note.value()).ok_or(Error::InvalidAmount)
})?;
let value_balance_i64 = i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?;
// Create the unauthorized Spend and Output descriptions.
let shielded_spends = spend_infos
.into_iter()
.map(|a| a.build::<SP, _>(&mut rng))
.collect::<Result<Vec<_>, _>>()?;
let shielded_outputs = output_infos
.into_iter()
.map(|a| a.build::<OP, _>(&mut rng))
.collect::<Vec<_>>();
// Verify that bsk and bvk are consistent.
let bvk = {
let spends = shielded_spends
.iter()
.map(|spend| spend.cv())
.sum::<CommitmentSum>();
let outputs = shielded_outputs
.iter()
.map(|output| output.cv())
.sum::<CommitmentSum>();
(spends - outputs).into_bvk(value_balance_i64)
};
assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
Ok(Bundle::from_parts(
shielded_spends,
shielded_outputs,
V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?,
InProgress {
sigs: Unsigned { bsk },
_proof_state: PhantomData::default(),
},
)
.map(|b| (b, tx_metadata)))
finisher(spend_infos, output_infos, value_balance, tx_metadata, rng)
}
/// Type alias for an in-progress bundle that has no proofs or signatures.

View File

@ -25,6 +25,16 @@ pub trait Authorization: Debug {
type AuthSig: Clone + Debug;
}
/// Marker type for a bundle that contains no authorizing data.
#[derive(Debug)]
pub struct EffectsOnly;
impl Authorization for EffectsOnly {
type SpendProof = ();
type OutputProof = ();
type AuthSig = ();
}
/// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to
/// the ledger.
#[derive(Debug, Copy, Clone)]

View File

@ -108,7 +108,7 @@ impl SpendAuthorizingKey {
}
/// Converts this spend authorizing key to its serialized form.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
pub fn to_bytes(&self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}
@ -193,7 +193,7 @@ impl SpendValidatingKey {
/// Converts this spend validating key to its serialized form,
/// `LEBS2OSP_256(repr_J(ak))`.
pub(crate) fn to_bytes(&self) -> [u8; 32] {
pub fn to_bytes(&self) -> [u8; 32] {
<[u8; 32]>::from(self.0)
}

View File

@ -25,6 +25,7 @@ pub mod group_hash;
pub mod keys;
pub mod note;
pub mod note_encryption;
pub mod pczt;
pub mod pedersen_hash;
pub mod prover;
mod spec;

294
src/pczt.rs Normal file
View File

@ -0,0 +1,294 @@
//! PCZT support for Sapling.
use core::fmt;
use std::collections::BTreeMap;
use getset::Getters;
use redjubjub::{Binding, SpendAuth};
use zcash_note_encryption::{
EphemeralKeyBytes, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
};
use zip32::ChildIndex;
use crate::{
bundle::GrothProofBytes,
keys::SpendAuthorizingKey,
note::ExtractedNoteCommitment,
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
Anchor, MerklePath, Nullifier, PaymentAddress, ProofGenerationKey, Rseed,
};
mod parse;
pub use parse::ParseError;
mod io_finalizer;
pub use io_finalizer::IoFinalizerError;
mod prover;
pub use prover::ProverError;
mod signer;
pub use signer::SignerError;
mod tx_extractor;
pub use tx_extractor::{TxExtractorError, Unbound};
/// PCZT fields that are specific to producing the transaction's Sapling bundle (if any).
///
/// This struct is for representing Sapling in a partially-created transaction. If you
/// have a fully-created transaction, use [the regular `Bundle` struct].
///
/// [the regular `Bundle` struct]: crate::Bundle
#[derive(Debug, Getters)]
#[getset(get = "pub")]
pub struct Bundle {
/// The Sapling spends in this bundle.
///
/// Entries are added by the Constructor, and modified by an Updater, IO Finalizer,
/// Prover, Signer, Combiner, or Spend Finalizer.
pub(crate) spends: Vec<Spend>,
/// The Sapling outputs in this bundle.
///
/// Entries are added by the Constructor, and modified by an Updater, IO Finalizer,
/// Prover, Signer, Combiner, or Spend Finalizer.
pub(crate) outputs: Vec<Output>,
/// The net value of Sapling `spends` minus `outputs`.
///
/// This is initialized by the Creator, and updated by the Constructor as spends or
/// outputs are added to the PCZT. It enables per-spend and per-output values to be
/// redacted from the PCZT after they are no longer necessary.
pub(crate) value_sum: ValueSum,
/// The Sapling anchor for this transaction.
///
/// Set by the Creator.
pub(crate) anchor: Anchor,
/// The Sapling binding signature signing key.
///
/// - This is `None` until it is set by the IO Finalizer.
/// - The Transaction Extractor uses this to produce the binding signature.
pub(crate) bsk: Option<redjubjub::SigningKey<Binding>>,
}
impl Bundle {
/// Returns a mutable reference to the spends in this bundle.
///
/// This is used by Signers to apply signatures with [`Spend::sign`].
pub fn spends_mut(&mut self) -> &mut [Spend] {
&mut self.spends
}
}
/// Information about a Sapling spend within a transaction.
///
/// This struct is for representing Sapling spends in a partially-created transaction. If
/// you have a fully-created transaction, use [the regular `SpendDescription` struct].
///
/// [the regular `SpendDescription` struct]: crate::bundle::SpendDescription
#[derive(Debug, Getters)]
#[getset(get = "pub")]
pub struct Spend {
/// A commitment to the value consumed by this spend.
pub(crate) cv: ValueCommitment,
/// The nullifier of the note being spent.
pub(crate) nullifier: Nullifier,
/// The randomized verification key for the note being spent.
pub(crate) rk: redjubjub::VerificationKey<SpendAuth>,
/// The Spend proof.
///
/// This is set by the Prover.
pub(crate) zkproof: Option<GrothProofBytes>,
/// The spend authorization signature.
///
/// This is set by the Signer.
pub(crate) spend_auth_sig: Option<redjubjub::Signature<SpendAuth>>,
/// The address that received the note being spent.
///
/// - This is set by the Constructor (or Updater?).
/// - This is required by the Prover.
pub(crate) recipient: Option<PaymentAddress>,
/// The value of the input being spent.
///
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
/// the values and change involved in the transaction.
///
/// This exposes the input value to all participants. For Signers who don't need this
/// information, or after signatures have been applied, this can be redacted.
pub(crate) value: Option<NoteValue>,
/// The seed randomness for the note being spent.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
pub(crate) rseed: Option<Rseed>,
/// The value commitment randomness.
///
/// - This is set by the Constructor.
/// - The IO Finalizer compresses it into the `bsk`.
/// - This is required by the Prover.
/// - This may be used by Signers to verify that the value correctly matches `cv`.
///
/// This opens `cv` for all participants. For Signers who don't need this information,
/// or after proofs / signatures have been applied, this can be redacted.
pub(crate) rcv: Option<ValueCommitTrapdoor>,
/// The proof generation key `(ak, nsk)` corresponding to the recipient that received
/// the note being spent.
///
/// - This is set by the Updater.
/// - This is required by the Prover.
pub(crate) proof_generation_key: Option<ProofGenerationKey>,
/// A witness from the note to the bundle's anchor.
///
/// - This is set by the Updater.
/// - This is required by the Prover.
pub(crate) witness: Option<MerklePath>,
/// The spend authorization randomizer.
///
/// - This is chosen by the Constructor.
/// - This is required by the Signer for creating `spend_auth_sig`, and may be used to
/// validate `rk`.
/// - After`zkproof` / `spend_auth_sig` has been set, this can be redacted.
pub(crate) alpha: Option<jubjub::Scalar>,
/// The ZIP 32 derivation path at which the spending key can be found for the note
/// being spent.
pub(crate) zip32_derivation: Option<Zip32Derivation>,
/// The spend authorizing key for this spent note, if it is a dummy note.
///
/// - This is chosen by the Constructor.
/// - This is required by the IO Finalizer, and is cleared by it once used.
/// - Signers MUST reject PCZTs that contain `dummy_ask` values.
pub(crate) dummy_ask: Option<SpendAuthorizingKey>,
/// Proprietary fields related to the note being spent.
pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
}
/// Information about a Sapling output within a transaction.
///
/// This struct is for representing Sapling outputs in a partially-created transaction. If
/// you have a fully-created transaction, use [the regular `OutputDescription` struct].
///
/// [the regular `OutputDescription` struct]: crate::bundle::OutputDescription
#[derive(Getters)]
#[getset(get = "pub")]
pub struct Output {
/// A commitment to the value created by this output.
pub(crate) cv: ValueCommitment,
/// A commitment to the new note being created.
pub(crate) cmu: ExtractedNoteCommitment,
/// The ephemeral key used to encrypt the note plaintext.
pub(crate) ephemeral_key: EphemeralKeyBytes,
/// The encrypted note plaintext for the output.
///
/// Once we have memo bundles, we will be able to set memos independently of Outputs.
/// For now, the Constructor sets both at the same time.
pub(crate) enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
/// The encrypted output plaintext for the output.
pub(crate) out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
/// The Output proof.
///
/// This is set by the Prover.
pub(crate) zkproof: Option<GrothProofBytes>,
/// The address that will receive the output.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
/// - The Signer can use `recipient` and `rseed` (if present) to verify that
/// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching
/// the public commitments), and to confirm the value of the memo.
pub(crate) recipient: Option<PaymentAddress>,
/// The value of the output.
///
/// This may be used by Signers to verify that the value matches `cv`, and to confirm
/// the values and change involved in the transaction.
///
/// This exposes the output value to all participants. For Signers who don't need this
/// information, or after signatures have been applied, this can be redacted.
pub(crate) value: Option<NoteValue>,
/// The seed randomness for the output.
///
/// - This is set by the Constructor.
/// - This is required by the Prover.
/// - The Signer can use `recipient` and `rseed` (if present) to verify that
/// `enc_ciphertext` is correctly encrypted (and contains a note plaintext matching
/// the public commitments), and to confirm the value of the memo.
pub(crate) rseed: Option<[u8; 32]>,
/// The value commitment randomness.
///
/// - This is set by the Constructor.
/// - The IO Finalizer compresses it into the bsk.
/// - This is required by the Prover.
/// - This may be used by Signers to verify that the value correctly matches `cv`.
///
/// This opens `cv` for all participants. For Signers who don't need this information,
/// or after proofs / signatures have been applied, this can be redacted.
pub(crate) rcv: Option<ValueCommitTrapdoor>,
/// The `ock` value used to encrypt `out_ciphertext`.
///
/// This enables Signers to verify that `out_ciphertext` is correctly encrypted.
///
/// This may be `None` if the Constructor added the output using an OVK policy of
/// "None", to make the output unrecoverable from the chain by the sender.
pub(crate) ock: Option<OutgoingCipherKey>,
/// The ZIP 32 derivation path at which the spending key can be found for the output.
pub(crate) zip32_derivation: Option<Zip32Derivation>,
/// Proprietary fields related to the note being created.
pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
}
impl fmt::Debug for Output {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Output")
.field("cv", &self.cv)
.field("cmu", &self.cmu)
.field("ephemeral_key", &self.ephemeral_key)
.field("enc_ciphertext", &self.enc_ciphertext)
.field("out_ciphertext", &self.out_ciphertext)
.field("zkproof", &self.zkproof)
.field("recipient", &self.recipient)
.field("value", &self.value)
.field("rseed", &self.rseed)
.field("rcv", &self.rcv)
.field("zip32_derivation", &self.zip32_derivation)
.field("proprietary", &self.proprietary)
.finish_non_exhaustive()
}
}
/// The ZIP 32 derivation path at which a key can be found.
#[derive(Debug, Getters, PartialEq, Eq)]
#[getset(get = "pub")]
pub struct Zip32Derivation {
/// The [ZIP 32 seed fingerprint](https://zips.z.cash/zip-0032#seed-fingerprints).
seed_fingerprint: [u8; 32],
/// The sequence of indices corresponding to the shielded HD path.
derivation_path: Vec<ChildIndex>,
}

91
src/pczt/io_finalizer.rs Normal file
View File

@ -0,0 +1,91 @@
use rand::{CryptoRng, RngCore};
use crate::value::{CommitmentSum, TrapdoorSum};
use super::SignerError;
impl super::Bundle {
/// Finalizes the IO for this bundle.
pub fn finalize_io<R: RngCore + CryptoRng>(
&mut self,
sighash: [u8; 32],
mut rng: R,
) -> Result<(), IoFinalizerError> {
// Compute the transaction binding signing key.
let bsk = {
let spend_rcvs = self
.spends
.iter()
.map(|spend| {
spend
.rcv
.as_ref()
.ok_or(IoFinalizerError::MissingValueCommitTrapdoor)
})
.collect::<Result<Vec<_>, _>>()?;
let output_rcvs = self
.outputs
.iter()
.map(|output| {
output
.rcv
.as_ref()
.ok_or(IoFinalizerError::MissingValueCommitTrapdoor)
})
.collect::<Result<Vec<_>, _>>()?;
let spends: TrapdoorSum = spend_rcvs.into_iter().sum();
let outputs: TrapdoorSum = output_rcvs.into_iter().sum();
(spends - outputs).into_bsk()
};
// Verify that bsk and bvk are consistent.
let bvk = {
let spends = self
.spends
.iter()
.map(|spend| spend.cv())
.sum::<CommitmentSum>();
let outputs = self
.outputs
.iter()
.map(|output| output.cv())
.sum::<CommitmentSum>();
(spends - outputs).into_bvk(
i64::try_from(self.value_sum).map_err(|_| IoFinalizerError::InvalidValueSum)?,
)
};
if redjubjub::VerificationKey::from(&bsk) != bvk {
return Err(IoFinalizerError::ValueCommitMismatch);
}
self.bsk = Some(bsk);
// Add signatures to dummy spends.
for spend in self.spends.iter_mut() {
// The `Option::take` ensures we don't have any spend authorizing keys in the
// PCZT after the IO Finalizer has run.
if let Some(ask) = spend.dummy_ask.take() {
spend
.sign(sighash, &ask, &mut rng)
.map_err(IoFinalizerError::DummySignature)?;
}
}
Ok(())
}
}
/// Errors that can occur while finalizing the I/O for a PCZT bundle.
#[derive(Debug)]
pub enum IoFinalizerError {
/// An error occurred while signing a dummy spend.
DummySignature(SignerError),
/// The `value_sum` is too large for the `value_balance` field.
InvalidValueSum,
/// The IO Finalizer role requires all `rcv` fields to be set.
MissingValueCommitTrapdoor,
/// The `cv_net`, `rcv`, and `value_sum` values within the Orchard bundle are
/// inconsistent.
ValueCommitMismatch,
}

294
src/pczt/parse.rs Normal file
View File

@ -0,0 +1,294 @@
use std::collections::BTreeMap;
use ff::PrimeField;
use zcash_note_encryption::{EphemeralKeyBytes, OutgoingCipherKey};
use zip32::ChildIndex;
use super::{Bundle, Output, Spend, Zip32Derivation};
use crate::{
bundle::GrothProofBytes,
keys::{SpendAuthorizingKey, SpendValidatingKey},
note::ExtractedNoteCommitment,
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
Anchor, MerklePath, Node, Nullifier, PaymentAddress, ProofGenerationKey, Rseed,
};
impl Bundle {
/// Parses a PCZT bundle from its component parts.
pub fn parse(
spends: Vec<Spend>,
outputs: Vec<Output>,
value_sum: i128,
anchor: [u8; 32],
bsk: Option<[u8; 32]>,
) -> Result<Self, ParseError> {
let value_sum = ValueSum::from_raw(value_sum);
let anchor = Anchor::from_bytes(anchor)
.into_option()
.ok_or(ParseError::InvalidAnchor)?;
let bsk = bsk
.map(redjubjub::SigningKey::try_from)
.transpose()
.map_err(|_| ParseError::InvalidBindingSignatureSigningKey)?;
Ok(Self {
spends,
outputs,
value_sum,
anchor,
bsk,
})
}
}
impl Spend {
/// Parses a PCZT spend from its component parts.
#[allow(clippy::too_many_arguments)]
pub fn parse(
cv: [u8; 32],
nullifier: [u8; 32],
rk: [u8; 32],
zkproof: Option<GrothProofBytes>,
spend_auth_sig: Option<[u8; 64]>,
recipient: Option<[u8; 43]>,
value: Option<u64>,
rcm: Option<[u8; 32]>,
rseed: Option<[u8; 32]>,
rcv: Option<[u8; 32]>,
proof_generation_key: Option<([u8; 32], [u8; 32])>,
witness: Option<(u32, [[u8; 32]; 32])>,
alpha: Option<[u8; 32]>,
zip32_derivation: Option<Zip32Derivation>,
dummy_ask: Option<[u8; 32]>,
proprietary: BTreeMap<String, Vec<u8>>,
) -> Result<Self, ParseError> {
let cv = ValueCommitment::from_bytes_not_small_order(&cv)
.into_option()
.ok_or(ParseError::InvalidValueCommitment)?;
let nullifier = Nullifier(nullifier);
let rk = redjubjub::VerificationKey::try_from(rk)
.map_err(|_| ParseError::InvalidRandomizedKey)?;
let spend_auth_sig = spend_auth_sig.map(redjubjub::Signature::from);
let recipient = recipient
.as_ref()
.map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient))
.transpose()?;
let value = value.map(NoteValue::from_raw);
let rseed = match (rcm, rseed) {
(None, None) => Ok(None),
(Some(rcm), None) => jubjub::Scalar::from_repr(rcm)
.into_option()
.ok_or(ParseError::InvalidNoteCommitRandomness)
.map(Rseed::BeforeZip212)
.map(Some),
(None, Some(rseed)) => Ok(Some(Rseed::AfterZip212(rseed))),
(Some(_), Some(_)) => Err(ParseError::MixedNoteCommitRandomnessAndRseed),
}?;
let rcv = rcv
.map(|rcv| {
ValueCommitTrapdoor::from_bytes(rcv)
.into_option()
.ok_or(ParseError::InvalidValueCommitTrapdoor)
})
.transpose()?;
let proof_generation_key = proof_generation_key
.map(|(ak, nsk)| {
Ok(ProofGenerationKey {
ak: SpendValidatingKey::from_bytes(&ak)
.ok_or(ParseError::InvalidProofGenerationKey)?,
nsk: jubjub::Scalar::from_repr(nsk)
.into_option()
.ok_or(ParseError::InvalidProofGenerationKey)?,
})
})
.transpose()?;
let witness = witness
.map(|(position, auth_path_bytes)| {
let path_elems = auth_path_bytes
.into_iter()
.map(|hash| {
Node::from_bytes(hash)
.into_option()
.ok_or(ParseError::InvalidWitness)
})
.collect::<Result<Vec<_>, _>>()?;
MerklePath::from_parts(path_elems, u64::from(position).into())
.map_err(|()| ParseError::InvalidWitness)
})
.transpose()?;
let alpha = alpha
.map(|alpha| {
jubjub::Scalar::from_repr(alpha)
.into_option()
.ok_or(ParseError::InvalidSpendAuthRandomizer)
})
.transpose()?;
let dummy_ask = dummy_ask
.map(|dummy_ask| {
SpendAuthorizingKey::from_bytes(&dummy_ask)
.ok_or(ParseError::InvalidDummySpendAuthorizingKey)
})
.transpose()?;
Ok(Self {
cv,
nullifier,
rk,
zkproof,
spend_auth_sig,
recipient,
value,
rseed,
rcv,
proof_generation_key,
witness,
alpha,
zip32_derivation,
dummy_ask,
proprietary,
})
}
}
impl Output {
/// Parses a PCZT output from its component parts.
#[allow(clippy::too_many_arguments)]
pub fn parse(
cv: [u8; 32],
cmu: [u8; 32],
ephemeral_key: [u8; 32],
enc_ciphertext: Vec<u8>,
out_ciphertext: Vec<u8>,
zkproof: Option<GrothProofBytes>,
recipient: Option<[u8; 43]>,
value: Option<u64>,
rseed: Option<[u8; 32]>,
rcv: Option<[u8; 32]>,
ock: Option<[u8; 32]>,
zip32_derivation: Option<Zip32Derivation>,
proprietary: BTreeMap<String, Vec<u8>>,
) -> Result<Self, ParseError> {
let cv = ValueCommitment::from_bytes_not_small_order(&cv)
.into_option()
.ok_or(ParseError::InvalidValueCommitment)?;
let cmu = ExtractedNoteCommitment::from_bytes(&cmu)
.into_option()
.ok_or(ParseError::InvalidExtractedNoteCommitment)?;
let ephemeral_key = EphemeralKeyBytes(ephemeral_key);
let enc_ciphertext = enc_ciphertext
.as_slice()
.try_into()
.map_err(|_| ParseError::InvalidEncCiphertext)?;
let out_ciphertext = out_ciphertext
.as_slice()
.try_into()
.map_err(|_| ParseError::InvalidOutCiphertext)?;
let recipient = recipient
.as_ref()
.map(|r| PaymentAddress::from_bytes(r).ok_or(ParseError::InvalidRecipient))
.transpose()?;
let value = value.map(NoteValue::from_raw);
let rcv = rcv
.map(|rcv| {
ValueCommitTrapdoor::from_bytes(rcv)
.into_option()
.ok_or(ParseError::InvalidValueCommitTrapdoor)
})
.transpose()?;
let ock = ock.map(OutgoingCipherKey);
Ok(Self {
cv,
cmu,
ephemeral_key,
enc_ciphertext,
out_ciphertext,
zkproof,
recipient,
value,
rseed,
rcv,
ock,
zip32_derivation,
proprietary,
})
}
}
impl Zip32Derivation {
/// Parses a ZIP 32 derivation path from its component parts.
///
/// Returns an error if any of the derivation path indices are non-hardened (which
/// this crate does not support, even though Sapling does).
pub fn parse(
seed_fingerprint: [u8; 32],
derivation_path: Vec<u32>,
) -> Result<Self, ParseError> {
Ok(Self {
seed_fingerprint,
derivation_path: derivation_path
.into_iter()
.map(|i| ChildIndex::from_index(i).ok_or(ParseError::InvalidZip32Derivation))
.collect::<Result<_, _>>()?,
})
}
}
/// Errors that can occur while parsing a PCZT bundle.
#[derive(Debug)]
pub enum ParseError {
/// An invalid `anchor` was provided.
InvalidAnchor,
/// An invalid `bsk` was provided.
InvalidBindingSignatureSigningKey,
/// An invalid `dummy_ask` was provided.
InvalidDummySpendAuthorizingKey,
/// An invalid `enc_ciphertext` was provided.
InvalidEncCiphertext,
/// An invalid `cmu` was provided.
InvalidExtractedNoteCommitment,
/// An invalid `rcm` was provided.
InvalidNoteCommitRandomness,
/// An invalid `out_ciphertext` was provided.
InvalidOutCiphertext,
/// An invalid `proof_generation_key` was provided.
InvalidProofGenerationKey,
/// An invalid `rk` was provided.
InvalidRandomizedKey,
/// An invalid `recipient` was provided.
InvalidRecipient,
/// An invalid `alpha` was provided.
InvalidSpendAuthRandomizer,
/// An invalid `cv` was provided.
InvalidValueCommitment,
/// An invalid `rcv` was provided.
InvalidValueCommitTrapdoor,
/// An invalid `witness` was provided.
InvalidWitness,
/// An invalid `zip32_derivation` was provided.
InvalidZip32Derivation,
/// Both `rcm` and `rseed` were provided for a Spend.
MixedNoteCommitRandomnessAndRseed,
}

105
src/pczt/prover.rs Normal file
View File

@ -0,0 +1,105 @@
use rand::{CryptoRng, RngCore};
use crate::{
prover::{OutputProver, SpendProver},
Note, Rseed,
};
impl super::Bundle {
/// Adds a proof to this PCZT bundle.
pub fn create_proofs<S, O, R: RngCore + CryptoRng>(
&mut self,
spend_prover: &S,
output_prover: &O,
mut rng: R,
) -> Result<(), ProverError>
where
S: SpendProver,
O: OutputProver,
{
for spend in &mut self.spends {
let proof_generation_key = spend
.proof_generation_key
.clone()
.ok_or(ProverError::MissingProofGenerationKey)?;
let note = Note::from_parts(
spend.recipient.ok_or(ProverError::MissingRecipient)?,
spend.value.ok_or(ProverError::MissingValue)?,
spend.rseed.ok_or(ProverError::MissingRandomSeed)?,
);
let alpha = spend.alpha.ok_or(ProverError::MissingSpendAuthRandomizer)?;
let rcv = spend
.rcv
.clone()
.ok_or(ProverError::MissingValueCommitTrapdoor)?;
let merkle_path = spend.witness.clone().ok_or(ProverError::MissingWitness)?;
let circuit = S::prepare_circuit(
proof_generation_key,
*note.recipient().diversifier(),
*note.rseed(),
note.value(),
alpha,
rcv,
self.anchor.inner(),
merkle_path,
)
.ok_or(ProverError::InvalidDiversifier)?;
let proof = spend_prover.create_proof(circuit, &mut rng);
spend.zkproof = Some(S::encode_proof(proof));
}
for output in &mut self.outputs {
let recipient = output.recipient.ok_or(ProverError::MissingRecipient)?;
let value = output.value.ok_or(ProverError::MissingValue)?;
let note = Note::from_parts(
recipient,
value,
output
.rseed
.map(Rseed::AfterZip212)
.ok_or(ProverError::MissingRandomSeed)?,
);
let esk = note.generate_or_derive_esk(&mut rng);
let rcm = note.rcm();
let rcv = output
.rcv
.clone()
.ok_or(ProverError::MissingValueCommitTrapdoor)?;
let circuit = O::prepare_circuit(&esk, recipient, rcm, value, rcv);
let proof = output_prover.create_proof(circuit, &mut rng);
output.zkproof = Some(O::encode_proof(proof));
}
Ok(())
}
}
/// Errors that can occur while creating Sapling proofs for a PCZT.
#[derive(Debug)]
pub enum ProverError {
InvalidDiversifier,
/// The Prover role requires all `proof_generation_key` fields to be set.
MissingProofGenerationKey,
/// The Prover role requires all `rseed` fields to be set.
MissingRandomSeed,
/// The Prover role requires all `recipient` fields to be set.
MissingRecipient,
/// The Prover role requires all `alpha` fields to be set.
MissingSpendAuthRandomizer,
/// The Prover role requires all `value` fields to be set.
MissingValue,
/// The Prover role requires all `rcv` fields to be set.
MissingValueCommitTrapdoor,
/// The Prover role requires all `witness` fields to be set.
MissingWitness,
}

38
src/pczt/signer.rs Normal file
View File

@ -0,0 +1,38 @@
use rand::{CryptoRng, RngCore};
use crate::keys::SpendAuthorizingKey;
impl super::Spend {
/// Signs the Sapling spend with the given spend authorizing key.
///
/// It is the caller's responsibility to perform any semantic validity checks on the
/// PCZT (for example, comfirming that the change amounts are correct) before calling
/// this method.
pub fn sign<R: RngCore + CryptoRng>(
&mut self,
sighash: [u8; 32],
ask: &SpendAuthorizingKey,
rng: R,
) -> Result<(), SignerError> {
let alpha = self.alpha.ok_or(SignerError::MissingSpendAuthRandomizer)?;
let rsk = ask.randomize(&alpha);
let rk = redjubjub::VerificationKey::from(&rsk);
if self.rk == rk {
self.spend_auth_sig = Some(rsk.sign(rng, &sighash));
Ok(())
} else {
Err(SignerError::WrongSpendAuthorizingKey)
}
}
}
/// Errors that can occur while signing an Orchard action in a PCZT.
#[derive(Debug)]
pub enum SignerError {
/// The Signer role requires `alpha` to be set.
MissingSpendAuthRandomizer,
/// The provided `ask` does not own the action's spent note.
WrongSpendAuthorizingKey,
}

165
src/pczt/tx_extractor.rs Normal file
View File

@ -0,0 +1,165 @@
use rand::{CryptoRng, RngCore};
use crate::{
bundle::{
Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription,
SpendDescription,
},
Bundle,
};
use super::{Output, Spend};
impl super::Bundle {
/// Extracts the effects of this PCZT bundle as a [regular `Bundle`].
///
/// This is used by the Signer role to produce the transaction sighash.
///
/// [regular `Bundle`]: crate::Bundle
pub fn extract_effects<V: TryFrom<i64>>(
&self,
) -> Result<Option<crate::Bundle<EffectsOnly, V>>, TxExtractorError> {
self.to_tx_data(|_| Ok(()), |_| Ok(()), |_| Ok(()), |_| Ok(EffectsOnly))
}
/// Extracts a fully authorized [regular `Bundle`] from this PCZT bundle.
///
/// This is used by the Transaction Extractor role to produce the final transaction.
///
/// [regular `Bundle`]: crate::Bundle
pub fn extract<V: TryFrom<i64>>(
self,
) -> Result<Option<crate::Bundle<Unbound, V>>, TxExtractorError> {
self.to_tx_data(
|spend| spend.zkproof.ok_or(TxExtractorError::MissingProof),
|spend| {
spend
.spend_auth_sig
.ok_or(TxExtractorError::MissingSpendAuthSig)
},
|output| output.zkproof.ok_or(TxExtractorError::MissingProof),
|bundle| {
Ok(Unbound {
bsk: bundle
.bsk
.ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?,
})
},
)
}
fn to_tx_data<A, V, E, F, G, H, I>(
&self,
spend_proof: F,
spend_auth: G,
output_proof: H,
bundle_auth: I,
) -> Result<Option<crate::Bundle<A, V>>, E>
where
A: Authorization,
E: From<TxExtractorError>,
F: Fn(&Spend) -> Result<<A as Authorization>::SpendProof, E>,
G: Fn(&Spend) -> Result<<A as Authorization>::AuthSig, E>,
H: Fn(&Output) -> Result<<A as Authorization>::OutputProof, E>,
I: FnOnce(&Self) -> Result<A, E>,
V: TryFrom<i64>,
{
let spends = self
.spends
.iter()
.map(|spend| {
Ok(SpendDescription::from_parts(
spend.cv.clone(),
self.anchor.inner(),
spend.nullifier,
spend.rk,
spend_proof(spend)?,
spend_auth(spend)?,
))
})
.collect::<Result<_, E>>()?;
let outputs = self
.outputs
.iter()
.map(|output| {
Ok(OutputDescription::from_parts(
output.cv.clone(),
output.cmu,
output.ephemeral_key.clone(),
output.enc_ciphertext,
output.out_ciphertext,
output_proof(output)?,
))
})
.collect::<Result<_, E>>()?;
let value_balance = i64::try_from(self.value_sum)
.ok()
.and_then(|v| v.try_into().ok())
.ok_or(TxExtractorError::ValueSumOutOfRange)?;
let authorization = bundle_auth(self)?;
Ok(Bundle::from_parts(
spends,
outputs,
value_balance,
authorization,
))
}
}
/// Errors that can occur while extracting a regular Sapling bundle from a PCZT bundle.
#[derive(Debug)]
pub enum TxExtractorError {
/// The Transaction Extractor role requires `bsk` to be set.
MissingBindingSignatureSigningKey,
/// The Transaction Extractor role requires all `zkproof` fields to be set.
MissingProof,
/// The Transaction Extractor role requires all `spend_auth_sig` fields to be set.
MissingSpendAuthSig,
/// The value sum does not fit into a `valueBalance`.
ValueSumOutOfRange,
}
/// Authorizing data for a bundle of actions that is just missing a binding signature.
#[derive(Debug)]
pub struct Unbound {
bsk: redjubjub::SigningKey<redjubjub::Binding>,
}
impl Authorization for Unbound {
type SpendProof = GrothProofBytes;
type OutputProof = GrothProofBytes;
type AuthSig = redjubjub::Signature<redjubjub::SpendAuth>;
}
impl<V> crate::Bundle<Unbound, V> {
/// Verifies the given sighash with every `spend_auth_sig`, and then binds the bundle.
///
/// Returns `None` if the given sighash does not validate against every `spend_auth_sig`.
pub fn apply_binding_signature<R: RngCore + CryptoRng>(
self,
sighash: [u8; 32],
rng: R,
) -> Option<crate::Bundle<Authorized, V>> {
if self
.shielded_spends()
.iter()
.all(|spend| spend.rk().verify(&sighash, spend.spend_auth_sig()).is_ok())
{
Some(self.map_authorization(
&mut (),
|_, p| p,
|_, p| p,
|_, s| s,
|_, Unbound { bsk }| Authorized {
binding_sig: bsk.sign(rng, &sighash),
},
))
} else {
None
}
}
}

View File

@ -93,6 +93,10 @@ impl Anchor {
Anchor(Node::empty_root(NOTE_COMMITMENT_TREE_DEPTH.into()).0)
}
pub(crate) fn inner(&self) -> jubjub::Base {
self.0
}
/// Parses a Sapling anchor from a byte encoding.
pub fn from_bytes(bytes: [u8; 32]) -> CtOption<Anchor> {
jubjub::Base::from_repr(bytes).map(Self)

View File

@ -38,6 +38,20 @@ impl ValueSum {
pub fn zero() -> Self {
ValueSum(0)
}
/// Instantiates a value sum from a raw encoding.
///
/// Only intended for use with PCZTs.
pub(crate) fn from_raw(value_sum: i128) -> Self {
Self(value_sum)
}
/// Extracts the raw encoding of this value sum.
///
/// Only intended for use with PCZTs.
pub fn to_raw(self) -> i128 {
self.0
}
}
impl Add<NoteValue> for ValueSum {