Implement PCZT support
This commit is contained in:
parent
44c4b1724c
commit
7696219bf3
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
|
305
src/builder.rs
305
src/builder.rs
|
@ -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.
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue