Merge pull request #286 from zcash/merge-non-consensus-changes

Merge non-consensus changes
This commit is contained in:
str4d 2022-02-15 23:36:29 +00:00 committed by GitHub
commit f1795f8068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 211 additions and 55 deletions

View File

@ -6,6 +6,13 @@ and this project adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `orchard::keys`:
- `DiversifierIndex::to_bytes`
- `IncomingViewingKey::diversifier_index`
- `orchard::primitives::redpallas::VerificationKey::verify`
- `orchard::tree::MerklePath::from_parts`
- `impl From<orchard::bundle::BundleCommitment> for [u8; 32]`
### Changed
- MSRV is now 1.54.0.
@ -14,8 +21,21 @@ and this project adheres to Rust's notion of
- `orchard::builder::Bundle::create_proof`
- `orchard::builder::InProgress::create_proof`
- `orchard::circuit::Proof::create`
- `orchard::Bundle::commitment` now requires the bound `V: Copy + Into<i64>`
instead of `i64: From<&'a V>`.
- `orchard::Bundle::binding_validating_key` now requires the bound
`V: Into<i64>` instead of `V: Into<ValueSum>`.
### Removed
- `orchard::bundle`:
- `commitments::hash_bundle_txid_data` (use `Bundle::commitment` instead).
- `commitments::hash_bundle_auth_data` (use `Bundle::authorizing_commitment`
instead).
- `orchard::keys`:
- `FullViewingKey::default_address`
- `IncomingViewingKey::default_address`
- `DiversifierKey` (use the APIs on `FullViewingKey` and `IncomingViewingKey`
instead).
- `orchard::value::ValueSum::from_raw`
## [0.1.0-beta.1] - 2021-12-17

View File

@ -20,7 +20,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let rng = OsRng;
let sk = SpendingKey::from_bytes([7; 32]).unwrap();
let recipient = FullViewingKey::from(&sk).default_address();
let recipient = FullViewingKey::from(&sk).address_at(0u32);
let vk = VerifyingKey::build();
let pk = ProvingKey::build();

View File

