diff --git a/circuits/CHANGELOG.md b/circuits/CHANGELOG.md index 9cebbc7..6119a67 100644 --- a/circuits/CHANGELOG.md +++ b/circuits/CHANGELOG.md @@ -11,8 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `phoenix-circuits` as a workspace member of `phoenix` [#171] - Add elgamal encryption and decryption gadgets [#171] +- Add the `transaction` module [#169] +[#169]: https://github.com/dusk-network/phoenix/issues/169 [#171]: https://github.com/dusk-network/phoenix/issues/171 diff --git a/circuits/Cargo.toml b/circuits/Cargo.toml index 33e216b..567de09 100644 --- a/circuits/Cargo.toml +++ b/circuits/Cargo.toml @@ -13,7 +13,12 @@ exclude = [".github/workflows/dusk-ci.yml", ".gitignore"] phoenix-core = { path = "../core" } dusk-plonk = { version = "0.19", default-features = false } dusk-jubjub = { version = "0.14", default-features = false } +poseidon-merkle = { version = "0.5", features = ["rkyv-impl", "zk", "size_32"] } +dusk-poseidon = { version = "0.33", default-features = false } +jubjub-schnorr = { version = "0.3", default-features = false, features = ["double", "alloc"] } +rand_core = { version = "0.6", default-features = false } +rand = "0.8" [dev-dependencies] ff = { version = "0.13", default-features = false } -rand_core = { version = "0.6", default-features = false } +lazy_static = "1.4" diff --git a/circuits/README.md b/circuits/README.md index 091fb29..a79192b 100644 --- a/circuits/README.md +++ b/circuits/README.md @@ -8,4 +8,4 @@ This library contains the implementation of the Phoenix-circuits, to prove, in z 2. Ownership: the sender holds the note secret key for every note that is about to be spent. 3. Nullification: the nullifier is calculated correctly. 4. Minting: the value commitment for the newly minted notes are computed correctly. -5. Balance integrity: the sum of the value of all spent notes is equal to the value of the sum of all minted notes + the gas fee + a crossover, where a crossover refers to funds being transfered to a contract. +5. Balance integrity: the sum of the values of all spent notes is equal to the sum of the values of all minted notes + the gas fee + a crossover, where a crossover refers to funds being transfered to a contract. diff --git a/circuits/src/lib.rs b/circuits/src/lib.rs index 9c0f7bc..9a61176 100644 --- a/circuits/src/lib.rs +++ b/circuits/src/lib.rs @@ -10,8 +10,10 @@ #![deny(missing_docs)] #![no_std] -/// Encryption algorithm mod encryption; +/// Transaction structs, gadget, and circuit +pub mod transaction; + /// ElGamal asymmetric cipher pub use encryption::elgamal; diff --git a/circuits/src/transaction.rs b/circuits/src/transaction.rs new file mode 100644 index 0000000..4985d0b --- /dev/null +++ b/circuits/src/transaction.rs @@ -0,0 +1,419 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use dusk_jubjub::{ + JubJubScalar, GENERATOR, GENERATOR_NUMS, GENERATOR_NUMS_EXTENDED, +}; +use dusk_plonk::prelude::*; +use dusk_poseidon::sponge; +use jubjub_schnorr::{gadgets, SignatureDouble}; +use poseidon_merkle::{zk::opening_gadget, Item, Opening, Tree}; + +use rand::rngs::StdRng; +use rand_core::{CryptoRng, RngCore, SeedableRng}; + +extern crate alloc; +use alloc::vec::Vec; + +use phoenix_core::{Error as PhoenixError, Note, Ownable, SecretKey, ViewKey}; + +const TX_OUTPUT_NOTES: usize = 2; + +/// Struct representing a note willing to be spent, in a way +/// suitable for being introduced in the transfer circuit +#[derive(Debug, Clone)] +pub struct TxInputNote { + pub(crate) merkle_opening: Opening<(), H, A>, + pub(crate) note: Note, + pub(crate) note_pk_p: JubJubAffine, + pub(crate) value: u64, + pub(crate) blinding_factor: JubJubScalar, + pub(crate) nullifier: BlsScalar, + pub(crate) signature: SignatureDouble, +} + +#[derive(Debug, Clone)] +struct WitnessTxInputNote { + note_pk: WitnessPoint, + note_pk_p: WitnessPoint, + note_type: Witness, + pos: Witness, + value: Witness, + blinding_factor: Witness, + nullifier: Witness, + signature_u: Witness, + signature_r: WitnessPoint, + signature_r_p: WitnessPoint, +} + +impl TxInputNote { + /// Create a tx input note + pub fn new( + note: &Note, + merkle_opening: poseidon_merkle::Opening<(), H, A>, + sk: &SecretKey, + skeleteon_hash: BlsScalar, + rng: &mut (impl RngCore + CryptoRng), + ) -> Result, PhoenixError> { + let note_sk = sk.gen_note_sk(note); + let note_pk_p = + JubJubAffine::from(GENERATOR_NUMS_EXTENDED * note_sk.as_ref()); + + let vk = ViewKey::from(sk); + let value = note.value(Some(&vk))?; + let blinding_factor = note.blinding_factor(Some(&vk))?; + + let nullifier = sponge::hash(&[ + note_pk_p.get_u(), + note_pk_p.get_v(), + (*note.pos()).into(), + ]); + + let signature = note_sk.sign_double(rng, skeleteon_hash); + + Ok(crate::transaction::TxInputNote { + merkle_opening, + note: note.clone(), + note_pk_p, + value, + blinding_factor, + nullifier, + signature, + }) + } + + fn append_to_circuit(&self, composer: &mut Composer) -> WitnessTxInputNote { + let nullifier = composer.append_public(self.nullifier); + + let note_pk = composer + .append_point(*self.note.stealth_address().note_pk().as_ref()); + let note_pk_p = composer.append_point(self.note_pk_p); + + let note_type = composer + .append_witness(BlsScalar::from(self.note.note_type() as u64)); + let pos = composer.append_witness(BlsScalar::from(*self.note.pos())); + + let value = composer.append_witness(self.value); + let blinding_factor = composer.append_witness(self.blinding_factor); + + let signature_u = composer.append_witness(*self.signature.u()); + let signature_r = composer.append_point(self.signature.R()); + let signature_r_p = composer.append_point(self.signature.R_prime()); + + WitnessTxInputNote { + note_pk, + note_pk_p, + + note_type, + pos, + value, + blinding_factor, + + nullifier, + + signature_u, + signature_r, + signature_r_p, + } + } +} + +/// Struct representing a note willing to be created, in a way +/// suitable for being introduced in the transfer circuit +#[derive(Debug, Clone)] +pub struct TxOutputNote { + pub(crate) value: u64, + pub(crate) value_commitment: JubJubAffine, + pub(crate) blinding_factor: JubJubScalar, +} + +#[derive(Debug, Clone)] +struct WitnessTxOutputNote { + value: Witness, + value_commitment: WitnessPoint, + blinding_factor: Witness, +} + +impl TxOutputNote { + /// Create a tx output note + pub fn new( + note: &Note, + vk: &ViewKey, + ) -> Result { + Ok(crate::transaction::TxOutputNote { + value: note.value(Some(vk))?, + value_commitment: note.value_commitment().into(), + blinding_factor: note.blinding_factor(Some(vk))?, + }) + } + + fn append_to_circuit( + &self, + composer: &mut Composer, + ) -> WitnessTxOutputNote { + let value = composer.append_witness(self.value); + let value_commitment = + composer.append_public_point(self.value_commitment); + let blinding_factor = composer.append_witness(self.blinding_factor); + + WitnessTxOutputNote { + value, + value_commitment, + blinding_factor, + } + } +} + +/// Transaction gadget proving the following properties in ZK for a generic +/// `I` [`TxInputNote`] and [`TX_OUTPUT_NOTES`] (2) [`TxOutputNote`]: +/// +/// 1. Membership: every [`TxInputNote`] is included in the Merkle tree of +/// notes. +/// 2. Ownership: the sender holds the note secret key for every +/// [`TxInputNote`]. +/// 3. Nullification: the nullifier is calculated correctly. +/// 4. Minting: the value commitment for every [`TxOutputNote`] is computed +/// correctly. +/// 5. Balance integrity: the sum of the values of all [`TxInputNote`] is equal +/// to the sum of the values of all [`TxOutputNote`] + the gas fee + a +/// crossover, where a crossover refers to funds being transfered to a +/// contract. +pub fn gadget( + composer: &mut Composer, + tx_input_notes: &[TxInputNote; I], + tx_output_notes: &[TxOutputNote; TX_OUTPUT_NOTES], + skeleton_hash: &BlsScalar, + root: &BlsScalar, + crossover: u64, + max_fee: u64, +) -> Result<(), Error> { + let skeleton_hash_pi = composer.append_public(*skeleton_hash); + let root_pi = composer.append_public(*root); + + let mut tx_input_notes_sum = Composer::ZERO; + + // NULLIFY ALL TX INPUT NOTES + for tx_input_note in tx_input_notes { + // APPEND THE WITNESSES TO THE CIRCUIT + let w_tx_input_note = tx_input_note.append_to_circuit(composer); + + // VERIFY THE DOUBLE KEY SCHNORR SIGNATURE + gadgets::verify_signature_double( + composer, + w_tx_input_note.signature_u, + w_tx_input_note.signature_r, + w_tx_input_note.signature_r_p, + w_tx_input_note.note_pk, + w_tx_input_note.note_pk_p, + skeleton_hash_pi, + )?; + + // COMPUTE AND ASSERT THE NULLIFIER + let nullifier = sponge::gadget( + composer, + &[ + *w_tx_input_note.note_pk_p.x(), + *w_tx_input_note.note_pk_p.y(), + w_tx_input_note.pos, + ], + ); + composer.assert_equal(nullifier, w_tx_input_note.nullifier); + + // PERFORM A RANGE CHECK ([0, 2^64 - 1]) ON THE VALUE OF THE NOTE + composer.component_range::<32>(w_tx_input_note.value); + + // SUM UP ALL THE TX INPUT NOTE VALUES + let constraint = Constraint::new() + .left(1) + .a(tx_input_notes_sum) + .right(1) + .b(w_tx_input_note.value); + tx_input_notes_sum = composer.gate_add(constraint); + + // COMMIT TO THE VALUE OF THE NOTE + let pc_1 = composer + .component_mul_generator(w_tx_input_note.value, GENERATOR)?; + let pc_2 = composer.component_mul_generator( + w_tx_input_note.blinding_factor, + GENERATOR_NUMS, + )?; + let value_commitment = composer.component_add_point(pc_1, pc_2); + + // COMPUTE THE NOTE HASH + let note_hash = sponge::gadget( + composer, + &[ + w_tx_input_note.note_type, + *value_commitment.x(), + *value_commitment.y(), + *w_tx_input_note.note_pk.x(), + *w_tx_input_note.note_pk.y(), + w_tx_input_note.pos, + ], + ); + + // VERIFY THE MERKLE OPENING + let root = + opening_gadget(composer, &tx_input_note.merkle_opening, note_hash); + composer.assert_equal(root, root_pi); + } + + let mut tx_output_sum = Composer::ZERO; + + // COMMIT TO ALL TX OUTPUT NOTES + for tx_output_note in tx_output_notes { + // APPEND THE WITNESSES TO THE CIRCUIT + let w_tx_output_note = tx_output_note.append_to_circuit(composer); + + // PERFORM A RANGE CHECK ([0, 2^64 - 1]) ON THE VALUE OF THE NOTE + composer.component_range::<32>(w_tx_output_note.value); + + // SUM UP ALL THE TX OUTPUT NOTE VALUES + let constraint = Constraint::new() + .left(1) + .a(tx_output_sum) + .right(1) + .b(w_tx_output_note.value); + tx_output_sum = composer.gate_add(constraint); + + // COMMIT TO THE VALUE OF THE NOTE + let pc_1 = composer + .component_mul_generator(w_tx_output_note.value, GENERATOR)?; + let pc_2 = composer.component_mul_generator( + w_tx_output_note.blinding_factor, + GENERATOR_NUMS, + )?; + let value_commitment = composer.component_add_point(pc_1, pc_2); + + composer.assert_equal_point( + w_tx_output_note.value_commitment, + value_commitment, + ); + } + + let max_fee = composer.append_public(max_fee); + let crossover = composer.append_public(crossover); + + // SUM UP THE CROSSOVER AND THE MAX FEE + let constraint = Constraint::new() + .left(1) + .a(tx_output_sum) + .right(1) + .b(max_fee) + .fourth(1) + .d(crossover); + tx_output_sum = composer.gate_add(constraint); + + // VERIFY BALANCE + composer.assert_equal(tx_input_notes_sum, tx_output_sum); + + Ok(()) +} + +/// Declaration of the transaction circuit calling the [`gadget`]. +#[derive(Debug)] +pub struct TxCircuit { + tx_input_notes: [TxInputNote; I], + tx_output_notes: [TxOutputNote; TX_OUTPUT_NOTES], + skeleton_hash: BlsScalar, + root: BlsScalar, + crossover: u64, + max_fee: u64, +} + +impl Default + for TxCircuit +{ + fn default() -> Self { + let mut rng = StdRng::seed_from_u64(0xbeef); + + let sk = SecretKey::random(&mut rng); + let vk = ViewKey::from(&sk); + + let mut tree = Tree::<(), H, A>::new(); + let skeleton_hash = BlsScalar::default(); + + let mut tx_input_notes = Vec::new(); + let note = Note::empty(); + let item = Item { + hash: note.hash(), + data: (), + }; + tree.insert(*note.pos(), item); + + for _ in 0..I { + let merkle_opening = tree.opening(*note.pos()).expect("Tree read."); + let tx_input_note = TxInputNote::new( + ¬e, + merkle_opening, + &sk, + skeleton_hash, + &mut rng, + ) + .expect("Note created properly."); + + tx_input_notes.push(tx_input_note); + } + + let tx_output_note_1 = + TxOutputNote::new(¬e, &vk).expect("Note created properly."); + let tx_output_note_2 = + TxOutputNote::new(¬e, &vk).expect("Note created properly."); + + let tx_output_notes = [tx_output_note_1, tx_output_note_2]; + + let root = BlsScalar::default(); + let crossover = u64::default(); + let max_fee = u64::default(); + + Self { + tx_input_notes: tx_input_notes.try_into().unwrap(), + tx_output_notes, + skeleton_hash, + root, + crossover, + max_fee, + } + } +} + +impl TxCircuit { + /// Create a new transfer circuit + pub fn new( + tx_input_notes: [TxInputNote; I], + tx_output_notes: [TxOutputNote; TX_OUTPUT_NOTES], + skeleton_hash: BlsScalar, + root: BlsScalar, + crossover: u64, + max_fee: u64, + ) -> Self { + Self { + tx_input_notes, + tx_output_notes, + skeleton_hash, + root, + crossover, + max_fee, + } + } +} + +impl Circuit + for TxCircuit +{ + fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { + gadget::( + composer, + &self.tx_input_notes, + &self.tx_output_notes, + &self.skeleton_hash, + &self.root, + self.crossover, + self.max_fee, + )?; + Ok(()) + } +} diff --git a/circuits/tests/transaction.rs b/circuits/tests/transaction.rs new file mode 100644 index 0000000..a833e38 --- /dev/null +++ b/circuits/tests/transaction.rs @@ -0,0 +1,255 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use rand_core::{CryptoRng, OsRng, RngCore}; + +use phoenix_circuits::transaction::{TxCircuit, TxInputNote, TxOutputNote}; +use phoenix_core::{Note, PublicKey, SecretKey, ViewKey}; + +use dusk_plonk::prelude::*; +use poseidon_merkle::{Item, Tree}; + +#[macro_use] +extern crate lazy_static; + +static LABEL: &[u8; 12] = b"dusk-network"; +const CAPACITY: usize = 17; // capacity required for the setup + +const HEIGHT: usize = 17; +const ARITY: usize = 4; + +struct TestingParameters { + sk: SecretKey, + pp: PublicParameters, + tx_input_notes: [TxInputNote; 4], + skeleton_hash: BlsScalar, + root: BlsScalar, + crossover: u64, + max_fee: u64, +} + +lazy_static! { + static ref TP: TestingParameters = { + let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng).unwrap(); + let sk = SecretKey::random(&mut OsRng); + + let mut tree = Tree::<(), HEIGHT, ARITY>::new(); + let skeleton_hash = BlsScalar::from(1234u64); + + // create and insert into the tree 4 testing tx input notes + let tx_input_notes = + create_test_tx_input_notes::<4>(&mut tree, &sk, skeleton_hash, &mut OsRng); + + // retrieve the root from the tree after inserting the notes + let root = tree.root().hash; + + let crossover = 5; + let max_fee = 5; + + TestingParameters { sk, pp, tx_input_notes, skeleton_hash, root, crossover, max_fee } + }; +} + +fn create_and_insert_test_note( + tree: &mut Tree<(), HEIGHT, ARITY>, + pk: &PublicKey, + pos: u64, + value: u64, + rng: &mut (impl RngCore + CryptoRng), +) -> Note { + let mut note = Note::transparent(rng, pk, value); + note.set_pos(pos); + + let item = Item { + hash: note.hash(), + data: (), + }; + tree.insert(*note.pos(), item); + + note +} + +fn create_test_tx_input_notes( + tree: &mut Tree<(), HEIGHT, ARITY>, + sk: &SecretKey, + skeleton_hash: BlsScalar, + rng: &mut (impl RngCore + CryptoRng), +) -> [TxInputNote; I] { + let pk = PublicKey::from(sk); + + let mut notes = Vec::new(); + for i in 0..I { + notes.push(create_and_insert_test_note( + tree, + &pk, + i.try_into().unwrap(), + 25, + rng, + )); + } + + let mut input_notes = Vec::new(); + for i in 0..I { + let merkle_opening = tree.opening(*notes[i].pos()).expect("Tree read."); + let input_note = TxInputNote::new( + ¬es[i], + merkle_opening, + &sk, + skeleton_hash, + rng, + ) + .expect("Note created properly."); + + input_notes.push(input_note); + } + + input_notes.try_into().unwrap() +} + +fn create_test_tx_output_note( + sk: &SecretKey, + value: u64, + rng: &mut (impl RngCore + CryptoRng), +) -> TxOutputNote { + let note = Note::transparent(rng, &PublicKey::from(sk), value); + TxOutputNote::new(¬e, &ViewKey::from(&TP.sk)) + .expect("Note created properly.") +} + +#[test] +fn test_transfer_circuit_1_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); + + let input_notes = [TP.tx_input_notes[0].clone()]; + + // create 2 testing tx output notes + let tx_output_notes = [ + create_test_tx_output_note(&TP.sk, 10, &mut OsRng), + create_test_tx_output_note(&TP.sk, 5, &mut OsRng), + ]; + + let (proof, public_inputs) = prover + .prove( + &mut OsRng, + &TxCircuit::new( + input_notes, + tx_output_notes, + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, + ), + ) + .expect("failed to prove"); + + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof"); +} + +#[test] +fn test_transfer_circuit_2_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); + + let input_notes = + [TP.tx_input_notes[0].clone(), TP.tx_input_notes[1].clone()]; + + // create 2 testing tx output notes + let tx_output_notes = [ + create_test_tx_output_note(&TP.sk, 35, &mut OsRng), + create_test_tx_output_note(&TP.sk, 5, &mut OsRng), + ]; + + let (proof, public_inputs) = prover + .prove( + &mut OsRng, + &TxCircuit::new( + input_notes, + tx_output_notes, + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, + ), + ) + .expect("failed to prove"); + + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof"); +} + +#[test] +fn test_transfer_circuit_3_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); + + let input_notes = [ + TP.tx_input_notes[0].clone(), + TP.tx_input_notes[1].clone(), + TP.tx_input_notes[2].clone(), + ]; + + // create 2 testing tx output notes + let tx_output_notes = [ + create_test_tx_output_note(&TP.sk, 35, &mut OsRng), + create_test_tx_output_note(&TP.sk, 30, &mut OsRng), + ]; + + let (proof, public_inputs) = prover + .prove( + &mut OsRng, + &TxCircuit::new( + input_notes, + tx_output_notes, + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, + ), + ) + .expect("failed to prove"); + + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof"); +} + +#[test] +fn test_transfer_circuit_4_2() { + let (prover, verifier) = + Compiler::compile::>(&TP.pp, LABEL) + .expect("failed to compile circuit"); + + // create 2 testing tx output notes + let tx_output_notes = [ + create_test_tx_output_note(&TP.sk, 60, &mut OsRng), + create_test_tx_output_note(&TP.sk, 30, &mut OsRng), + ]; + + let (proof, public_inputs) = prover + .prove( + &mut OsRng, + &TxCircuit::new( + TP.tx_input_notes.clone(), + tx_output_notes, + TP.skeleton_hash, + TP.root, + TP.crossover, + TP.max_fee, + ), + ) + .expect("failed to prove"); + + verifier + .verify(&proof, &public_inputs) + .expect("failed to verify proof"); +} diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 6ea78db..4d59cd9 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -12,11 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `empty` method for the `Note` [#165] - Add `From` trait implementation for `Error` [#166] - Add `ElGamal` encryption module [#162] +- Add impl `Ownable` for `&Note`. ### Changed - Restructure `Encryption` module. - Move phoenix-core into a phoenix workspace [#171] +- Rename `note` method to `note_type`. ### Removed diff --git a/core/src/note.rs b/core/src/note.rs index 4d74604..12aeb0d 100644 --- a/core/src/note.rs +++ b/core/src/note.rs @@ -264,7 +264,7 @@ impl Note { } /// Return the type of the note - pub const fn note(&self) -> NoteType { + pub const fn note_type(&self) -> NoteType { self.note_type } @@ -331,6 +331,12 @@ impl Ownable for Note { } } +impl Ownable for &Note { + fn stealth_address(&self) -> &StealthAddress { + &self.stealth_address + } +} + // Serialize into 105 + ENCRYPTION_SIZE bytes, where 105 is the size of all the // note elements without the encryption. ENCRYPTION_SIZE = PLAINTEXT_SIZE + // ENCRYPTION_EXTRA_SIZE diff --git a/core/tests/note_test.rs b/core/tests/note_test.rs index 856d4e9..b8b7814 100644 --- a/core/tests/note_test.rs +++ b/core/tests/note_test.rs @@ -23,7 +23,7 @@ fn transparent_note() -> Result<(), Error> { let note = Note::transparent(&mut rng, &pk, value); - assert_eq!(note.note(), NoteType::Transparent); + assert_eq!(note.note_type(), NoteType::Transparent); assert_eq!(value, note.value(None)?); Ok(()) @@ -43,7 +43,7 @@ fn transparent_stealth_note() -> Result<(), Error> { let note = Note::transparent_stealth(sa, value); - assert_eq!(note.note(), NoteType::Transparent); + assert_eq!(note.note_type(), NoteType::Transparent); assert_eq!(value, note.value(None)?); assert_eq!(sa, *note.stealth_address()); @@ -62,7 +62,7 @@ fn obfuscated_note() -> Result<(), Error> { let blinding_factor = JubJubScalar::random(&mut rng); let note = Note::obfuscated(&mut rng, &pk, value, blinding_factor); - assert_eq!(note.note(), NoteType::Obfuscated); + assert_eq!(note.note_type(), NoteType::Obfuscated); assert_eq!(value, note.value(Some(&vk))?); Ok(())