Merge pull request #412 from nuttycom/bundle_metadata

Return bundle metadata from bundle building.
This commit is contained in:
Kris Nuttycombe 2024-01-09 15:45:01 -07:00 committed by GitHub
commit 9a85034ce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 34 deletions

View File

@ -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`

View File

@ -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()

View File

@ -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()

View File

@ -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])

View File

@ -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