Modify builder to take spending keys as late as possible
This commit is contained in:
parent
f228f52542
commit
833eb2ec02
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -23,7 +23,16 @@ and this library adheres to Rust's notion of
|
||||||
that expose it.
|
that expose it.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- `sapling_crypto::builder::Error` has a new variant `PcztRequiresZip212`.
|
- `sapling_crypto::builder`:
|
||||||
|
- `SpendInfo::new` now takes a `FullViewingKey` instead of a
|
||||||
|
`ProofGenerationKey`.
|
||||||
|
- `Builder::add_spend` now takes a `FullViewingKey` instead of an
|
||||||
|
`&ExtendedSpendingKey`.
|
||||||
|
- `Builder::build` and `bundle` now take an `&[ExtendedSpendingKey]` argument.
|
||||||
|
- `Error` has new variants:
|
||||||
|
- `MissingSpendingKey`
|
||||||
|
- `PcztRequiresZip212`
|
||||||
|
- `WrongSpendingKey`
|
||||||
- `sapling_crypto::bundle::SpendDescriptionV5::into_spend_description` now
|
- `sapling_crypto::bundle::SpendDescriptionV5::into_spend_description` now
|
||||||
supports any `Authorization` for which the `SpendDescription` itself is fully
|
supports any `Authorization` for which the `SpendDescription` itself is fully
|
||||||
authorized.
|
authorized.
|
||||||
|
|
|
@ -15,7 +15,10 @@ use crate::{
|
||||||
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription,
|
Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription,
|
||||||
},
|
},
|
||||||
circuit,
|
circuit,
|
||||||
keys::{EphemeralSecretKey, OutgoingViewingKey, SpendAuthorizingKey, SpendValidatingKey},
|
keys::{
|
||||||
|
EphemeralSecretKey, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey,
|
||||||
|
SpendAuthorizingKey, SpendValidatingKey,
|
||||||
|
},
|
||||||
note::ExtractedNoteCommitment,
|
note::ExtractedNoteCommitment,
|
||||||
note_encryption::{sapling_note_encryption, Zip212Enforcement},
|
note_encryption::{sapling_note_encryption, Zip212Enforcement},
|
||||||
prover::{OutputProver, SpendProver},
|
prover::{OutputProver, SpendProver},
|
||||||
|
@ -123,11 +126,15 @@ pub enum Error {
|
||||||
InvalidExternalSignature,
|
InvalidExternalSignature,
|
||||||
/// A bundle could not be built because required signatures were missing.
|
/// A bundle could not be built because required signatures were missing.
|
||||||
MissingSignatures,
|
MissingSignatures,
|
||||||
|
/// A required spending key was not provided.
|
||||||
|
MissingSpendingKey,
|
||||||
/// [`Builder::build_for_pczt`] requires [`Zip212Enforcement::On`].
|
/// [`Builder::build_for_pczt`] requires [`Zip212Enforcement::On`].
|
||||||
PcztRequiresZip212,
|
PcztRequiresZip212,
|
||||||
SpendProof,
|
SpendProof,
|
||||||
/// The bundle being constructed violated the construction rules for the requested bundle type.
|
/// The bundle being constructed violated the construction rules for the requested bundle type.
|
||||||
BundleTypeNotSatisfiable,
|
BundleTypeNotSatisfiable,
|
||||||
|
/// The wrong spending key was provided.
|
||||||
|
WrongSpendingKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
|
@ -142,6 +149,7 @@ impl fmt::Display for Error {
|
||||||
Error::InvalidAmount => write!(f, "Invalid amount"),
|
Error::InvalidAmount => write!(f, "Invalid amount"),
|
||||||
Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
|
Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
|
||||||
Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
|
Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
|
||||||
|
Error::MissingSpendingKey => write!(f, "A required spending key was not provided"),
|
||||||
Error::PcztRequiresZip212 => {
|
Error::PcztRequiresZip212 => {
|
||||||
write!(f, "PCZTs require that ZIP 212 is enforced for outputs")
|
write!(f, "PCZTs require that ZIP 212 is enforced for outputs")
|
||||||
}
|
}
|
||||||
|
@ -149,6 +157,7 @@ impl fmt::Display for Error {
|
||||||
Error::BundleTypeNotSatisfiable => {
|
Error::BundleTypeNotSatisfiable => {
|
||||||
f.write_str("Bundle structure did not conform to requested bundle type.")
|
f.write_str("Bundle structure did not conform to requested bundle type.")
|
||||||
}
|
}
|
||||||
|
Error::WrongSpendingKey => write!(f, "The wrong spending key was provided"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,24 +165,20 @@ impl fmt::Display for Error {
|
||||||
/// A struct containing the information necessary to add a spend to a bundle.
|
/// A struct containing the information necessary to add a spend to a bundle.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SpendInfo {
|
pub struct SpendInfo {
|
||||||
proof_generation_key: ProofGenerationKey,
|
fvk: FullViewingKey,
|
||||||
note: Note,
|
note: Note,
|
||||||
merkle_path: MerklePath,
|
merkle_path: MerklePath,
|
||||||
dummy_ask: Option<SpendAuthorizingKey>,
|
dummy_expsk: Option<ExpandedSpendingKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpendInfo {
|
impl SpendInfo {
|
||||||
/// Constructs a [`SpendInfo`] from its constituent parts.
|
/// Constructs a [`SpendInfo`] from its constituent parts.
|
||||||
pub fn new(
|
pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self {
|
||||||
proof_generation_key: ProofGenerationKey,
|
|
||||||
note: Note,
|
|
||||||
merkle_path: MerklePath,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
proof_generation_key,
|
fvk,
|
||||||
note,
|
note,
|
||||||
merkle_path,
|
merkle_path,
|
||||||
dummy_ask: None,
|
dummy_expsk: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,10 +201,13 @@ impl SpendInfo {
|
||||||
.expect("The path length corresponds to the length of the generated vector.");
|
.expect("The path length corresponds to the length of the generated vector.");
|
||||||
|
|
||||||
SpendInfo {
|
SpendInfo {
|
||||||
proof_generation_key: sk.proof_generation_key(),
|
fvk: FullViewingKey {
|
||||||
|
vk: sk.proof_generation_key().to_viewing_key(),
|
||||||
|
ovk: sk.ovk,
|
||||||
|
},
|
||||||
note,
|
note,
|
||||||
merkle_path,
|
merkle_path,
|
||||||
dummy_ask: Some(sk.ask),
|
dummy_expsk: Some(sk),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,22 +222,22 @@ impl SpendInfo {
|
||||||
|
|
||||||
fn prepare<R: RngCore>(self, rng: R) -> PreparedSpendInfo {
|
fn prepare<R: RngCore>(self, rng: R) -> PreparedSpendInfo {
|
||||||
PreparedSpendInfo {
|
PreparedSpendInfo {
|
||||||
proof_generation_key: self.proof_generation_key,
|
fvk: self.fvk,
|
||||||
note: self.note,
|
note: self.note,
|
||||||
merkle_path: self.merkle_path,
|
merkle_path: self.merkle_path,
|
||||||
rcv: ValueCommitTrapdoor::random(rng),
|
rcv: ValueCommitTrapdoor::random(rng),
|
||||||
dummy_ask: self.dummy_ask,
|
dummy_expsk: self.dummy_expsk,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct PreparedSpendInfo {
|
struct PreparedSpendInfo {
|
||||||
proof_generation_key: ProofGenerationKey,
|
fvk: FullViewingKey,
|
||||||
note: Note,
|
note: Note,
|
||||||
merkle_path: MerklePath,
|
merkle_path: MerklePath,
|
||||||
rcv: ValueCommitTrapdoor,
|
rcv: ValueCommitTrapdoor,
|
||||||
dummy_ask: Option<SpendAuthorizingKey>,
|
dummy_expsk: Option<ExpandedSpendingKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreparedSpendInfo {
|
impl PreparedSpendInfo {
|
||||||
|
@ -246,13 +254,13 @@ impl PreparedSpendInfo {
|
||||||
let alpha = jubjub::Fr::random(&mut rng);
|
let alpha = jubjub::Fr::random(&mut rng);
|
||||||
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
|
let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
|
||||||
|
|
||||||
let ak = self.proof_generation_key.ak.clone();
|
let ak = self.fvk.vk.ak.clone();
|
||||||
|
|
||||||
// This is the result of the re-randomization, we compute it for the caller
|
// This is the result of the re-randomization, we compute it for the caller
|
||||||
let rk = ak.randomize(&alpha);
|
let rk = ak.randomize(&alpha);
|
||||||
|
|
||||||
let nullifier = self.note.nf(
|
let nullifier = self.note.nf(
|
||||||
&self.proof_generation_key.to_viewing_key().nk,
|
&self.fvk.vk.nk,
|
||||||
u64::try_from(self.merkle_path.position())
|
u64::try_from(self.merkle_path.position())
|
||||||
.expect("Sapling note commitment tree position must fit into a u64"),
|
.expect("Sapling note commitment tree position must fit into a u64"),
|
||||||
);
|
);
|
||||||
|
@ -262,18 +270,30 @@ impl PreparedSpendInfo {
|
||||||
|
|
||||||
fn build<Pr: SpendProver, R: RngCore>(
|
fn build<Pr: SpendProver, R: RngCore>(
|
||||||
self,
|
self,
|
||||||
|
proof_generation_key: Option<ProofGenerationKey>,
|
||||||
rng: R,
|
rng: R,
|
||||||
) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
|
) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
|
||||||
|
let proof_generation_key = match (proof_generation_key, self.dummy_expsk.as_ref()) {
|
||||||
|
(Some(proof_generation_key), None) => Ok(proof_generation_key),
|
||||||
|
(None, Some(expsk)) => Ok(expsk.proof_generation_key()),
|
||||||
|
(Some(_), Some(_)) => Err(Error::WrongSpendingKey),
|
||||||
|
(None, None) => Err(Error::MissingSpendingKey),
|
||||||
|
}?;
|
||||||
|
let expected_vk = proof_generation_key.to_viewing_key();
|
||||||
|
if (&expected_vk.ak, &expected_vk.nk) != (&self.fvk.vk.ak, &self.fvk.vk.nk) {
|
||||||
|
return Err(Error::WrongSpendingKey);
|
||||||
|
}
|
||||||
|
|
||||||
let (cv, nullifier, rk, alpha) = self.build_inner(rng);
|
let (cv, nullifier, rk, alpha) = self.build_inner(rng);
|
||||||
|
|
||||||
// Construct the value commitment.
|
// Construct the value commitment.
|
||||||
let node = Node::from_cmu(&self.note.cmu());
|
let node = Node::from_cmu(&self.note.cmu());
|
||||||
let anchor = *self.merkle_path.root(node).inner();
|
let anchor = *self.merkle_path.root(node).inner();
|
||||||
|
|
||||||
let ak = self.proof_generation_key.ak.clone();
|
let ak = self.fvk.vk.ak.clone();
|
||||||
|
|
||||||
let zkproof = Pr::prepare_circuit(
|
let zkproof = Pr::prepare_circuit(
|
||||||
self.proof_generation_key,
|
proof_generation_key,
|
||||||
*self.note.recipient().diversifier(),
|
*self.note.recipient().diversifier(),
|
||||||
*self.note.rseed(),
|
*self.note.rseed(),
|
||||||
self.note.value(),
|
self.note.value(),
|
||||||
|
@ -291,7 +311,7 @@ impl PreparedSpendInfo {
|
||||||
rk,
|
rk,
|
||||||
zkproof,
|
zkproof,
|
||||||
SigningMetadata {
|
SigningMetadata {
|
||||||
dummy_ask: self.dummy_ask,
|
dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask),
|
||||||
parts: SigningParts { ak, alpha },
|
parts: SigningParts { ak, alpha },
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
@ -310,11 +330,11 @@ impl PreparedSpendInfo {
|
||||||
value: Some(self.note.value()),
|
value: Some(self.note.value()),
|
||||||
rseed: Some(*self.note.rseed()),
|
rseed: Some(*self.note.rseed()),
|
||||||
rcv: Some(self.rcv),
|
rcv: Some(self.rcv),
|
||||||
proof_generation_key: Some(self.proof_generation_key),
|
proof_generation_key: None,
|
||||||
witness: Some(self.merkle_path),
|
witness: Some(self.merkle_path),
|
||||||
alpha: Some(alpha),
|
alpha: Some(alpha),
|
||||||
zip32_derivation: None,
|
zip32_derivation: None,
|
||||||
dummy_ask: self.dummy_ask,
|
dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask),
|
||||||
proprietary: BTreeMap::new(),
|
proprietary: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -595,11 +615,11 @@ impl Builder {
|
||||||
/// paths for previous Sapling notes.
|
/// paths for previous Sapling notes.
|
||||||
pub fn add_spend(
|
pub fn add_spend(
|
||||||
&mut self,
|
&mut self,
|
||||||
extsk: &ExtendedSpendingKey,
|
fvk: FullViewingKey,
|
||||||
note: Note,
|
note: Note,
|
||||||
merkle_path: MerklePath,
|
merkle_path: MerklePath,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let spend = SpendInfo::new(extsk.expsk.proof_generation_key(), note, merkle_path);
|
let spend = SpendInfo::new(fvk, note, merkle_path);
|
||||||
|
|
||||||
// Consistency check: all anchors must equal the first one
|
// Consistency check: all anchors must equal the first one
|
||||||
match self.bundle_type {
|
match self.bundle_type {
|
||||||
|
@ -642,6 +662,7 @@ impl Builder {
|
||||||
/// Constructs the Sapling bundle from the builder's accumulated state.
|
/// Constructs the Sapling bundle from the builder's accumulated state.
|
||||||
pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
|
pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
|
||||||
self,
|
self,
|
||||||
|
extsks: &[ExtendedSpendingKey],
|
||||||
rng: R,
|
rng: R,
|
||||||
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
|
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
|
||||||
bundle::<SP, OP, _, _>(
|
bundle::<SP, OP, _, _>(
|
||||||
|
@ -651,6 +672,7 @@ impl Builder {
|
||||||
self.anchor,
|
self.anchor,
|
||||||
self.spends,
|
self.spends,
|
||||||
self.outputs,
|
self.outputs,
|
||||||
|
extsks,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,6 +729,7 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
spends: Vec<SpendInfo>,
|
spends: Vec<SpendInfo>,
|
||||||
outputs: Vec<OutputInfo>,
|
outputs: Vec<OutputInfo>,
|
||||||
|
extsks: &[ExtendedSpendingKey],
|
||||||
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
|
) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
|
||||||
build_bundle::<_, SP, OP, _>(
|
build_bundle::<_, SP, OP, _>(
|
||||||
rng,
|
rng,
|
||||||
|
@ -729,7 +752,21 @@ pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
|
||||||
// Create the unauthorized Spend and Output descriptions.
|
// Create the unauthorized Spend and Output descriptions.
|
||||||
let shielded_spends = spend_infos
|
let shielded_spends = spend_infos
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|a| a.build::<SP, _>(&mut rng))
|
.map(|a| {
|
||||||
|
// Look up the proof generation key for non-dummy spends.
|
||||||
|
let proof_generation_key = a
|
||||||
|
.dummy_expsk
|
||||||
|
.is_none()
|
||||||
|
.then(|| {
|
||||||
|
extsks.iter().find_map(|extsk| {
|
||||||
|
let dfvk = extsk.to_diversifiable_full_viewing_key();
|
||||||
|
(dfvk.fvk().to_bytes() == a.fvk.to_bytes())
|
||||||
|
.then(|| extsk.expsk.proof_generation_key())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
a.build::<SP, _>(proof_generation_key, &mut rng)
|
||||||
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let shielded_outputs = output_infos
|
let shielded_outputs = output_infos
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -1288,6 +1325,7 @@ pub(crate) mod testing {
|
||||||
})
|
})
|
||||||
.prop_map(
|
.prop_map(
|
||||||
move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| {
|
move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| {
|
||||||
|
let dfvk = extsk.to_diversifiable_full_viewing_key();
|
||||||
let anchor = spendable_notes
|
let anchor = spendable_notes
|
||||||
.first()
|
.first()
|
||||||
.zip(commitment_trees.first())
|
.zip(commitment_trees.first())
|
||||||
|
@ -1302,11 +1340,14 @@ pub(crate) mod testing {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(commitment_trees.into_iter())
|
.zip(commitment_trees.into_iter())
|
||||||
{
|
{
|
||||||
builder.add_spend(&extsk, note, path).unwrap();
|
builder.add_spend(dfvk.fvk().clone(), note, path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (bundle, _) = builder
|
let (bundle, _) = builder
|
||||||
.build::<MockSpendProver, MockOutputProver, _, _>(&mut rng)
|
.build::<MockSpendProver, MockOutputProver, _, _>(
|
||||||
|
&[extsk.clone()],
|
||||||
|
&mut rng,
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue