Skip to content

Commit

Permalink
circuits: Implement serialization
Browse files Browse the repository at this point in the history
Resolves #232
  • Loading branch information
moCello committed Aug 8, 2024
1 parent 63a9208 commit cac7086
Show file tree
Hide file tree
Showing 3 changed files with 435 additions and 13 deletions.
7 changes: 6 additions & 1 deletion circuits/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add `dusk-bytes` dependency at v0.1 [#232]
- Add `TxCircuit::from_slice` constructor [#232]
- Add `TxCircuit::from_slice` and `TxCircuit::to_var_bytes` [#232]
- Add `InputNoteInfo::from_slice` and `InputNoteInfo::to_var_bytes` [#232]
- Add `Serializable` trait implementation for `OutputNoteInfo` [#232]
- Add `Clone` and `PartialEq` derives for `TxCircuit` [#232]
- Add `PartialEq` derive for `InputNoteInfo` [#232]
- Add `PartialEq` derive for `OutputNoteInfo` [#232]

## [0.2.1] - 2024-07-03

Expand Down
233 changes: 221 additions & 12 deletions circuits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@ mod sender_enc;
/// ElGamal asymmetric cipher
pub use encryption::elgamal;

use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable};
use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR, GENERATOR_NUMS};
use dusk_plonk::prelude::*;
use dusk_poseidon::{Domain, HashGadget};
use jubjub_schnorr::{gadgets, Signature as SchnorrSignature, SignatureDouble};
use poseidon_merkle::{zk::opening_gadget, Item, Opening, Tree};
use poseidon_merkle::{zk::opening_gadget, Item, Opening, Tree, ARITY};

use phoenix_core::{Note, PublicKey, SecretKey, OUTPUT_NOTES};

extern crate alloc;
use alloc::vec::Vec;

/// Declaration of the transaction circuit calling the [`gadget`].
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq)]
pub struct TxCircuit<const H: usize, const I: usize> {
/// All information needed in relation to the transaction input-notes
pub input_notes_info: [InputNoteInfo<H>; I],
Expand Down Expand Up @@ -238,16 +239,83 @@ impl<const H: usize, const I: usize> Circuit for TxCircuit<H, I> {
}

impl<const H: usize, const I: usize> TxCircuit<H, I> {
const SIZE: usize = I * InputNoteInfo::<H>::SIZE
+ OUTPUT_NOTES * OutputNoteInfo::SIZE
+ 2 * BlsScalar::SIZE
+ 2 * u64::SIZE
+ PublicKey::SIZE
+ 2 * SchnorrSignature::SIZE;

/// Serialize a [`TxCircuit`] to a vector of bytes.
// Once the new implementation of the `Serializable` trait becomes
// available, we will want that instead, but for the time being we use
// this implementation.
pub fn to_var_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(Self::SIZE);

for info in self.input_notes_info.iter() {
bytes.extend(info.to_var_bytes());
}
for info in self.output_notes_info.iter() {
bytes.extend(info.to_bytes());
}
bytes.extend(self.payload_hash.to_bytes());
bytes.extend(self.root.to_bytes());
bytes.extend(self.deposit.to_bytes());
bytes.extend(self.max_fee.to_bytes());
bytes.extend(self.sender_pk.to_bytes());
bytes.extend(self.signatures.0.to_bytes());
bytes.extend(self.signatures.1.to_bytes());

bytes
}

/// Deserialize a [`TxCircuit`] from a slice of bytes.
///
/// # Errors
///
/// Will return [`dusk_bytes::Error`] in case of a deserialization error.
// Once the new implementation of the `Serializable` trait becomes
// available, we will want that instead, but for the time being we use
// this implementation.
pub fn from_slice(bytes: &[u8]) -> Result<Self, BytesError> {
if bytes.len() < Self::SIZE {
return Err(BytesError::BadLength {
found: bytes.len(),
expected: Self::SIZE,
});
}

let mut input_notes_info = Vec::new();
for _ in 0..I {
input_notes_info.push(InputNoteInfo::from_slice(bytes)?);
}

let mut reader = &bytes[I * InputNoteInfo::<H>::SIZE..];

let output_notes_info = [
OutputNoteInfo::from_reader(&mut reader)?,
OutputNoteInfo::from_reader(&mut reader)?,
];
let payload_hash = BlsScalar::from_reader(&mut reader)?;
let root = BlsScalar::from_reader(&mut reader)?;
let deposit = u64::from_reader(&mut reader)?;
let max_fee = u64::from_reader(&mut reader)?;
let sender_pk = PublicKey::from_reader(&mut reader)?;
let signature_0 = SchnorrSignature::from_reader(&mut reader)?;
let signature_1 = SchnorrSignature::from_reader(&mut reader)?;

Ok(Self {
input_notes_info, // [InputNoteInfo<H>; I],
output_notes_info, // [OutputNoteInfo; OUTPUT_NOTES],
payload_hash, // BlsScalar,
root, // BlsScalar,
deposit, // u64,
max_fee, // u64,
sender_pk, // PublicKey,
signatures, // (SchnorrSignature, SchnorrSignature),
input_notes_info: input_notes_info
.try_into()
.expect("The vector has exactly I elements"),
output_notes_info,
payload_hash,
root,
deposit,
max_fee,
sender_pk,
signatures: (signature_0, signature_1),
})
}
}
Expand Down Expand Up @@ -315,7 +383,7 @@ impl<const H: usize, const I: usize> Default for TxCircuit<H, I> {

/// Struct holding all information needed by the transfer circuit regarding the
/// transaction input-notes.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct InputNoteInfo<const H: usize> {
/// The merkle opening for the note
pub merkle_opening: Opening<(), H>,
Expand Down Expand Up @@ -380,11 +448,78 @@ impl<const H: usize> InputNoteInfo<H> {
signature_r_p,
)
}

const SIZE: usize = (1 + H * ARITY) * Item::SIZE
+ H * (u32::BITS as usize / 8)
+ Note::SIZE
+ JubJubAffine::SIZE
+ u64::SIZE
+ JubJubScalar::SIZE
+ BlsScalar::SIZE
+ SignatureDouble::SIZE;

/// Serialize an [`InputNoteInfo`] to a vector of bytes.
// Once the new implementation of the `Serializable` trait becomes
// available, we will want that instead, but for the time being we use
// this implementation.
pub fn to_var_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(Self::SIZE);

bytes.extend(self.merkle_opening.to_var_bytes());
bytes.extend(self.note.to_bytes());
bytes.extend(self.note_pk_p.to_bytes());
bytes.extend(self.value.to_bytes());
bytes.extend(self.value_blinder.to_bytes());
bytes.extend(self.nullifier.to_bytes());
bytes.extend(self.signature.to_bytes());

bytes
}

/// Deserialize an [`InputNoteInfo`] from a slice of bytes.
///
/// # Errors
///
/// Will return [`dusk_bytes::Error`] in case of a deserialization error.
// Once the new implementation of the `Serializable` trait becomes
// available, we will want that instead, but for the time being we use
// this implementation.
pub fn from_slice(bytes: &[u8]) -> Result<Self, BytesError> {
if bytes.len() < Self::SIZE {
return Err(BytesError::BadLength {
found: bytes.len(),
expected: Self::SIZE,
});
}

let merkle_opening_size =
(1 + H * ARITY) * Item::SIZE + H * (u32::BITS as usize / 8);
let merkle_opening =
Opening::<(), H>::from_slice(&bytes[..merkle_opening_size])?;

let mut buf = &bytes[merkle_opening_size..];
let note = Note::from_reader(&mut buf)?;
let note_pk_p = JubJubAffine::from_reader(&mut buf)?;
let value = u64::from_reader(&mut buf)?;
let value_blinder = JubJubScalar::from_reader(&mut buf)?;
let nullifier = BlsScalar::from_reader(&mut buf)?;
let signature = SignatureDouble::from_reader(&mut buf)?;

Ok(Self {
merkle_opening,
note,
note_pk_p,
value,
value_blinder,
nullifier,
signature,
})
}
}

/// Struct holding all information needed by the transfer circuit regarding the
/// transaction output-notes.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct OutputNoteInfo {
/// The value of the note
pub value: u64,
Expand All @@ -399,3 +534,77 @@ pub struct OutputNoteInfo {
/// The blinder used to encrypt the sender
pub sender_blinder: [JubJubScalar; 2],
}

const OUTPUT_NOTE_INFO_SIZE: usize = u64::SIZE
+ JubJubAffine::SIZE
+ JubJubScalar::SIZE
+ JubJubAffine::SIZE
+ 4 * JubJubAffine::SIZE
+ 2 * JubJubScalar::SIZE;

impl Serializable<OUTPUT_NOTE_INFO_SIZE> for OutputNoteInfo {
type Error = BytesError;

fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut bytes = [0u8; Self::SIZE];
let mut offset = 0;

bytes[..u64::SIZE].copy_from_slice(&self.value.to_bytes());
offset += u64::SIZE;
bytes[offset..offset + JubJubAffine::SIZE]
.copy_from_slice(&self.value_commitment.to_bytes());
offset += JubJubAffine::SIZE;
bytes[offset..offset + JubJubScalar::SIZE]
.copy_from_slice(&self.value_blinder.to_bytes());
offset += JubJubScalar::SIZE;
bytes[offset..offset + JubJubAffine::SIZE]
.copy_from_slice(&self.note_pk.to_bytes());
offset += JubJubAffine::SIZE;
bytes[offset..offset + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[0].0.to_bytes());
offset += JubJubAffine::SIZE;
bytes[offset..offset + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[0].1.to_bytes());
offset += JubJubAffine::SIZE;
bytes[offset..offset + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[1].0.to_bytes());
offset += JubJubAffine::SIZE;
bytes[offset..offset + JubJubAffine::SIZE]
.copy_from_slice(&self.sender_enc[1].1.to_bytes());
offset += JubJubAffine::SIZE;
bytes[offset..offset + JubJubScalar::SIZE]
.copy_from_slice(&self.sender_blinder[0].to_bytes());
offset += JubJubScalar::SIZE;
bytes[offset..offset + JubJubScalar::SIZE]
.copy_from_slice(&self.sender_blinder[1].to_bytes());

bytes
}

fn from_bytes(bytes: &[u8; Self::SIZE]) -> Result<Self, Self::Error> {
let mut reader = &bytes[..];

let value = u64::from_reader(&mut reader)?;
let value_commitment = JubJubAffine::from_reader(&mut reader)?;
let value_blinder = JubJubScalar::from_reader(&mut reader)?;
let note_pk = JubJubAffine::from_reader(&mut reader)?;
let sender_enc_0_0 = JubJubAffine::from_reader(&mut reader)?;
let sender_enc_0_1 = JubJubAffine::from_reader(&mut reader)?;
let sender_enc_1_0 = JubJubAffine::from_reader(&mut reader)?;
let sender_enc_1_1 = JubJubAffine::from_reader(&mut reader)?;
let sender_blinder_0 = JubJubScalar::from_reader(&mut reader)?;
let sender_blinder_1 = JubJubScalar::from_reader(&mut reader)?;

Ok(Self {
value,
value_commitment,
value_blinder,
note_pk,
sender_enc: [
(sender_enc_0_0, sender_enc_0_1),
(sender_enc_1_0, sender_enc_1_1),
],
sender_blinder: [sender_blinder_0, sender_blinder_1],
})
}
}
Loading

0 comments on commit cac7086

Please sign in to comment.