mirror of https://github.com/zcash/orchard.git
Merge pull request #412 from nuttycom/bundle_metadata
Return bundle metadata from bundle building.
This commit is contained in:
commit
9a85034ce9
|
@ -16,6 +16,7 @@ and this project adheres to Rust's notion of
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `orchard::builder::bundle`
|
- `orchard::builder::bundle`
|
||||||
|
- `orchard::builder::BundleMetadata`
|
||||||
- `orchard::builder::BundleType`
|
- `orchard::builder::BundleType`
|
||||||
- `orchard::builder::OutputInfo`
|
- `orchard::builder::OutputInfo`
|
||||||
- `orchard::bundle::Flags::{ENABLED, SPENDS_DISABLED, OUTPUTS_DISABLED}`
|
- `orchard::bundle::Flags::{ENABLED, SPENDS_DISABLED, OUTPUTS_DISABLED}`
|
||||||
|
@ -29,7 +30,7 @@ and this project adheres to Rust's notion of
|
||||||
sent to the same recipient.
|
sent to the same recipient.
|
||||||
- `orchard::builder::Builder::build` now takes an additional `BundleType` argument
|
- `orchard::builder::Builder::build` now takes an additional `BundleType` argument
|
||||||
that specifies how actions should be padded, instead of using hardcoded padding.
|
that specifies how actions should be padded, instead of using hardcoded padding.
|
||||||
It also now returns a `Result<Option<Bundle<...>>, ...>` instead of a
|
It also now returns a `Result<Option<(Bundle<...>, BundleMetadata)>, ...>` instead of a
|
||||||
`Result<Bundle<...>, ...>`.
|
`Result<Bundle<...>, ...>`.
|
||||||
- `orchard::builder::BuildError` has additional variants:
|
- `orchard::builder::BuildError` has additional variants:
|
||||||
- `SpendsDisabled`
|
- `SpendsDisabled`
|
||||||
|
|
|
@ -31,7 +31,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||||
.add_output(None, recipient, NoteValue::from_raw(10), None)
|
.add_output(None, recipient, NoteValue::from_raw(10), None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
let bundle: Bundle<_, i64> = builder.build(rng).unwrap().unwrap();
|
let bundle: Bundle<_, i64> = builder.build(rng).unwrap().unwrap().0;
|
||||||
|
|
||||||
let instances: Vec<_> = bundle
|
let instances: Vec<_> = bundle
|
||||||
.actions()
|
.actions()
|
||||||
|
|
|
@ -53,7 +53,7 @@ fn bench_note_decryption(c: &mut Criterion) {
|
||||||
builder
|
builder
|
||||||
.add_output(None, recipient, NoteValue::from_raw(10), None)
|
.add_output(None, recipient, NoteValue::from_raw(10), None)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let bundle: Bundle<_, i64> = builder.build(rng).unwrap().unwrap();
|
let bundle: Bundle<_, i64> = builder.build(rng).unwrap().unwrap().0;
|
||||||
bundle
|
bundle
|
||||||
.create_proof(&pk, rng)
|
.create_proof(&pk, rng)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
132
src/builder.rs
132
src/builder.rs
|
@ -373,6 +373,56 @@ impl ActionInfo {
|
||||||
/// This is returned by [`Builder::build`].
|
/// This is returned by [`Builder::build`].
|
||||||
pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unauthorized>, V>;
|
pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unauthorized>, V>;
|
||||||
|
|
||||||
|
/// Metadata about a bundle created by [`bundle`] or [`Builder::build`] that is not
|
||||||
|
/// necessarily recoverable from the bundle itself.
|
||||||
|
///
|
||||||
|
/// This includes information about how [`Action`]s within the bundle are ordered (after
|
||||||
|
/// padding and randomization) relative to the order in which spends and outputs were
|
||||||
|
/// provided (to [`bundle`]), or the order in which [`Builder`] mutations were performed.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct BundleMetadata {
|
||||||
|
spend_indices: Vec<usize>,
|
||||||
|
output_indices: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BundleMetadata {
|
||||||
|
fn new(num_requested_spends: usize, num_requested_outputs: usize) -> Self {
|
||||||
|
BundleMetadata {
|
||||||
|
spend_indices: vec![0; num_requested_spends],
|
||||||
|
output_indices: vec![0; num_requested_outputs],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata for a [`Bundle`] that contains only dummy actions, if any.
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::new(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index within the bundle of the [`Action`] corresponding to the `n`-th
|
||||||
|
/// spend specified in bundle construction. If a [`Builder`] was used, this refers to
|
||||||
|
/// the spend added by the `n`-th call to [`Builder::add_spend`].
|
||||||
|
///
|
||||||
|
/// For the purpose of improving indistinguishability, actions are padded and note
|
||||||
|
/// positions are randomized when building bundles. This means that the bundle
|
||||||
|
/// consumer cannot assume that e.g. the first spend they added corresponds to the
|
||||||
|
/// first action in the bundle.
|
||||||
|
pub fn spend_action_index(&self, n: usize) -> Option<usize> {
|
||||||
|
self.spend_indices.get(n).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index within the bundle of the [`Action`] corresponding to the `n`-th
|
||||||
|
/// output specified in bundle construction. If a [`Builder`] was used, this refers to
|
||||||
|
/// the output added by the `n`-th call to [`Builder::add_output`].
|
||||||
|
///
|
||||||
|
/// For the purpose of improving indistinguishability, actions are padded and note
|
||||||
|
/// positions are randomized when building bundles. This means that the bundle
|
||||||
|
/// consumer cannot assume that e.g. the first output they added corresponds to the
|
||||||
|
/// first action in the bundle.
|
||||||
|
pub fn output_action_index(&self, n: usize) -> Option<usize> {
|
||||||
|
self.output_indices.get(n).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and outputs
|
/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and outputs
|
||||||
/// to receive funds.
|
/// to receive funds.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -492,7 +542,7 @@ impl Builder {
|
||||||
pub fn build<V: TryFrom<i64>>(
|
pub fn build<V: TryFrom<i64>>(
|
||||||
self,
|
self,
|
||||||
rng: impl RngCore,
|
rng: impl RngCore,
|
||||||
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
|
||||||
bundle(
|
bundle(
|
||||||
rng,
|
rng,
|
||||||
self.anchor,
|
self.anchor,
|
||||||
|
@ -511,9 +561,9 @@ pub fn bundle<V: TryFrom<i64>>(
|
||||||
mut rng: impl RngCore,
|
mut rng: impl RngCore,
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
bundle_type: BundleType,
|
bundle_type: BundleType,
|
||||||
mut spends: Vec<SpendInfo>,
|
spends: Vec<SpendInfo>,
|
||||||
mut outputs: Vec<OutputInfo>,
|
outputs: Vec<OutputInfo>,
|
||||||
) -> Result<Option<UnauthorizedBundle<V>>, BuildError> {
|
) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
|
||||||
let flags = bundle_type.flags();
|
let flags = bundle_type.flags();
|
||||||
|
|
||||||
let num_requested_spends = spends.len();
|
let num_requested_spends = spends.len();
|
||||||
|
@ -537,27 +587,48 @@ pub fn bundle<V: TryFrom<i64>>(
|
||||||
.map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
|
.map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
|
||||||
|
|
||||||
// Pair up the spends and outputs, extending with dummy values as necessary.
|
// Pair up the spends and outputs, extending with dummy values as necessary.
|
||||||
let pre_actions: Vec<_> = {
|
let (pre_actions, bundle_meta) = {
|
||||||
spends.extend(
|
let mut indexed_spends = spends
|
||||||
iter::repeat_with(|| SpendInfo::dummy(&mut rng))
|
.into_iter()
|
||||||
.take(num_actions - num_requested_spends),
|
.chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
|
||||||
);
|
.enumerate()
|
||||||
outputs.extend(
|
.take(num_actions)
|
||||||
iter::repeat_with(|| OutputInfo::dummy(&mut rng))
|
.collect::<Vec<_>>();
|
||||||
.take(num_actions - num_requested_outputs),
|
|
||||||
);
|
let mut indexed_outputs = outputs
|
||||||
|
.into_iter()
|
||||||
|
.chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
|
||||||
|
.enumerate()
|
||||||
|
.take(num_actions)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Shuffle the spends and outputs, so that learning the position of a
|
// Shuffle the spends and outputs, so that learning the position of a
|
||||||
// specific spent note or output note doesn't reveal anything on its own about
|
// specific spent note or output note doesn't reveal anything on its own about
|
||||||
// the meaning of that note in the transaction context.
|
// the meaning of that note in the transaction context.
|
||||||
spends.shuffle(&mut rng);
|
indexed_spends.shuffle(&mut rng);
|
||||||
outputs.shuffle(&mut rng);
|
indexed_outputs.shuffle(&mut rng);
|
||||||
|
|
||||||
spends
|
let mut bundle_meta = BundleMetadata::new(num_requested_spends, num_requested_outputs);
|
||||||
|
let pre_actions = indexed_spends
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(outputs.into_iter())
|
.zip(indexed_outputs.into_iter())
|
||||||
.map(|(spend, output)| ActionInfo::new(spend, output, &mut rng))
|
.enumerate()
|
||||||
.collect()
|
.map(|(action_idx, ((spend_idx, spend), (out_idx, output)))| {
|
||||||
|
// Record the post-randomization spend location
|
||||||
|
if spend_idx < num_requested_spends {
|
||||||
|
bundle_meta.spend_indices[spend_idx] = action_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the post-randomization output location
|
||||||
|
if out_idx < num_requested_outputs {
|
||||||
|
bundle_meta.output_indices[out_idx] = action_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionInfo::new(spend, output, &mut rng)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
(pre_actions, bundle_meta)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine the value balance for this bundle, ensuring it is valid.
|
// Determine the value balance for this bundle, ensuring it is valid.
|
||||||
|
@ -590,15 +661,18 @@ pub fn bundle<V: TryFrom<i64>>(
|
||||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||||
|
|
||||||
Ok(NonEmpty::from_vec(actions).map(|actions| {
|
Ok(NonEmpty::from_vec(actions).map(|actions| {
|
||||||
Bundle::from_parts(
|
(
|
||||||
actions,
|
Bundle::from_parts(
|
||||||
flags,
|
actions,
|
||||||
result_value_balance,
|
flags,
|
||||||
anchor,
|
result_value_balance,
|
||||||
InProgress {
|
anchor,
|
||||||
proof: Unproven { circuits },
|
InProgress {
|
||||||
sigs: Unauthorized { bsk },
|
proof: Unproven { circuits },
|
||||||
},
|
sigs: Unauthorized { bsk },
|
||||||
|
},
|
||||||
|
),
|
||||||
|
bundle_meta,
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -957,6 +1031,7 @@ pub mod testing {
|
||||||
.build(&mut self.rng)
|
.build(&mut self.rng)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.0
|
||||||
.create_proof(&pk, &mut self.rng)
|
.create_proof(&pk, &mut self.rng)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.prepare(&mut self.rng, [0; 32])
|
.prepare(&mut self.rng, [0; 32])
|
||||||
|
@ -1069,6 +1144,7 @@ mod tests {
|
||||||
.build(&mut rng)
|
.build(&mut rng)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.0
|
||||||
.create_proof(&pk, &mut rng)
|
.create_proof(&pk, &mut rng)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.prepare(rng, [0; 32])
|
.prepare(rng, [0; 32])
|
||||||
|
|
|
@ -49,11 +49,25 @@ fn bundle_chain() {
|
||||||
},
|
},
|
||||||
anchor,
|
anchor,
|
||||||
);
|
);
|
||||||
|
let note_value = NoteValue::from_raw(5000);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
|
builder.add_output(None, recipient, note_value, None),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
let unauthorized = builder.build(&mut rng).unwrap().unwrap();
|
let (unauthorized, bundle_meta) = builder.build(&mut rng).unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
unauthorized
|
||||||
|
.decrypt_output_with_key(
|
||||||
|
bundle_meta
|
||||||
|
.output_action_index(0)
|
||||||
|
.expect("Output 0 can be found"),
|
||||||
|
&fvk.to_ivk(Scope::External)
|
||||||
|
)
|
||||||
|
.map(|(note, _, _)| note.value()),
|
||||||
|
Some(note_value)
|
||||||
|
);
|
||||||
|
|
||||||
let sighash = unauthorized.commitment().into();
|
let sighash = unauthorized.commitment().into();
|
||||||
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
|
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
|
||||||
proven.apply_signatures(rng, sighash, &[]).unwrap()
|
proven.apply_signatures(rng, sighash, &[]).unwrap()
|
||||||
|
@ -95,7 +109,7 @@ fn bundle_chain() {
|
||||||
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
|
builder.add_output(None, recipient, NoteValue::from_raw(5000), None),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
let unauthorized = builder.build(&mut rng).unwrap().unwrap();
|
let (unauthorized, _) = builder.build(&mut rng).unwrap().unwrap();
|
||||||
let sighash = unauthorized.commitment().into();
|
let sighash = unauthorized.commitment().into();
|
||||||
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
|
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
|
||||||
proven
|
proven
|
||||||
|
|
Loading…
Reference in New Issue