@ -22,7 +22,7 @@ fn bench_note_decryption(c: &mut Criterion) {
let fvk = FullViewingKey::from(&SpendingKey::from_bytes([7; 32]).unwrap());
let valid_ivk = IncomingViewingKey::from(&fvk);
let recipient = fvk.default_address();
let recipient = fvk.address_at(0u32);
// Compact actions don't have the full AEAD ciphertext, so ZIP 307 trial-decryption
// relies on an invalid ivk resulting in random noise for which the note commitment

View File

@ -12,7 +12,7 @@ fn key_derivation(c: &mut Criterion) {
let fvk = FullViewingKey::from(&sk);
c.bench_function("derive_fvk", |b| b.iter(|| FullViewingKey::from(&sk)));
c.bench_function("default_address", |b| b.iter(|| fvk.default_address()));
c.bench_function("default_address", |b| b.iter(|| fvk.address_at(0u32)));
}
criterion_group!(benches, key_derivation);

View File

@ -15,7 +15,7 @@ use crate::{
/// use orchard::keys::{SpendingKey, FullViewingKey};
///
/// let sk = SpendingKey::from_bytes([7; 32]).unwrap();
/// let address = FullViewingKey::from(&sk).default_address();
/// let address = FullViewingKey::from(&sk).address_at(0u32);
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Address {
@ -32,7 +32,7 @@ impl Address {
Address { d, pk_d }
}
pub(crate) fn diversifer(&self) -> Diversifier {
pub(crate) fn diversifier(&self) -> Diversifier {
self.d
}
@ -71,15 +71,18 @@ impl Address {
pub mod testing {
use proptest::prelude::*;
use crate::keys::{testing::arb_spending_key, FullViewingKey};
use crate::keys::{
testing::{arb_diversifier_index, arb_spending_key},
FullViewingKey,
};
use super::Address;
prop_compose! {
/// Generates an arbitrary payment address.
pub(crate) fn arb_address()(sk in arb_spending_key()) -> Address {
pub(crate) fn arb_address()(sk in arb_spending_key(), j in arb_diversifier_index()) -> Address {
let fvk = FullViewingKey::from(&sk);
fvk.default_address()
fvk.address_at(j)
}
}
}

View File

@ -90,7 +90,7 @@ impl RecipientInfo {
/// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
fn dummy(rng: &mut impl RngCore) -> Self {
let fvk: FullViewingKey = (&SpendingKey::random(rng)).into();
let recipient = fvk.default_address();
let recipient = fvk.address_at(0u32);
RecipientInfo {
ovk: None,
@ -133,7 +133,7 @@ impl ActionInfo {
let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
let nf_old = self.spend.note.nullifier(&self.spend.fvk);
let sender_address = self.spend.fvk.default_address();
let sender_address = self.spend.note.recipient();
let rho_old = self.spend.note.rho();
let psi_old = self.spend.note.rseed().psi(&rho_old);
let rcm_old = self.spend.note.rseed().rcm(&rho_old);
@ -731,7 +731,7 @@ mod tests {
let sk = SpendingKey::random(&mut rng);
let fvk = FullViewingKey::from(&sk);
let recipient = fvk.default_address();
let recipient = fvk.address_at(0u32);
let mut builder = Builder::new(
Flags::from_parts(true, true),

View File

@ -2,6 +2,7 @@
pub mod commitments;
use std::convert::TryInto;
use std::io;
use blake2b_simd::Hash as Blake2bHash;
@ -298,15 +299,6 @@ impl<T: Authorization, V> Bundle<T, V> {
&self.authorization
}
/// Computes a commitment to the effects of this bundle, suitable for inclusion within
/// a transaction ID.
pub fn commitment<'a>(&'a self) -> BundleCommitment
where
i64: From<&'a V>,
{
BundleCommitment(hash_bundle_txid_data(self))
}
/// Construct a new bundle by applying a transformation that might fail
/// to the value balance.
pub fn try_map_value_balance<V0, E, F: FnOnce(V) -> Result<V0, E>>(
@ -405,7 +397,13 @@ impl<T: Authorization, V> Bundle<T, V> {
}
}
impl<T: Authorization, V: Copy + Into<ValueSum>> Bundle<T, V> {
impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
/// Computes a commitment to the effects of this bundle, suitable for inclusion within
/// a transaction ID.
pub fn commitment(&self) -> BundleCommitment {
BundleCommitment(hash_bundle_txid_data(self))
}
/// Returns the transaction binding validating key for this bundle.
///
/// This can be used to validate the [`Authorized::binding_signature`] returned from
@ -416,7 +414,10 @@ impl<T: Authorization, V: Copy + Into<ValueSum>> Bundle<T, V> {
.iter()
.map(|a| a.cv_net())
.sum::<ValueCommitment>()
- ValueCommitment::derive(self.value_balance.into(), ValueCommitTrapdoor::zero()))
- ValueCommitment::derive(
ValueSum::from_raw(self.value_balance.into()),
ValueCommitTrapdoor::zero(),
))
.into_bvk()
}
}
@ -500,6 +501,13 @@ impl<V: DynamicUsage> DynamicUsage for Bundle<Authorized, V> {
#[derive(Debug)]
pub struct BundleCommitment(pub Blake2bHash);
impl From<BundleCommitment> for [u8; 32] {
fn from(commitment: BundleCommitment) -> Self {
// The commitment uses BLAKE2b-256.
commitment.0.as_bytes().try_into().unwrap()
}
}
/// A commitment to the authorizing data within a bundle of actions.
#[derive(Debug)]
pub struct BundleAuthorizingCommitment(pub Blake2bHash);

View File

@ -28,10 +28,9 @@ fn hasher(personal: &[u8; 16]) -> State {
/// personalized with ZCASH_ORCHARD_ACTIONS_HASH_PERSONALIZATION
///
/// [zip244]: https://zips.z.cash/zip-0244
pub fn hash_bundle_txid_data<'a, A: Authorization, V>(bundle: &'a Bundle<A, V>) -> Blake2bHash
where
i64: From<&'a V>,
{
pub(crate) fn hash_bundle_txid_data<A: Authorization, V: Copy + Into<i64>>(
bundle: &Bundle<A, V>,
) -> Blake2bHash {
let mut h = hasher(ZCASH_ORCHARD_HASH_PERSONALIZATION);
let mut ch = hasher(ZCASH_ORCHARD_ACTIONS_COMPACT_HASH_PERSONALIZATION);
let mut mh = hasher(ZCASH_ORCHARD_ACTIONS_MEMOS_HASH_PERSONALIZATION);
@ -59,7 +58,7 @@ where
h.write_all(mh.finalize().as_bytes()).unwrap();
h.write_all(nh.finalize().as_bytes()).unwrap();
h.write_all(&[bundle.flags().to_byte()]).unwrap();
h.write_all(&<i64>::from(bundle.value_balance()).to_le_bytes())
h.write_all(&(*bundle.value_balance()).into().to_le_bytes())
.unwrap();
h.write_all(&bundle.anchor().to_bytes()).unwrap();
h.finalize()
@ -78,7 +77,7 @@ pub fn hash_bundle_txid_empty() -> Blake2bHash {
/// Identifier Non-Malleability][zip244]
///
/// [zip244]: https://zips.z.cash/zip-0244
pub fn hash_bundle_auth_data<V>(bundle: &Bundle<Authorized, V>) -> Blake2bHash {
pub(crate) fn hash_bundle_auth_data<V>(bundle: &Bundle<Authorized, V>) -> Blake2bHash {
let mut h = hasher(ZCASH_ORCHARD_SIGS_HASH_PERSONALIZATION);
h.write_all(bundle.authorization().proof().as_ref())
.unwrap();

View File

@ -910,7 +910,7 @@ mod tests {
.map(|()| {
let (_, fvk, spent_note) = Note::dummy(&mut rng, None);
let sender_address = fvk.default_address();
let sender_address = spent_note.recipient();
let nk = *fvk.nk();
let rivk = *fvk.rivk();
let nf_old = spent_note.nullifier(&fvk);

View File

@ -345,11 +345,6 @@ impl FullViewingKey {
)
}
/// Returns the default payment address for this key.
pub fn default_address(&self) -> Address {
IncomingViewingKey::from(self).default_address()
}
/// Returns the payment address for this key at the given index.
pub fn address_at(&self, j: impl Into<DiversifierIndex>) -> Address {
IncomingViewingKey::from(self).address_at(j)
@ -427,13 +422,7 @@ impl FullViewingKey {
///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DiversifierKey([u8; 32]);
impl From<&FullViewingKey> for DiversifierKey {
fn from(fvk: &FullViewingKey) -> Self {
fvk.derive_dk_ovk().0
}
}
pub(crate) struct DiversifierKey([u8; 32]);
/// The index for a particular diversifier.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -460,12 +449,14 @@ impl From<[u8; 11]> for DiversifierIndex {
}
}
impl DiversifierKey {
/// Returns the diversifier at index 0.
pub fn default_diversifier(&self) -> Diversifier {
self.get(0u32)
impl DiversifierIndex {
/// Returns the raw bytes of the diversifier index.
pub fn to_bytes(&self) -> &[u8; 11] {
&self.0
}
}
impl DiversifierKey {
/// Returns the diversifier at the given index.
pub fn get(&self, j: impl Into<DiversifierIndex>) -> Diversifier {
let ff = FF1::<Aes256>::new(&self.0, 2).expect("valid radix");
@ -579,7 +570,7 @@ pub struct IncomingViewingKey {
impl From<&FullViewingKey> for IncomingViewingKey {
fn from(fvk: &FullViewingKey) -> Self {
IncomingViewingKey {
dk: fvk.into(),
dk: fvk.derive_dk_ovk().0,
ivk: fvk.into(),
}
}
@ -606,9 +597,16 @@ impl IncomingViewingKey {
})
}
/// Returns the default payment address for this key.
pub fn default_address(&self) -> Address {
self.address(self.dk.default_diversifier())
/// Checks whether the given address was derived from this incoming viewing
/// key, and returns the diversifier index used to derive the address if
/// so. Returns `None` if the address was not derived from this key.
pub fn diversifier_index(&self, addr: &Address) -> Option<DiversifierIndex> {
let j = self.dk.diversifier_index(&addr.diversifier());
if &self.address_at(j) == addr {
Some(j)
} else {
None
}
}
/// Returns the payment address for this key at the given index.
@ -860,7 +858,7 @@ pub mod testing {
prop_compose! {
/// Generate a uniformly distributed Orchard diversifier key.
pub fn arb_diversifier_key()(
pub(crate) fn arb_diversifier_key()(
dk_bytes in prop::array::uniform32(prop::num::u8::ANY)
) -> DiversifierKey {
DiversifierKey::from_bytes(dk_bytes)
@ -913,9 +911,10 @@ mod tests {
fn key_agreement(
sk in arb_spending_key(),
esk in arb_esk(),
j in arb_diversifier_index(),
) {
let ivk = IncomingViewingKey::from(&(&sk).into());
let addr = ivk.default_address();
let addr = ivk.address_at(j);
let epk = esk.derive_public(addr.g_d());

View File

@ -155,7 +155,7 @@ impl Note {
) -> (SpendingKey, FullViewingKey, Self) {
let sk = SpendingKey::random(rng);
let fvk: FullViewingKey = (&sk).into();
let recipient = fvk.default_address();
let recipient = fvk.address_at(0u32);
let note = Note::new(
recipient,

View File

@ -149,7 +149,7 @@ impl Domain for OrchardDomain {
) -> NotePlaintextBytes {
let mut np = [0; NOTE_PLAINTEXT_SIZE];
np[0] = 0x02;
np[1..12].copy_from_slice(note.recipient().diversifer().as_array());
np[1..12].copy_from_slice(note.recipient().diversifier().as_array());
np[12..20].copy_from_slice(&note.value().to_bytes());
np[20..52].copy_from_slice(note.rseed().as_bytes());
np[52..].copy_from_slice(memo);

View File

@ -147,6 +147,13 @@ impl VerificationKey<Binding> {
}
}
impl<T: SigType> VerificationKey<T> {
/// Verifies a purported `signature` over `msg` made by this verification key.
pub fn verify(&self, msg: &[u8], signature: &Signature<T>) -> Result<(), reddsa::Error> {
self.0.verify(msg, &signature.0)
}
}
/// A RedPallas signature.
#[derive(Debug, Clone)]
pub struct Signature<T: SigType>(reddsa::Signature<T>);

View File

@ -108,11 +108,19 @@ impl MerklePath {
/// Instantiates a new Merkle path given a leaf position and authentication path.
pub(crate) fn new(position: u32, auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD]) -> Self {
Self {
Self::from_parts(
position,
auth_path: gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |i| {
gen_const_array_with_default(MerkleHashOrchard::empty_leaf(), |i| {
MerkleHashOrchard(auth_path[i])
}),
)
}
/// Instantiates a new Merkle path given a leaf position and authentication path.
pub fn from_parts(position: u32, auth_path: [MerkleHashOrchard; MERKLE_DEPTH_ORCHARD]) -> Self {
Self {
position,
auth_path,
}
}

View File

@ -140,6 +140,16 @@ impl ValueSum {
// Default for i64 is zero.
Default::default()
}
/// Creates a value sum from its raw numeric value.
///
/// This only enforces that the value is a signed 63-bit integer. We use it internally
/// in `Bundle::binding_validating_key`, where we are converting from the user-defined
/// `valueBalance` type that enforces any additional constraints on the value's valid
/// range.
pub(crate) fn from_raw(value: i64) -> Self {
ValueSum(value as i128)
}
}
impl Add for ValueSum {

102
tests/builder.rs Normal file
View File

@ -0,0 +1,102 @@
use std::convert::TryInto;
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Hashable, Tree};
use orchard::{
builder::Builder,
bundle::{Authorized, Flags},
circuit::{ProvingKey, VerifyingKey},
keys::{FullViewingKey, IncomingViewingKey, SpendAuthorizingKey, SpendingKey},
note::ExtractedNoteCommitment,
note_encryption::OrchardDomain,
tree::{MerkleHashOrchard, MerklePath},
value::NoteValue,
Bundle,
};
use rand::rngs::OsRng;
use zcash_note_encryption::try_note_decryption;
fn verify_bundle(bundle: &Bundle<Authorized, i64>, vk: &VerifyingKey) {
assert!(matches!(bundle.verify_proof(vk), Ok(())));
let sighash: [u8; 32] = bundle.commitment().into();
let bvk = bundle.binding_validating_key();
for action in bundle.actions() {
assert_eq!(action.rk().verify(&sighash, action.authorization()), Ok(()));
}
assert_eq!(
bvk.verify(&sighash, bundle.authorization().binding_signature()),
Ok(())
);
}
#[test]
fn bundle_chain() {
let mut rng = OsRng;
let pk = ProvingKey::build();
let vk = VerifyingKey::build();
let sk = SpendingKey::from_bytes([0; 32]).unwrap();
let fvk = FullViewingKey::from(&sk);
let recipient = fvk.address_at(0u32);
// Create a shielding bundle.
let shielding_bundle: Bundle<_, i64> = {
// Use the empty tree.
let anchor = MerkleHashOrchard::empty_root(32.into()).into();
let mut builder = Builder::new(Flags::from_parts(false, true), anchor);
assert_eq!(
builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None),
Ok(())
);
let unauthorized = builder.build(&mut rng).unwrap();
let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
proven.apply_signatures(&mut rng, sighash, &[]).unwrap()
};
// Verify the shielding bundle.
verify_bundle(&shielding_bundle, &vk);
// Create a shielded bundle spending the previous output.
let shielded_bundle: Bundle<_, i64> = {
let ivk = IncomingViewingKey::from(&fvk);
let (note, _, _) = shielding_bundle
.actions()
.iter()
.find_map(|action| {
let domain = OrchardDomain::for_action(action);
try_note_decryption(&domain, &ivk, action)
})
.unwrap();
// Use the tree with a single leaf.
let cmx: ExtractedNoteCommitment = note.commitment().into();
let leaf = MerkleHashOrchard::from_cmx(&cmx);
let mut tree = BridgeTree::<MerkleHashOrchard, 32>::new(0);
tree.append(&leaf);
tree.witness();
let (position, auth_path) = tree.authentication_path(&leaf).unwrap();
let merkle_path = MerklePath::from_parts(
u64::from(position).try_into().unwrap(),
auth_path[..].try_into().unwrap(),
);
let anchor = tree.root().into();
assert_eq!(anchor, merkle_path.root(cmx));
let mut builder = Builder::new(Flags::from_parts(true, true), anchor);
assert_eq!(builder.add_spend(fvk, note, merkle_path), Ok(()));
assert_eq!(
builder.add_recipient(None, recipient, NoteValue::from_raw(5000), None),
Ok(())
);
let unauthorized = builder.build(&mut rng).unwrap();
let sighash = unauthorized.commitment().into();
let proven = unauthorized.create_proof(&pk, &mut rng).unwrap();
proven
.apply_signatures(&mut rng, sighash, &[SpendAuthorizingKey::from(&sk)])
.unwrap()
};
// Verify the shielded bundle.
verify_bundle(&shielded_bundle, &vk);
}