Added NoteType to Notes (#2)

* Added NoteType to Notes
* Added NoteType to value commitment derivation
This commit is contained in:
Paul 2022-06-14 21:23:03 +03:00 committed by GitHub
parent 769f2f7b9c
commit 8a6f59927e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 182 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

77
src/note/note_type.rs Normal file
View File

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

View File

@ -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(&note.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(

View File

@ -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);
}
}
}