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
|
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
|
orbs:
|
||||||
|
slack: circleci/slack@4.1
|
||||||
|
|
||||||
# Define a job to be invoked later in a workflow.
|
# Define a job to be invoked later in a workflow.
|
||||||
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -17,8 +20,15 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: "cargo test"
|
name: "cargo test"
|
||||||
command: |
|
command: |
|
||||||
|
sudo apt update && sudo apt-get install libfontconfig libfontconfig1-dev libfreetype6-dev;
|
||||||
cargo version;
|
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
|
# Invoke jobs via workflows
|
||||||
|
@ -26,4 +36,5 @@ jobs:
|
||||||
workflows:
|
workflows:
|
||||||
build-and-test:
|
build-and-test:
|
||||||
jobs:
|
jobs:
|
||||||
- cargo-test
|
- cargo-test:
|
||||||
|
context: CI-Orchard-slack
|
||||||
|
|
|
@ -126,6 +126,7 @@ pub(crate) mod testing {
|
||||||
|
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
note::{
|
note::{
|
||||||
commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier,
|
commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier,
|
||||||
|
@ -150,7 +151,8 @@ pub(crate) mod testing {
|
||||||
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
||||||
let cv_net = ValueCommitment::derive(
|
let cv_net = ValueCommitment::derive(
|
||||||
spend_value - output_value,
|
spend_value - output_value,
|
||||||
ValueCommitTrapdoor::zero()
|
ValueCommitTrapdoor::zero(),
|
||||||
|
NoteType::native()
|
||||||
);
|
);
|
||||||
// FIXME: make a real one from the note.
|
// FIXME: make a real one from the note.
|
||||||
let encrypted_note = TransmittedNoteCiphertext {
|
let encrypted_note = TransmittedNoteCiphertext {
|
||||||
|
@ -181,7 +183,8 @@ pub(crate) mod testing {
|
||||||
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
let cmx = ExtractedNoteCommitment::from(note.commitment());
|
||||||
let cv_net = ValueCommitment::derive(
|
let cv_net = ValueCommitment::derive(
|
||||||
spend_value - output_value,
|
spend_value - output_value,
|
||||||
ValueCommitTrapdoor::zero()
|
ValueCommitTrapdoor::zero(),
|
||||||
|
NoteType::native()
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIXME: make a real one from the note.
|
// FIXME: make a real one from the note.
|
||||||
|
|
|
@ -8,6 +8,7 @@ use nonempty::NonEmpty;
|
||||||
use pasta_curves::pallas;
|
use pasta_curves::pallas;
|
||||||
use rand::{prelude::SliceRandom, CryptoRng, RngCore};
|
use rand::{prelude::SliceRandom, CryptoRng, RngCore};
|
||||||
|
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
address::Address,
|
address::Address,
|
||||||
|
@ -140,7 +141,7 @@ impl ActionInfo {
|
||||||
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
/// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
|
||||||
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
|
fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
|
||||||
let v_net = self.value_sum();
|
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 nf_old = self.spend.note.nullifier(&self.spend.fvk);
|
||||||
let sender_address = self.spend.note.recipient();
|
let sender_address = self.spend.note.recipient();
|
||||||
|
@ -150,8 +151,15 @@ impl ActionInfo {
|
||||||
let ak: SpendValidatingKey = self.spend.fvk.clone().into();
|
let ak: SpendValidatingKey = self.spend.fvk.clone().into();
|
||||||
let alpha = pallas::Scalar::random(&mut rng);
|
let alpha = pallas::Scalar::random(&mut rng);
|
||||||
let rk = ak.randomize(&alpha);
|
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 cm_new = note.commitment();
|
||||||
let cmx = cm_new.into();
|
let cmx = cm_new.into();
|
||||||
|
|
||||||
|
@ -361,7 +369,11 @@ impl Builder {
|
||||||
|
|
||||||
// Verify that bsk and bvk are consistent.
|
// Verify that bsk and bvk are consistent.
|
||||||
let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
|
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();
|
.into_bvk();
|
||||||
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use memuse::DynamicUsage;
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk};
|
use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk};
|
||||||
|
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
address::Address,
|
address::Address,
|
||||||
|
@ -376,6 +377,7 @@ impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
|
||||||
- ValueCommitment::derive(
|
- ValueCommitment::derive(
|
||||||
ValueSum::from_raw(self.value_balance.into()),
|
ValueSum::from_raw(self.value_balance.into()),
|
||||||
ValueCommitTrapdoor::zero(),
|
ValueCommitTrapdoor::zero(),
|
||||||
|
NoteType::native(),
|
||||||
))
|
))
|
||||||
.into_bvk()
|
.into_bvk()
|
||||||
}
|
}
|
||||||
|
|
|
@ -882,6 +882,7 @@ mod tests {
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{rngs::OsRng, RngCore};
|
||||||
|
|
||||||
use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
|
use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K};
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
keys::SpendValidatingKey,
|
keys::SpendValidatingKey,
|
||||||
note::Note,
|
note::Note,
|
||||||
|
@ -905,7 +906,7 @@ mod tests {
|
||||||
|
|
||||||
let value = spent_note.value() - output_note.value();
|
let value = spent_note.value() - output_note.value();
|
||||||
let rcv = ValueCommitTrapdoor::random(&mut rng);
|
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 path = MerklePath::dummy(&mut rng);
|
||||||
let anchor = path.root(spent_note.commitment().into());
|
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";
|
pub const ORCHARD_PERSONALIZATION: &str = "z.cash:Orchard";
|
||||||
|
|
||||||
/// SWU hash-to-curve personalization for the value commitment generator
|
/// 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";
|
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
|
/// SWU hash-to-curve value for the value commitment generator
|
||||||
pub const VALUE_COMMITMENT_V_BYTES: [u8; 1] = *b"v";
|
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},
|
testing::{arb_diversifier_index, arb_diversifier_key, arb_esk, arb_spending_key},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
|
note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
|
||||||
value::NoteValue,
|
value::NoteValue,
|
||||||
|
@ -1136,6 +1137,7 @@ mod tests {
|
||||||
let note = Note::from_parts(
|
let note = Note::from_parts(
|
||||||
addr,
|
addr,
|
||||||
NoteValue::from_raw(tv.note_v),
|
NoteValue::from_raw(tv.note_v),
|
||||||
|
NoteType::native(),
|
||||||
rho,
|
rho,
|
||||||
RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(),
|
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(crate) mod nullifier;
|
||||||
pub use self::nullifier::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.
|
/// The ZIP 212 seed randomness for a note.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub(crate) struct RandomSeed([u8; 32]);
|
pub(crate) struct RandomSeed([u8; 32]);
|
||||||
|
@ -86,6 +89,8 @@ pub struct Note {
|
||||||
recipient: Address,
|
recipient: Address,
|
||||||
/// The value of this note.
|
/// The value of this note.
|
||||||
value: NoteValue,
|
value: NoteValue,
|
||||||
|
/// The type of this note.
|
||||||
|
note_type: NoteType,
|
||||||
/// A unique creation ID for this note.
|
/// A unique creation ID for this note.
|
||||||
///
|
///
|
||||||
/// This is set to the nullifier of the note that was spent in the [`Action`] that
|
/// 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(
|
pub(crate) fn from_parts(
|
||||||
recipient: Address,
|
recipient: Address,
|
||||||
value: NoteValue,
|
value: NoteValue,
|
||||||
|
note_type: NoteType,
|
||||||
rho: Nullifier,
|
rho: Nullifier,
|
||||||
rseed: RandomSeed,
|
rseed: RandomSeed,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Note {
|
Note {
|
||||||
recipient,
|
recipient,
|
||||||
value,
|
value,
|
||||||
|
note_type,
|
||||||
rho,
|
rho,
|
||||||
rseed,
|
rseed,
|
||||||
}
|
}
|
||||||
|
@ -130,6 +137,7 @@ impl Note {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
recipient: Address,
|
recipient: Address,
|
||||||
value: NoteValue,
|
value: NoteValue,
|
||||||
|
note_type: NoteType,
|
||||||
rho: Nullifier,
|
rho: Nullifier,
|
||||||
mut rng: impl RngCore,
|
mut rng: impl RngCore,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -137,6 +145,7 @@ impl Note {
|
||||||
let note = Note {
|
let note = Note {
|
||||||
recipient,
|
recipient,
|
||||||
value,
|
value,
|
||||||
|
note_type,
|
||||||
rho,
|
rho,
|
||||||
rseed: RandomSeed::random(&mut rng, &rho),
|
rseed: RandomSeed::random(&mut rng, &rho),
|
||||||
};
|
};
|
||||||
|
@ -162,6 +171,7 @@ impl Note {
|
||||||
let note = Note::new(
|
let note = Note::new(
|
||||||
recipient,
|
recipient,
|
||||||
NoteValue::zero(),
|
NoteValue::zero(),
|
||||||
|
NoteType::native(),
|
||||||
rho.unwrap_or_else(|| Nullifier::dummy(rng)),
|
rho.unwrap_or_else(|| Nullifier::dummy(rng)),
|
||||||
rng,
|
rng,
|
||||||
);
|
);
|
||||||
|
@ -179,6 +189,11 @@ impl Note {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the note type
|
||||||
|
pub fn note_type(&self) -> NoteType {
|
||||||
|
self.note_type
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the rseed value of this note.
|
/// Returns the rseed value of this note.
|
||||||
pub(crate) fn rseed(&self) -> &RandomSeed {
|
pub(crate) fn rseed(&self) -> &RandomSeed {
|
||||||
&self.rseed
|
&self.rseed
|
||||||
|
@ -265,6 +280,7 @@ impl fmt::Debug for TransmittedNoteCiphertext {
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
use crate::note::note_type::testing::arb_note_type;
|
||||||
use crate::{
|
use crate::{
|
||||||
address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
|
address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue,
|
||||||
};
|
};
|
||||||
|
@ -284,10 +300,12 @@ pub mod testing {
|
||||||
recipient in arb_address(),
|
recipient in arb_address(),
|
||||||
rho in arb_nullifier(),
|
rho in arb_nullifier(),
|
||||||
rseed in arb_rseed(),
|
rseed in arb_rseed(),
|
||||||
|
note_type in arb_note_type(),
|
||||||
) -> Note {
|
) -> Note {
|
||||||
Note {
|
Note {
|
||||||
recipient,
|
recipient,
|
||||||
value,
|
value,
|
||||||
|
note_type,
|
||||||
rho,
|
rho,
|
||||||
rseed,
|
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,
|
OUT_PLAINTEXT_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
keys::{
|
keys::{
|
||||||
|
@ -75,7 +76,8 @@ where
|
||||||
let pk_d = get_validated_pk_d(&diversifier)?;
|
let pk_d = get_validated_pk_d(&diversifier)?;
|
||||||
|
|
||||||
let recipient = Address::from_parts(diversifier, pk_d);
|
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))
|
Some((note, recipient))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +153,7 @@ impl Domain for OrchardDomain {
|
||||||
np[0] = 0x02;
|
np[0] = 0x02;
|
||||||
np[1..12].copy_from_slice(note.recipient().diversifier().as_array());
|
np[1..12].copy_from_slice(note.recipient().diversifier().as_array());
|
||||||
np[12..20].copy_from_slice(¬e.value().to_bytes());
|
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[20..52].copy_from_slice(note.rseed().as_bytes());
|
||||||
np[52..].copy_from_slice(memo);
|
np[52..].copy_from_slice(memo);
|
||||||
NotePlaintextBytes(np)
|
NotePlaintextBytes(np)
|
||||||
|
@ -316,6 +319,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption};
|
use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption};
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
keys::{
|
keys::{
|
||||||
|
@ -369,7 +373,7 @@ mod tests {
|
||||||
assert_eq!(ock.as_ref(), tv.ock);
|
assert_eq!(ock.as_ref(), tv.ock);
|
||||||
|
|
||||||
let recipient = Address::from_parts(d, pk_d);
|
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);
|
assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx);
|
||||||
|
|
||||||
let action = Action::from_parts(
|
let action = Action::from_parts(
|
||||||
|
|
66
src/value.rs
66
src/value.rs
|
@ -50,10 +50,9 @@ use pasta_curves::{
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use subtle::CtOption;
|
use subtle::CtOption;
|
||||||
|
|
||||||
|
use crate::note::NoteType;
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::fixed_bases::{
|
constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES},
|
||||||
VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES, VALUE_COMMITMENT_V_BYTES,
|
|
||||||
},
|
|
||||||
primitives::redpallas::{self, Binding},
|
primitives::redpallas::{self, Binding},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -209,7 +208,7 @@ impl TryFrom<ValueSum> for i64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The blinding factor for a [`ValueCommitment`].
|
/// The blinding factor for a [`ValueCommitment`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ValueCommitTrapdoor(pallas::Scalar);
|
pub struct ValueCommitTrapdoor(pallas::Scalar);
|
||||||
|
|
||||||
impl ValueCommitTrapdoor {
|
impl ValueCommitTrapdoor {
|
||||||
|
@ -292,9 +291,8 @@ impl ValueCommitment {
|
||||||
///
|
///
|
||||||
/// [concretehomomorphiccommit]: https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit
|
/// [concretehomomorphiccommit]: https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit
|
||||||
#[allow(non_snake_case)]
|
#[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 hasher = pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION);
|
||||||
let V = hasher(&VALUE_COMMITMENT_V_BYTES);
|
|
||||||
let R = hasher(&VALUE_COMMITMENT_R_BYTES);
|
let R = hasher(&VALUE_COMMITMENT_R_BYTES);
|
||||||
let abs_value = u64::try_from(value.0.abs()).expect("value must be in valid range");
|
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)
|
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> {
|
pub(crate) fn into_bvk(self) -> redpallas::VerificationKey<Binding> {
|
||||||
|
@ -407,6 +407,8 @@ pub mod testing {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::note::note_type::testing::arb_note_type;
|
||||||
|
use crate::note::NoteType;
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -415,6 +417,29 @@ mod tests {
|
||||||
};
|
};
|
||||||
use crate::primitives::redpallas;
|
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! {
|
proptest! {
|
||||||
#[test]
|
#[test]
|
||||||
fn bsk_consistent_with_bvk(
|
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|
|
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)
|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor()), n_values)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
arb_note_type in arb_note_type(),
|
||||||
) {
|
) {
|
||||||
let value_balance = values
|
// Test with native note type (zec)
|
||||||
.iter()
|
_bsk_consistent_with_bvk(&values, NoteType::native());
|
||||||
.map(|(value, _)| value)
|
// Test with arbitrary note type
|
||||||
.sum::<Result<ValueSum, OverflowError>>()
|
_bsk_consistent_with_bvk(&values, arb_note_type);
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue