diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index e8035e4..e383060 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Implement `rkyv` traits for `RecipientParameters` +- Implement `dusk_bytes::Serializable` for `RecipientParameters` - Add `RecipientParameters` - Add `elgamal::encrypt` and `elgamal::decrypt` - Add `stealth_address` and `sync_address` functions directly to note [#208] @@ -18,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Include `recipient_params` field in `TxSkeleton` - Move `OUTPUT_NOTES` to crate root - Change `owns` and `owns_unchecked` to take `&Note` [#208] - Change `gen_note_sk` to take `&StealthAddress` [#208] diff --git a/core/src/recipient.rs b/core/src/recipient.rs index 376930c..a433a7d 100644 --- a/core/src/recipient.rs +++ b/core/src/recipient.rs @@ -7,15 +7,24 @@ #![allow(non_snake_case)] use dusk_bls12_381::BlsScalar; +use dusk_bytes::{DeserializableSlice, Serializable}; use dusk_jubjub::{JubJubAffine, JubJubExtended, JubJubScalar}; use ff::Field; use jubjub_schnorr::{SecretKey as SchnorrSecretKey, Signature}; use rand::{CryptoRng, RngCore}; +#[cfg(feature = "rkyv-impl")] +use rkyv::{Archive, Deserialize, Serialize}; + use crate::{encryption::elgamal, PublicKey, SecretKey, OUTPUT_NOTES}; /// Parameters needed to prove a recipient in-circuit -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr( + feature = "rkyv-impl", + derive(Archive, Serialize, Deserialize), + archive_attr(derive(bytecheck::CheckBytes)) +)] pub struct RecipientParameters { /// Public key of the transaction sender pub sender_pk: PublicKey, @@ -53,6 +62,105 @@ impl Default for RecipientParameters { } } +const PARAMS_SIZE: usize = PublicKey::SIZE + + JubJubAffine::SIZE * OUTPUT_NOTES + + Signature::SIZE * OUTPUT_NOTES + + JubJubAffine::SIZE * 2 * OUTPUT_NOTES + + JubJubAffine::SIZE * 2 * OUTPUT_NOTES + + JubJubScalar::SIZE * OUTPUT_NOTES + + JubJubScalar::SIZE * OUTPUT_NOTES; + +impl Serializable for RecipientParameters { + type Error = dusk_bytes::Error; + + fn from_bytes(buf: &[u8; PARAMS_SIZE]) -> Result + where + Self: Sized, + { + let mut reader = &buf[..]; + + let sender_pk = PublicKey::from_reader(&mut reader)?; + + let output_npk_0 = JubJubAffine::from_reader(&mut reader)?; + let output_npk_1 = JubJubAffine::from_reader(&mut reader)?; + + let sig_0 = Signature::from_reader(&mut reader)?; + let sig_1 = Signature::from_reader(&mut reader)?; + + let enc_A_0_0 = JubJubAffine::from_reader(&mut reader)?; + let enc_A_1_0 = JubJubAffine::from_reader(&mut reader)?; + let enc_A_0_1 = JubJubAffine::from_reader(&mut reader)?; + let enc_A_1_1 = JubJubAffine::from_reader(&mut reader)?; + + let enc_B_0_0 = JubJubAffine::from_reader(&mut reader)?; + let enc_B_1_0 = JubJubAffine::from_reader(&mut reader)?; + let enc_B_0_1 = JubJubAffine::from_reader(&mut reader)?; + let enc_B_1_1 = JubJubAffine::from_reader(&mut reader)?; + + let r_A_0 = JubJubScalar::from_reader(&mut reader)?; + let r_A_1 = JubJubScalar::from_reader(&mut reader)?; + + let r_B_0 = JubJubScalar::from_reader(&mut reader)?; + let r_B_1 = JubJubScalar::from_reader(&mut reader)?; + + Ok(Self { + sender_pk, + output_npk: [output_npk_0, output_npk_1], + sig: [sig_0, sig_1], + enc_A: [ + (enc_A_0_0.into(), enc_A_1_0.into()), + (enc_A_0_1.into(), enc_A_1_1.into()), + ], + enc_B: [ + (enc_B_0_0.into(), enc_B_1_0.into()), + (enc_B_0_1.into(), enc_B_1_1.into()), + ], + r_A: [r_A_0, r_A_1], + r_B: [r_B_0, r_B_1], + }) + } + + fn to_bytes(&self) -> [u8; PARAMS_SIZE] { + let mut bytes = [0u8; PARAMS_SIZE]; + + bytes[0..64].copy_from_slice(&self.sender_pk.to_bytes()); + + bytes[64..96].copy_from_slice(&self.output_npk[0].to_bytes()); + bytes[96..128].copy_from_slice(&self.output_npk[1].to_bytes()); + + bytes[128..192].copy_from_slice(&self.sig[0].to_bytes()); + bytes[192..256].copy_from_slice(&self.sig[1].to_bytes()); + + let enc_A_0_0 = JubJubAffine::from(self.enc_A[0].0); + let enc_A_1_0 = JubJubAffine::from(self.enc_A[0].1); + let enc_A_0_1 = JubJubAffine::from(self.enc_A[1].0); + let enc_A_1_1 = JubJubAffine::from(self.enc_A[1].1); + + bytes[256..288].copy_from_slice(&enc_A_0_0.to_bytes()); + bytes[288..320].copy_from_slice(&enc_A_1_0.to_bytes()); + bytes[320..352].copy_from_slice(&enc_A_0_1.to_bytes()); + bytes[352..384].copy_from_slice(&enc_A_1_1.to_bytes()); + + let enc_B_0_0 = JubJubAffine::from(self.enc_B[0].0); + let enc_B_1_0 = JubJubAffine::from(self.enc_B[0].1); + let enc_B_0_1 = JubJubAffine::from(self.enc_B[1].0); + let enc_B_1_1 = JubJubAffine::from(self.enc_B[1].1); + + bytes[384..416].copy_from_slice(&enc_B_0_0.to_bytes()); + bytes[416..448].copy_from_slice(&enc_B_1_0.to_bytes()); + bytes[448..480].copy_from_slice(&enc_B_0_1.to_bytes()); + bytes[480..512].copy_from_slice(&enc_B_1_1.to_bytes()); + + bytes[512..544].copy_from_slice(&self.r_A[0].to_bytes()); + bytes[544..576].copy_from_slice(&self.r_A[1].to_bytes()); + + bytes[576..608].copy_from_slice(&self.r_B[0].to_bytes()); + bytes[608..640].copy_from_slice(&self.r_B[1].to_bytes()); + + bytes + } +} + impl RecipientParameters { /// Create the recipient parameter pub fn new( diff --git a/core/src/transaction.rs b/core/src/transaction.rs index 5978fce..b4e99f6 100644 --- a/core/src/transaction.rs +++ b/core/src/transaction.rs @@ -16,10 +16,10 @@ use rkyv::{Archive, Deserialize, Serialize}; use dusk_bls12_381::BlsScalar; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; -use crate::{Note, OUTPUT_NOTES}; +use crate::{Note, RecipientParameters, OUTPUT_NOTES}; /// A phoenix transaction, referred to as tx-skeleton in the specs. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr( feature = "rkyv-impl", derive(Archive, Serialize, Deserialize), @@ -37,6 +37,8 @@ pub struct TxSkeleton { pub tx_max_fee: u64, /// A deposit is used to transferring funds to a contract pub deposit: u64, + /// Parameters needed to prove a recipient in-circuit + pub recipient_params: RecipientParameters, } impl TxSkeleton { @@ -56,6 +58,7 @@ impl TxSkeleton { bytes.extend(self.tx_max_fee.to_bytes()); bytes.extend(self.deposit.to_bytes()); + bytes.extend(self.recipient_params.to_bytes()); bytes } @@ -80,6 +83,7 @@ impl TxSkeleton { bytes.extend(self.tx_max_fee.to_bytes()); bytes.extend(self.deposit.to_bytes()); + bytes.extend(self.recipient_params.to_bytes()); bytes } @@ -104,6 +108,7 @@ impl TxSkeleton { let tx_max_fee = u64::from_reader(&mut buffer)?; let deposit = u64::from_reader(&mut buffer)?; + let recipient_params = RecipientParameters::from_reader(&mut buffer)?; Ok(Self { root, @@ -111,6 +116,7 @@ impl TxSkeleton { outputs, tx_max_fee, deposit, + recipient_params, }) } diff --git a/core/tests/transaction.rs b/core/tests/transaction.rs index 6f1695a..76e6f18 100644 --- a/core/tests/transaction.rs +++ b/core/tests/transaction.rs @@ -7,9 +7,11 @@ #![cfg(feature = "alloc")] use dusk_bls12_381::BlsScalar; -use dusk_jubjub::JubJubScalar; +use dusk_jubjub::{JubJubScalar, GENERATOR}; use ff::Field; -use phoenix_core::{Error, Note, PublicKey, SecretKey, TxSkeleton}; +use phoenix_core::{ + Error, Note, PublicKey, RecipientParameters, SecretKey, TxSkeleton, +}; use rand::rngs::OsRng; #[test] @@ -29,12 +31,22 @@ fn transaction_parse() -> Result<(), Error> { let tx_max_fee = 0; let deposit = 0; + let output_npks = [ + (GENERATOR * JubJubScalar::random(&mut rng)).into(), + (GENERATOR * JubJubScalar::random(&mut rng)).into(), + ]; + let hash = BlsScalar::random(&mut rng); + + let recipient_params = + RecipientParameters::new(&mut rng, &sk, output_npks, hash); + let tx_skeleton = TxSkeleton { root, nullifiers, outputs, tx_max_fee, deposit, + recipient_params, }; let bytes_of_transaction = tx_skeleton.to_var_bytes(); assert_eq!(