mirror of https://github.com/zcash/orchard.git
Added NoteType to Notes (#2)
* Added NoteType to Notes * Added NoteType to value commitment derivation
This commit is contained in:
parent
769f2f7b9c
commit
8a6f59927e
|
@ -2,6 +2,9 @@
|
|||
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
slack: circleci/slack@4.1
|
||||
|
||||
# Define a job to be invoked later in a workflow.
|
||||
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||
jobs:
|
||||
|
@ -17,8 +20,15 @@ jobs:
|
|||
- run:
|
||||
name: "cargo test"
|
||||
command: |
|
||||
sudo apt update && sudo apt-get install libfontconfig libfontconfig1-dev libfreetype6-dev;
|
||||
cargo version;
|
||||
cargo test;
|
||||
cargo test --all --all-features;
|
||||
- slack/notify:
|
||||
event: fail
|
||||
template: basic_fail_1
|
||||
- slack/notify:
|
||||
event: pass
|
||||
template: basic_success_1
|
||||
|
||||
|
||||
# Invoke jobs via workflows
|
||||
|
@ -26,4 +36,5 @@ jobs:
|
|||
workflows:
|
||||
build-and-test:
|
||||
jobs:
|
||||
- cargo-test
|
||||
- cargo-test:
|
||||
context: CI-Orchard-slack
|
||||
|
|
|
@ -126,6 +126,7 @@ pub(crate) mod testing {
|
|||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
note::{
|
||||
commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier,
|
||||
|
@ -150,7 +151,8 @@ pub(crate) mod testing {
|
|||
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
||||
let cv_net = ValueCommitment::derive(
|
||||
spend_value - output_value,
|
||||
ValueCommitTrapdoor::zero()
|
||||
ValueCommitTrapdoor::zero(),
|
||||
NoteType::native()
|
||||
);
|
||||
// FIXME: make a real one from the note.
|
||||
let encrypted_note = TransmittedNoteCiphertext {
|
||||
|
@ -181,7 +183,8 @@ pub(crate) mod testing {
|
|||
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
||||
let cv_net = ValueCommitment::derive(
|
||||
spend_value - output_value,
|
||||
ValueCommitTrapdoor::zero()
|
||||
ValueCommitTrapdoor::zero(),
|
||||
NoteType::native()
|
||||
);
|
||||
|
||||
// FIXME: make a real one from the note.
|
||||
|
|
|
@ -8,6 +8,7 @@ use nonempty::NonEmpty;
|
|||
use pasta_curves::pallas;
|
||||
use rand::{prelude::SliceRandom, CryptoRng, RngCore};
|
||||
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
action::Action,
|
||||
address::Address,
|
||||
|
@ -140,7 +141,7 @@ impl ActionInfo {
|
|||
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
||||
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
|
||||
let v_net = self.value_sum();
|
||||
let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
|
||||
let cv_net = ValueCommitment::derive(v_net, self.rcv, NoteType::native());
|
||||
|
||||
let nf_old = self.spend.note.nullifier(&self.spend.fvk);
|
||||
let sender_address = self.spend.note.recipient();
|
||||
|
@ -150,8 +151,15 @@ impl ActionInfo {
|
|||
let ak: SpendValidatingKey = self.spend.fvk.clone().into();
|
||||
let alpha = pallas::Scalar::random(&mut rng);
|
||||
let rk = ak.randomize(&alpha);
|
||||
let note_type = self.spend.note.note_type();
|
||||
|
||||
let note = Note::new(self.output.recipient, self.output.value, nf_old, &mut rng);
|
||||
let note = Note::new(
|
||||
self.output.recipient,
|
||||
self.output.value,
|
||||
note_type,
|
||||
nf_old,
|
||||
&mut rng,
|
||||
);
|
||||
let cm_new = note.commitment();
|
||||
let cmx = cm_new.into();
|
||||
|
||||
|
@ -361,7 +369,11 @@ impl Builder {
|
|||
|
||||
// Verify that bsk and bvk are consistent.
|
||||
let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
|
||||
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero()))
|
||||
- ValueCommitment::derive(
|
||||
value_balance,
|
||||
ValueCommitTrapdoor::zero(),
|
||||
NoteType::native(),
|
||||
))
|
||||
.into_bvk();
|
||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use memuse::DynamicUsage;
|
|||
use nonempty::NonEmpty;
|
||||
use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk};
|
||||
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
action::Action,
|
||||
address::Address,
|
||||
|
@ -376,6 +377,7 @@ impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
|
|||
- ValueCommitment::derive(
|
||||
ValueSum::from_raw(self.value_balance.into()),
|
||||
ValueCommitTrapdoor::zero(),
|
||||
NoteType::native(),
|
||||
))
|
||||
.into_bvk()
|
||||
}
|
||||
|
|
|
@ -882,6 +882,7 @@ mod tests {
|
|||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
keys::SpendValidatingKey,
|
||||
note::Note,
|
||||
|
@ -905,7 +906,7 @@ mod tests {
|
|||
|
||||
let value = spent_note.value() - output_note.value();
|
||||
let rcv = ValueCommitTrapdoor::random(&mut rng);
|
||||
let cv_net = ValueCommitment::derive(value, rcv.clone());
|
||||
let cv_net = ValueCommitment::derive(value, rcv, NoteType::native());
|
||||
|
||||
let path = MerklePath::dummy(&mut rng);
|
||||
let anchor = path.root(spent_note.commitment().into());
|
||||
|
|
|
@ -19,8 +19,12 @@ pub mod value_commit_v;
|
|||
pub const ORCHARD_PERSONALIZATION: &str = "z.cash:Orchard";
|
||||
|
||||
/// SWU hash-to-curve personalization for the value commitment generator
|
||||
/// TODO: should we change to "NOTE_TYPE_PERSONALIZATION"?
|
||||
pub const VALUE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-cv";
|
||||
|
||||
/// SWU hash-to-curve personalization for the note type generator
|
||||
// pub const NOTE_TYPE_PERSONALIZATION: &str = "z.cash:Orchard-NoteType";
|
||||
|
||||
/// SWU hash-to-curve value for the value commitment generator
|
||||
pub const VALUE_COMMITMENT_V_BYTES: [u8; 1] = *b"v";
|
||||
|
||||
|
|
|
@ -1045,6 +1045,7 @@ mod tests {
|
|||
testing::{arb_diversifier_index, arb_diversifier_key, arb_esk, arb_spending_key},
|
||||
*,
|
||||
};
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
|
||||
value::NoteValue,
|
||||
|
@ -1136,6 +1137,7 @@ mod tests {
|
|||
let note = Note::from_parts(
|
||||
addr,
|
||||
NoteValue::from_raw(tv.note_v),
|
||||
NoteType::native(),
|
||||
rho,
|
||||
RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(),
|
||||
);
|
||||
|
|
18
src/note.rs
18
src/note.rs
|
@ -19,6 +19,9 @@ pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment};
|
|||
pub(crate) mod nullifier;
|
||||
pub use self::nullifier::Nullifier;
|
||||
|
||||
pub(crate) mod note_type;
|
||||
pub use self::note_type::NoteType;
|
||||
|
||||
/// The ZIP 212 seed randomness for a note.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct RandomSeed([u8; 32]);
|
||||
|
@ -86,6 +89,8 @@ pub struct Note {
|
|||
recipient: Address,
|
||||
/// The value of this note.
|
||||
value: NoteValue,
|
||||
/// The type of this note.
|
||||
note_type: NoteType,
|
||||
/// A unique creation ID for this note.
|
||||
///
|
||||
/// This is set to the nullifier of the note that was spent in the [`Action`] that
|
||||
|
@ -111,12 +116,14 @@ impl Note {
|
|||
pub(crate) fn from_parts(
|
||||
recipient: Address,
|
||||
value: NoteValue,
|
||||
note_type: NoteType,
|
||||
rho: Nullifier,
|
||||
rseed: RandomSeed,
|
||||
) -> Self {
|
||||
Note {
|
||||
recipient,
|
||||
value,
|
||||
note_type,
|
||||
rho,
|
||||
rseed,
|
||||
}
|
||||
|
@ -130,6 +137,7 @@ impl Note {
|
|||
pub(crate) fn new(
|
||||
recipient: Address,
|
||||
value: NoteValue,
|
||||
note_type: NoteType,
|
||||
rho: Nullifier,
|
||||
mut rng: impl RngCore,
|
||||
) -> Self {
|
||||
|
@ -137,6 +145,7 @@ impl Note {
|
|||
let note = Note {
|
||||
recipient,
|
||||
value,
|
||||
note_type,
|
||||
rho,
|
||||
rseed: RandomSeed::random(&mut rng, &rho),
|
||||
};
|
||||
|
@ -162,6 +171,7 @@ impl Note {
|
|||
let note = Note::new(
|
||||
recipient,
|
||||
NoteValue::zero(),
|
||||
NoteType::native(),
|
||||
rho.unwrap_or_else(|| Nullifier::dummy(rng)),
|
||||
rng,
|
||||
);
|
||||
|
@ -179,6 +189,11 @@ impl Note {
|
|||
self.value
|
||||
}
|
||||
|
||||
/// Returns the note type
|
||||
pub fn note_type(&self) -> NoteType {
|
||||
self.note_type
|
||||
}
|
||||
|
||||
/// Returns the rseed value of this note.
|
||||
pub(crate) fn rseed(&self) -> &RandomSeed {
|
||||
&self.rseed
|
||||
|
@ -265,6 +280,7 @@ impl fmt::Debug for TransmittedNoteCiphertext {
|
|||
pub mod testing {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::note::note_type::testing::arb_note_type;
|
||||
use crate::{
|
||||
address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
|
||||
};
|
||||
|
@ -284,10 +300,12 @@ pub mod testing {
|
|||
recipient in arb_address(),
|
||||
rho in arb_nullifier(),
|
||||
rseed in arb_rseed(),
|
||||
note_type in arb_note_type(),
|
||||
) -> Note {
|
||||
Note {
|
||||
recipient,
|
||||
value,
|
||||
note_type,
|
||||
rho,
|
||||
rseed,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
use group::GroupEncoding;
|
||||
use halo2_proofs::arithmetic::CurveExt;
|
||||
use pasta_curves::pallas;
|
||||
use subtle::CtOption;
|
||||
|
||||
use crate::constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_V_BYTES};
|
||||
use crate::keys::IssuerValidatingKey;
|
||||
|
||||
/// Note type identifier.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct NoteType(pub(crate) pallas::Point);
|
||||
|
||||
const MAX_ASSET_DESCRIPTION_SIZE: usize = 512;
|
||||
|
||||
// the hasher used to derive the assetID
|
||||
#[allow(non_snake_case)]
|
||||
fn assetID_hasher(msg: Vec<u8>) -> pallas::Point {
|
||||
// TODO(zsa) replace personalization, will require circuit change?
|
||||
pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION)(&msg)
|
||||
}
|
||||
|
||||
impl NoteType {
|
||||
/// Deserialize the note_type from a byte array.
|
||||
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||
pallas::Point::from_bytes(bytes).map(NoteType)
|
||||
}
|
||||
|
||||
/// Serialize the note_type to its canonical byte representation.
|
||||
pub fn to_bytes(self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
/// $DeriveNoteType$.
|
||||
///
|
||||
/// Defined in [Zcash Protocol Spec § TBD: Note Types][notetypes].
|
||||
///
|
||||
/// [notetypes]: https://zips.z.cash/protocol/nu5.pdf#notetypes
|
||||
#[allow(non_snake_case)]
|
||||
pub fn derive(ik: &IssuerValidatingKey, assetDesc: Vec<u8>) -> Self {
|
||||
assert!(assetDesc.len() < MAX_ASSET_DESCRIPTION_SIZE);
|
||||
|
||||
let mut s = vec![];
|
||||
s.extend(ik.to_bytes());
|
||||
s.extend(assetDesc);
|
||||
|
||||
NoteType(assetID_hasher(s))
|
||||
}
|
||||
|
||||
/// Note type for the "native" currency (zec), maintains backward compatibility with Orchard untyped notes.
|
||||
pub fn native() -> Self {
|
||||
NoteType(assetID_hasher(VALUE_COMMITMENT_V_BYTES.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generators for property testing.
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
|
||||
pub mod testing {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::NoteType;
|
||||
|
||||
use crate::keys::{testing::arb_spending_key, IssuerAuthorizingKey, IssuerValidatingKey};
|
||||
|
||||
prop_compose! {
|
||||
/// Generate a uniformly distributed note type
|
||||
pub fn arb_note_type()(
|
||||
sk in arb_spending_key(),
|
||||
bytes32a in prop::array::uniform32(prop::num::u8::ANY),
|
||||
bytes32b in prop::array::uniform32(prop::num::u8::ANY),
|
||||
) -> NoteType {
|
||||
let bytes64 = [bytes32a, bytes32b].concat();
|
||||
let isk = IssuerAuthorizingKey::from(&sk);
|
||||
NoteType::derive(&IssuerValidatingKey::from(&isk), bytes64)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use zcash_note_encryption::{
|
|||
OUT_PLAINTEXT_SIZE,
|
||||
};
|
||||
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
action::Action,
|
||||
keys::{
|
||||
|
@ -75,7 +76,8 @@ where
|
|||
let pk_d = get_validated_pk_d(&diversifier)?;
|
||||
|
||||
let recipient = Address::from_parts(diversifier, pk_d);
|
||||
let note = Note::from_parts(recipient, value, domain.rho, rseed);
|
||||
// TODO: add note_type
|
||||
let note = Note::from_parts(recipient, value, NoteType::native(), domain.rho, rseed);
|
||||
Some((note, recipient))
|
||||
}
|
||||
|
||||
|
@ -151,6 +153,7 @@ impl Domain for OrchardDomain {
|
|||
np[0] = 0x02;
|
||||
np[1..12].copy_from_slice(note.recipient().diversifier().as_array());
|
||||
np[12..20].copy_from_slice(¬e.value().to_bytes());
|
||||
// todo: add note_type
|
||||
np[20..52].copy_from_slice(note.rseed().as_bytes());
|
||||
np[52..].copy_from_slice(memo);
|
||||
NotePlaintextBytes(np)
|
||||
|
@ -316,6 +319,7 @@ mod tests {
|
|||
};
|
||||
|
||||
use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption};
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
action::Action,
|
||||
keys::{
|
||||
|
@ -369,7 +373,7 @@ mod tests {
|
|||
assert_eq!(ock.as_ref(), tv.ock);
|
||||
|
||||
let recipient = Address::from_parts(d, pk_d);
|
||||
let note = Note::from_parts(recipient, value, rho, rseed);
|
||||
let note = Note::from_parts(recipient, value, NoteType::native(), rho, rseed);
|
||||
assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx);
|
||||
|
||||
let action = Action::from_parts(
|
||||
|
|
66
src/value.rs
66
src/value.rs
|
@ -50,10 +50,9 @@ use pasta_curves::{
|
|||
use rand::RngCore;
|
||||
use subtle::CtOption;
|
||||
|
||||
use crate::note::NoteType;
|
||||
use crate::{
|
||||
constants::fixed_bases::{
|
||||
VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES, VALUE_COMMITMENT_V_BYTES,
|
||||
},
|
||||
constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES},
|
||||
primitives::redpallas::{self, Binding},
|
||||
};
|
||||
|
||||
|
@ -209,7 +208,7 @@ impl TryFrom<ValueSum> for i64 {
|
|||
}
|
||||
|
||||
/// The blinding factor for a [`ValueCommitment`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ValueCommitTrapdoor(pallas::Scalar);
|
||||
|
||||
impl ValueCommitTrapdoor {
|
||||
|
@ -292,9 +291,8 @@ impl ValueCommitment {
|
|||
///
|
||||
/// [concretehomomorphiccommit]: https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn derive(value: ValueSum, rcv: ValueCommitTrapdoor) -> Self {
|
||||
pub(crate) fn derive(value: ValueSum, rcv: ValueCommitTrapdoor, note_type: NoteType) -> Self {
|
||||
let hasher = pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
|
||||
let V = hasher(&VALUE_COMMITMENT_V_BYTES);
|
||||
let R = hasher(&VALUE_COMMITMENT_R_BYTES);
|
||||
let abs_value = u64::try_from(value.0.abs()).expect("value must be in valid range");
|
||||
|
||||
|
@ -304,7 +302,9 @@ impl ValueCommitment {
|
|||
pallas::Scalar::from(abs_value)
|
||||
};
|
||||
|
||||
ValueCommitment(V * value + R * rcv.0)
|
||||
let V_zsa = note_type.0;
|
||||
|
||||
ValueCommitment(V_zsa * value + R * rcv.0)
|
||||
}
|
||||
|
||||
pub(crate) fn into_bvk(self) -> redpallas::VerificationKey<Binding> {
|
||||
|
@ -407,6 +407,8 @@ pub mod testing {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::note::note_type::testing::arb_note_type;
|
||||
use crate::note::NoteType;
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::{
|
||||
|
@ -415,6 +417,29 @@ mod tests {
|
|||
};
|
||||
use crate::primitives::redpallas;
|
||||
|
||||
fn _bsk_consistent_with_bvk(values: &[(ValueSum, ValueCommitTrapdoor)], note_type: NoteType) {
|
||||
let value_balance = values
|
||||
.iter()
|
||||
.map(|(value, _)| value)
|
||||
.sum::<Result<ValueSum, OverflowError>>()
|
||||
.expect("we generate values that won't overflow");
|
||||
|
||||
let bsk = values
|
||||
.iter()
|
||||
.map(|(_, rcv)| rcv)
|
||||
.sum::<ValueCommitTrapdoor>()
|
||||
.into_bsk();
|
||||
|
||||
let bvk = (values
|
||||
.iter()
|
||||
.map(|(value, rcv)| ValueCommitment::derive(*value, *rcv, note_type))
|
||||
.sum::<ValueCommitment>()
|
||||
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero(), note_type))
|
||||
.into_bvk();
|
||||
|
||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn bsk_consistent_with_bvk(
|
||||
|
@ -422,28 +447,13 @@ mod tests {
|
|||
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
|
||||
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor()), n_values)
|
||||
)
|
||||
)
|
||||
),
|
||||
arb_note_type in arb_note_type(),
|
||||
) {
|
||||
let value_balance = values
|
||||
.iter()
|
||||
.map(|(value, _)| value)
|
||||
.sum::<Result<ValueSum, OverflowError>>()
|
||||
.expect("we generate values that won't overflow");
|
||||
|
||||
let bsk = values
|
||||
.iter()
|
||||
.map(|(_, rcv)| rcv)
|
||||
.sum::<ValueCommitTrapdoor>()
|
||||
.into_bsk();
|
||||
|
||||
let bvk = (values
|
||||
.into_iter()
|
||||
.map(|(value, rcv)| ValueCommitment::derive(value, rcv))
|
||||
.sum::<ValueCommitment>()
|
||||
- ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero()))
|
||||
.into_bvk();
|
||||
|
||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||
// Test with native note type (zec)
|
||||
_bsk_consistent_with_bvk(&values, NoteType::native());
|
||||
// Test with arbitrary note type
|
||||
_bsk_consistent_with_bvk(&values, arb_note_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue