[zk-token-sdk] replace hard-coded constants with constant variables (#32274)

* add ristretto and scalar byte length constants

* add serialization and deserialization helper functions

* remove hard-coded constants in the `sigma` module

* remove hard-coded constants in the `encryption` module

* remove hard-coded constants in the `zk-token-elgamal` module

* Apply suggestions from code review

Co-authored-by: Tyera <tyera@solana.com>

* fix docs for range proof constants

* Apply suggestions from code review

Co-authored-by: Tyera <tyera@solana.com>

* clippy

---------

Co-authored-by: Tyera <tyera@solana.com>
This commit is contained in:
samkim-crypto 2023-06-29 07:03:26 +09:00 committed by GitHub
parent 5dee2e4d0c
commit 91186d3860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 422 additions and 227 deletions

View File

@ -31,6 +31,19 @@ use {
zeroize::Zeroize, zeroize::Zeroize,
}; };
/// Byte length of an authenticated encryption secret key
const AE_KEY_LEN: usize = 16;
/// Byte length of an authenticated encryption nonce component
const NONCE_LEN: usize = 12;
/// Byte lenth of an authenticated encryption ciphertext component
const CIPHERTEXT_LEN: usize = 24;
/// Byte length of a complete authenticated encryption ciphertext component that includes the
/// ciphertext and nonce components
const AE_CIPHERTEXT_LEN: usize = 36;
#[derive(Error, Clone, Debug, Eq, PartialEq)] #[derive(Error, Clone, Debug, Eq, PartialEq)]
pub enum AuthenticatedEncryptionError { pub enum AuthenticatedEncryptionError {
#[error("key derivation method not supported")] #[error("key derivation method not supported")]
@ -46,7 +59,7 @@ impl AuthenticatedEncryption {
/// This function is randomized. It internally samples a 128-bit key using `OsRng`. /// This function is randomized. It internally samples a 128-bit key using `OsRng`.
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
fn keygen() -> AeKey { fn keygen() -> AeKey {
AeKey(OsRng.gen::<[u8; 16]>()) AeKey(OsRng.gen::<[u8; AE_KEY_LEN]>())
} }
/// On input of an authenticated encryption key and an amount, the function returns a /// On input of an authenticated encryption key and an amount, the function returns a
@ -54,7 +67,7 @@ impl AuthenticatedEncryption {
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
fn encrypt(key: &AeKey, balance: u64) -> AeCiphertext { fn encrypt(key: &AeKey, balance: u64) -> AeCiphertext {
let mut plaintext = balance.to_le_bytes(); let mut plaintext = balance.to_le_bytes();
let nonce: Nonce = OsRng.gen::<[u8; 12]>(); let nonce: Nonce = OsRng.gen::<[u8; NONCE_LEN]>();
// The balance and the nonce have fixed length and therefore, encryption should not fail. // The balance and the nonce have fixed length and therefore, encryption should not fail.
let ciphertext = Aes128GcmSiv::new(&key.0.into()) let ciphertext = Aes128GcmSiv::new(&key.0.into())
@ -86,7 +99,7 @@ impl AuthenticatedEncryption {
} }
#[derive(Debug, Zeroize)] #[derive(Debug, Zeroize)]
pub struct AeKey([u8; 16]); pub struct AeKey([u8; AE_KEY_LEN]);
impl AeKey { impl AeKey {
/// Deterministically derives an authenticated encryption key from a Solana signer and a public /// Deterministically derives an authenticated encryption key from a Solana signer and a public
/// seed. /// seed.
@ -144,7 +157,7 @@ impl AeKey {
impl EncodableKey for AeKey { impl EncodableKey for AeKey {
fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> { fn read<R: Read>(reader: &mut R) -> Result<Self, Box<dyn error::Error>> {
let bytes: [u8; 16] = serde_json::from_reader(reader)?; let bytes: [u8; AE_KEY_LEN] = serde_json::from_reader(reader)?;
Ok(Self(bytes)) Ok(Self(bytes))
} }
@ -158,7 +171,7 @@ impl EncodableKey for AeKey {
impl SeedDerivable for AeKey { impl SeedDerivable for AeKey {
fn from_seed(seed: &[u8]) -> Result<Self, Box<dyn error::Error>> { fn from_seed(seed: &[u8]) -> Result<Self, Box<dyn error::Error>> {
const MINIMUM_SEED_LEN: usize = 16; const MINIMUM_SEED_LEN: usize = AE_KEY_LEN;
if seed.len() < MINIMUM_SEED_LEN { if seed.len() < MINIMUM_SEED_LEN {
return Err(AuthenticatedEncryptionError::SeedLengthTooShort.into()); return Err(AuthenticatedEncryptionError::SeedLengthTooShort.into());
@ -168,7 +181,7 @@ impl SeedDerivable for AeKey {
hasher.update(seed); hasher.update(seed);
let result = hasher.finalize(); let result = hasher.finalize();
Ok(Self(result[..16].try_into()?)) Ok(Self(result[..AE_KEY_LEN].try_into()?))
} }
fn from_seed_and_derivation_path( fn from_seed_and_derivation_path(
@ -191,8 +204,8 @@ impl SeedDerivable for AeKey {
/// For the purpose of encrypting balances for the spl token accounts, the nonce and ciphertext /// For the purpose of encrypting balances for the spl token accounts, the nonce and ciphertext
/// sizes should always be fixed. /// sizes should always be fixed.
type Nonce = [u8; 12]; type Nonce = [u8; NONCE_LEN];
type Ciphertext = [u8; 24]; type Ciphertext = [u8; CIPHERTEXT_LEN];
/// Authenticated encryption nonce and ciphertext /// Authenticated encryption nonce and ciphertext
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@ -205,20 +218,20 @@ impl AeCiphertext {
AuthenticatedEncryption::decrypt(key, self) AuthenticatedEncryption::decrypt(key, self)
} }
pub fn to_bytes(&self) -> [u8; 36] { pub fn to_bytes(&self) -> [u8; AE_CIPHERTEXT_LEN] {
let mut buf = [0_u8; 36]; let mut buf = [0_u8; AE_CIPHERTEXT_LEN];
buf[..12].copy_from_slice(&self.nonce); buf[..NONCE_LEN].copy_from_slice(&self.nonce);
buf[12..].copy_from_slice(&self.ciphertext); buf[NONCE_LEN..].copy_from_slice(&self.ciphertext);
buf buf
} }
pub fn from_bytes(bytes: &[u8]) -> Option<AeCiphertext> { pub fn from_bytes(bytes: &[u8]) -> Option<AeCiphertext> {
if bytes.len() != 36 { if bytes.len() != AE_CIPHERTEXT_LEN {
return None; return None;
} }
let nonce = bytes[..32].try_into().ok()?; let nonce = bytes[..NONCE_LEN].try_into().ok()?;
let ciphertext = bytes[32..].try_into().ok()?; let ciphertext = bytes[NONCE_LEN..].try_into().ok()?;
Some(AeCiphertext { nonce, ciphertext }) Some(AeCiphertext { nonce, ciphertext })
} }

View File

@ -17,6 +17,7 @@
#![cfg(not(target_os = "solana"))] #![cfg(not(target_os = "solana"))]
use { use {
crate::RISTRETTO_POINT_LEN,
curve25519_dalek::{ curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT as G, constants::RISTRETTO_BASEPOINT_POINT as G,
ristretto::RistrettoPoint, ristretto::RistrettoPoint,
@ -32,6 +33,9 @@ use {
const TWO16: u64 = 65536; // 2^16 const TWO16: u64 = 65536; // 2^16
const TWO17: u64 = 131072; // 2^17 const TWO17: u64 = 131072; // 2^17
/// Maximum number of threads permitted for discrete log computation
const MAX_THREAD: usize = 65536;
#[derive(Error, Clone, Debug, Eq, PartialEq)] #[derive(Error, Clone, Debug, Eq, PartialEq)]
pub enum DiscreteLogError { pub enum DiscreteLogError {
#[error("discrete log number of threads not power-of-two")] #[error("discrete log number of threads not power-of-two")]
@ -61,7 +65,7 @@ pub struct DiscreteLog {
} }
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize, Default)]
pub struct DecodePrecomputation(HashMap<[u8; 32], u16>); pub struct DecodePrecomputation(HashMap<[u8; RISTRETTO_POINT_LEN], u16>);
/// Builds a HashMap of 2^16 elements /// Builds a HashMap of 2^16 elements
#[allow(dead_code)] #[allow(dead_code)]
@ -110,7 +114,7 @@ impl DiscreteLog {
/// Adjusts number of threads in a discrete log instance. /// Adjusts number of threads in a discrete log instance.
pub fn num_threads(&mut self, num_threads: usize) -> Result<(), DiscreteLogError> { pub fn num_threads(&mut self, num_threads: usize) -> Result<(), DiscreteLogError> {
// number of threads must be a positive power-of-two integer // number of threads must be a positive power-of-two integer
if num_threads == 0 || (num_threads & (num_threads - 1)) != 0 || num_threads > 65536 { if num_threads == 0 || (num_threads & (num_threads - 1)) != 0 || num_threads > MAX_THREAD {
return Err(DiscreteLogError::DiscreteLogThreads); return Err(DiscreteLogError::DiscreteLogThreads);
} }

View File

@ -14,9 +14,14 @@
//! discrete log to recover the originally encrypted value. //! discrete log to recover the originally encrypted value.
use { use {
crate::encryption::{ crate::{
encryption::{
discrete_log::DiscreteLog, discrete_log::DiscreteLog,
pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G, H}, pedersen::{
Pedersen, PedersenCommitment, PedersenOpening, G, H, PEDERSEN_COMMITMENT_LEN,
},
},
RISTRETTO_POINT_LEN, SCALAR_LEN,
}, },
base64::{prelude::BASE64_STANDARD, Engine}, base64::{prelude::BASE64_STANDARD, Engine},
core::ops::{Add, Mul, Sub}, core::ops::{Add, Mul, Sub},
@ -50,6 +55,21 @@ use {
}, },
}; };
/// Byte length of a decrypt handle
const DECRYPT_HANDLE_LEN: usize = RISTRETTO_POINT_LEN;
/// Byte length of an ElGamal ciphertext
const ELGAMAL_CIPHERTEXT_LEN: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN;
/// Byte length of an ElGamal public key
const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN;
/// Byte length of an ElGamal secret key
const ELGAMAL_SECRET_KEY_LEN: usize = SCALAR_LEN;
/// Byte length of an ElGamal keypair
const ELGAMAL_KEYPAIR_LEN: usize = ELGAMAL_PUBKEY_LEN + ELGAMAL_SECRET_KEY_LEN;
#[derive(Error, Clone, Debug, Eq, PartialEq)] #[derive(Error, Clone, Debug, Eq, PartialEq)]
pub enum ElGamalError { pub enum ElGamalError {
#[error("key derivation method not supported")] #[error("key derivation method not supported")]
@ -209,21 +229,21 @@ impl ElGamalKeypair {
&self.secret &self.secret
} }
pub fn to_bytes(&self) -> [u8; 64] { pub fn to_bytes(&self) -> [u8; ELGAMAL_KEYPAIR_LEN] {
let mut bytes = [0u8; 64]; let mut bytes = [0u8; ELGAMAL_KEYPAIR_LEN];
bytes[..32].copy_from_slice(&self.public.to_bytes()); bytes[..ELGAMAL_PUBKEY_LEN].copy_from_slice(&self.public.to_bytes());
bytes[32..].copy_from_slice(self.secret.as_bytes()); bytes[ELGAMAL_PUBKEY_LEN..].copy_from_slice(self.secret.as_bytes());
bytes bytes
} }
pub fn from_bytes(bytes: &[u8]) -> Option<Self> { pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 64 { if bytes.len() != ELGAMAL_KEYPAIR_LEN {
return None; return None;
} }
Some(Self { Some(Self {
public: ElGamalPubkey::from_bytes(&bytes[..32])?, public: ElGamalPubkey::from_bytes(&bytes[..ELGAMAL_PUBKEY_LEN])?,
secret: ElGamalSecretKey::from_bytes(bytes[32..].try_into().ok()?)?, secret: ElGamalSecretKey::from_bytes(bytes[ELGAMAL_PUBKEY_LEN..].try_into().ok()?)?,
}) })
} }
@ -317,12 +337,12 @@ impl ElGamalPubkey {
&self.0 &self.0
} }
pub fn to_bytes(&self) -> [u8; 32] { pub fn to_bytes(&self) -> [u8; ELGAMAL_PUBKEY_LEN] {
self.0.compress().to_bytes() self.0.compress().to_bytes()
} }
pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalPubkey> { pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalPubkey> {
if bytes.len() != 32 { if bytes.len() != ELGAMAL_PUBKEY_LEN {
return None; return None;
} }
@ -428,7 +448,7 @@ impl ElGamalSecretKey {
/// Derive an ElGamal secret key from an entropy seed. /// Derive an ElGamal secret key from an entropy seed.
pub fn from_seed(seed: &[u8]) -> Result<Self, ElGamalError> { pub fn from_seed(seed: &[u8]) -> Result<Self, ElGamalError> {
const MINIMUM_SEED_LEN: usize = 32; const MINIMUM_SEED_LEN: usize = ELGAMAL_SECRET_KEY_LEN;
if seed.len() < MINIMUM_SEED_LEN { if seed.len() < MINIMUM_SEED_LEN {
return Err(ElGamalError::SeedLengthTooShort); return Err(ElGamalError::SeedLengthTooShort);
@ -453,11 +473,11 @@ impl ElGamalSecretKey {
ElGamal::decrypt_u32(self, ciphertext) ElGamal::decrypt_u32(self, ciphertext)
} }
pub fn as_bytes(&self) -> &[u8; 32] { pub fn as_bytes(&self) -> &[u8; ELGAMAL_SECRET_KEY_LEN] {
self.0.as_bytes() self.0.as_bytes()
} }
pub fn to_bytes(&self) -> [u8; 32] { pub fn to_bytes(&self) -> [u8; ELGAMAL_SECRET_KEY_LEN] {
self.0.to_bytes() self.0.to_bytes()
} }
@ -554,21 +574,21 @@ impl ElGamalCiphertext {
} }
} }
pub fn to_bytes(&self) -> [u8; 64] { pub fn to_bytes(&self) -> [u8; ELGAMAL_CIPHERTEXT_LEN] {
let mut bytes = [0u8; 64]; let mut bytes = [0u8; ELGAMAL_CIPHERTEXT_LEN];
bytes[..32].copy_from_slice(&self.commitment.to_bytes()); bytes[..PEDERSEN_COMMITMENT_LEN].copy_from_slice(&self.commitment.to_bytes());
bytes[32..].copy_from_slice(&self.handle.to_bytes()); bytes[PEDERSEN_COMMITMENT_LEN..].copy_from_slice(&self.handle.to_bytes());
bytes bytes
} }
pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalCiphertext> { pub fn from_bytes(bytes: &[u8]) -> Option<ElGamalCiphertext> {
if bytes.len() != 64 { if bytes.len() != ELGAMAL_CIPHERTEXT_LEN {
return None; return None;
} }
Some(ElGamalCiphertext { Some(ElGamalCiphertext {
commitment: PedersenCommitment::from_bytes(&bytes[..32])?, commitment: PedersenCommitment::from_bytes(&bytes[..PEDERSEN_COMMITMENT_LEN])?,
handle: DecryptHandle::from_bytes(&bytes[32..])?, handle: DecryptHandle::from_bytes(&bytes[PEDERSEN_COMMITMENT_LEN..])?,
}) })
} }
@ -676,12 +696,12 @@ impl DecryptHandle {
&self.0 &self.0
} }
pub fn to_bytes(&self) -> [u8; 32] { pub fn to_bytes(&self) -> [u8; DECRYPT_HANDLE_LEN] {
self.0.compress().to_bytes() self.0.compress().to_bytes()
} }
pub fn from_bytes(bytes: &[u8]) -> Option<DecryptHandle> { pub fn from_bytes(bytes: &[u8]) -> Option<DecryptHandle> {
if bytes.len() != 32 { if bytes.len() != DECRYPT_HANDLE_LEN {
return None; return None;
} }

View File

@ -12,11 +12,14 @@
//! //!
use { use {
crate::encryption::{ crate::{
encryption::{
discrete_log::DiscreteLog, discrete_log::DiscreteLog,
elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey}, elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey},
pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
}, },
RISTRETTO_POINT_LEN,
},
curve25519_dalek::scalar::Scalar, curve25519_dalek::scalar::Scalar,
thiserror::Error, thiserror::Error,
}; };
@ -163,7 +166,7 @@ impl<const N: usize> GroupedElGamalCiphertext<N> {
/// `(N+1) * 32`. /// `(N+1) * 32`.
fn expected_byte_length() -> usize { fn expected_byte_length() -> usize {
N.checked_add(1) N.checked_add(1)
.and_then(|length| length.checked_mul(32)) .and_then(|length| length.checked_mul(RISTRETTO_POINT_LEN))
.unwrap() .unwrap()
} }
@ -181,7 +184,7 @@ impl<const N: usize> GroupedElGamalCiphertext<N> {
return None; return None;
} }
let mut iter = bytes.chunks(32); let mut iter = bytes.chunks(RISTRETTO_POINT_LEN);
let commitment = PedersenCommitment::from_bytes(iter.next()?)?; let commitment = PedersenCommitment::from_bytes(iter.next()?)?;
let mut handles = Vec::with_capacity(N); let mut handles = Vec::with_capacity(N);

View File

@ -3,6 +3,7 @@
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
use rand::rngs::OsRng; use rand::rngs::OsRng;
use { use {
crate::{RISTRETTO_POINT_LEN, SCALAR_LEN},
core::ops::{Add, Mul, Sub}, core::ops::{Add, Mul, Sub},
curve25519_dalek::{ curve25519_dalek::{
constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT}, constants::{RISTRETTO_BASEPOINT_COMPRESSED, RISTRETTO_BASEPOINT_POINT},
@ -17,6 +18,12 @@ use {
zeroize::Zeroize, zeroize::Zeroize,
}; };
/// Byte length of a Pedersen opening.
const PEDERSEN_OPENING_LEN: usize = SCALAR_LEN;
/// Byte length of a Pedersen commitment.
pub(crate) const PEDERSEN_COMMITMENT_LEN: usize = RISTRETTO_POINT_LEN;
lazy_static::lazy_static! { lazy_static::lazy_static! {
/// Pedersen base point for encoding messages to be committed. /// Pedersen base point for encoding messages to be committed.
pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT; pub static ref G: RistrettoPoint = RISTRETTO_BASEPOINT_POINT;
@ -82,11 +89,11 @@ impl PedersenOpening {
PedersenOpening(Scalar::random(&mut OsRng)) PedersenOpening(Scalar::random(&mut OsRng))
} }
pub fn as_bytes(&self) -> &[u8; 32] { pub fn as_bytes(&self) -> &[u8; PEDERSEN_OPENING_LEN] {
self.0.as_bytes() self.0.as_bytes()
} }
pub fn to_bytes(&self) -> [u8; 32] { pub fn to_bytes(&self) -> [u8; PEDERSEN_OPENING_LEN] {
self.0.to_bytes() self.0.to_bytes()
} }
@ -177,12 +184,12 @@ impl PedersenCommitment {
&self.0 &self.0
} }
pub fn to_bytes(&self) -> [u8; 32] { pub fn to_bytes(&self) -> [u8; PEDERSEN_COMMITMENT_LEN] {
self.0.compress().to_bytes() self.0.compress().to_bytes()
} }
pub fn from_bytes(bytes: &[u8]) -> Option<PedersenCommitment> { pub fn from_bytes(bytes: &[u8]) -> Option<PedersenCommitment> {
if bytes.len() != 32 { if bytes.len() != PEDERSEN_COMMITMENT_LEN {
return None; return None;
} }

View File

@ -38,3 +38,10 @@ pub mod zk_token_elgamal;
pub mod zk_token_proof_instruction; pub mod zk_token_proof_instruction;
pub mod zk_token_proof_program; pub mod zk_token_proof_program;
pub mod zk_token_proof_state; pub mod zk_token_proof_state;
/// Byte length of a compressed Ristretto point or scalar in Curve255519
const UNIT_LEN: usize = 32;
/// Byte length of a compressed Ristretto point in Curve25519
const RISTRETTO_POINT_LEN: usize = UNIT_LEN;
/// Byte length of a scalar in Curve25519
const SCALAR_LEN: usize = UNIT_LEN;

View File

@ -11,7 +11,8 @@ use {
pedersen::{PedersenOpening, G, H}, pedersen::{PedersenOpening, G, H},
}, },
errors::ProofVerificationError, errors::ProofVerificationError,
sigma_proofs::canonical_scalar_from_slice, sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
UNIT_LEN,
}, },
curve25519_dalek::traits::MultiscalarMul, curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng, rand::rngs::OsRng,
@ -27,6 +28,9 @@ use {
merlin::Transcript, merlin::Transcript,
}; };
/// Byte length of a ciphertext-ciphertext equality proof.
const CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN: usize = UNIT_LEN * 7;
/// The ciphertext-ciphertext equality proof. /// The ciphertext-ciphertext equality proof.
/// ///
/// Contains all the elliptic curve and scalar components that make up the sigma protocol. /// Contains all the elliptic curve and scalar components that make up the sigma protocol.
@ -221,30 +225,31 @@ impl CiphertextCiphertextEqualityProof {
} }
} }
pub fn to_bytes(&self) -> [u8; 224] { pub fn to_bytes(&self) -> [u8; CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN] {
let mut buf = [0_u8; 224]; let mut buf = [0_u8; CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN];
buf[..32].copy_from_slice(self.Y_0.as_bytes()); let mut chunks = buf.chunks_mut(UNIT_LEN);
buf[32..64].copy_from_slice(self.Y_1.as_bytes());
buf[64..96].copy_from_slice(self.Y_2.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes());
buf[96..128].copy_from_slice(self.Y_3.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes());
buf[128..160].copy_from_slice(self.z_s.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes());
buf[160..192].copy_from_slice(self.z_x.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_3.as_bytes());
buf[192..224].copy_from_slice(self.z_r.as_bytes()); chunks.next().unwrap().copy_from_slice(self.z_s.as_bytes());
chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes());
chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes());
buf buf
} }
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> {
if bytes.len() != 224 { let mut chunks = bytes.chunks(UNIT_LEN);
return Err(ProofVerificationError::Deserialization.into());
}
let Y_0 = CompressedRistretto::from_slice(&bytes[..32]); let Y_0 = ristretto_point_from_optional_slice(chunks.next())?;
let Y_1 = CompressedRistretto::from_slice(&bytes[32..64]); let Y_1 = ristretto_point_from_optional_slice(chunks.next())?;
let Y_2 = CompressedRistretto::from_slice(&bytes[64..96]); let Y_2 = ristretto_point_from_optional_slice(chunks.next())?;
let Y_3 = CompressedRistretto::from_slice(&bytes[96..128]); let Y_3 = ristretto_point_from_optional_slice(chunks.next())?;
let z_s = canonical_scalar_from_slice(&bytes[128..160])?; let z_s = canonical_scalar_from_optional_slice(chunks.next())?;
let z_x = canonical_scalar_from_slice(&bytes[160..192])?; let z_x = canonical_scalar_from_optional_slice(chunks.next())?;
let z_r = canonical_scalar_from_slice(&bytes[192..224])?; let z_r = canonical_scalar_from_optional_slice(chunks.next())?;
Ok(CiphertextCiphertextEqualityProof { Ok(CiphertextCiphertextEqualityProof {
Y_0, Y_0,

View File

@ -16,7 +16,8 @@ use {
pedersen::{PedersenCommitment, PedersenOpening, G, H}, pedersen::{PedersenCommitment, PedersenOpening, G, H},
}, },
errors::ProofVerificationError, errors::ProofVerificationError,
sigma_proofs::canonical_scalar_from_slice, sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
UNIT_LEN,
}, },
curve25519_dalek::traits::MultiscalarMul, curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng, rand::rngs::OsRng,
@ -32,6 +33,9 @@ use {
merlin::Transcript, merlin::Transcript,
}; };
/// Byte length of a ciphertext-commitment equality proof.
const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN: usize = UNIT_LEN * 6;
/// Equality proof. /// Equality proof.
/// ///
/// Contains all the elliptic curve and scalar components that make up the sigma protocol. /// Contains all the elliptic curve and scalar components that make up the sigma protocol.
@ -203,28 +207,26 @@ impl CiphertextCommitmentEqualityProof {
} }
} }
pub fn to_bytes(&self) -> [u8; 192] { pub fn to_bytes(&self) -> [u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN] {
let mut buf = [0_u8; 192]; let mut buf = [0_u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN];
buf[..32].copy_from_slice(self.Y_0.as_bytes()); let mut chunks = buf.chunks_mut(UNIT_LEN);
buf[32..64].copy_from_slice(self.Y_1.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes());
buf[64..96].copy_from_slice(self.Y_2.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes());
buf[96..128].copy_from_slice(self.z_s.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes());
buf[128..160].copy_from_slice(self.z_x.as_bytes()); chunks.next().unwrap().copy_from_slice(self.z_s.as_bytes());
buf[160..192].copy_from_slice(self.z_r.as_bytes()); chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes());
chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes());
buf buf
} }
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> {
if bytes.len() != 192 { let mut chunks = bytes.chunks(UNIT_LEN);
return Err(ProofVerificationError::Deserialization.into()); let Y_0 = ristretto_point_from_optional_slice(chunks.next())?;
} let Y_1 = ristretto_point_from_optional_slice(chunks.next())?;
let Y_2 = ristretto_point_from_optional_slice(chunks.next())?;
let Y_0 = CompressedRistretto::from_slice(&bytes[..32]); let z_s = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_1 = CompressedRistretto::from_slice(&bytes[32..64]); let z_x = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_2 = CompressedRistretto::from_slice(&bytes[64..96]); let z_r = canonical_scalar_from_optional_slice(chunks.next())?;
let z_s = canonical_scalar_from_slice(&bytes[96..128])?;
let z_x = canonical_scalar_from_slice(&bytes[128..160])?;
let z_r = canonical_scalar_from_slice(&bytes[160..192])?;
Ok(CiphertextCommitmentEqualityProof { Ok(CiphertextCommitmentEqualityProof {
Y_0, Y_0,

View File

@ -14,7 +14,8 @@
use { use {
crate::{ crate::{
encryption::pedersen::{PedersenCommitment, PedersenOpening, G, H}, encryption::pedersen::{PedersenCommitment, PedersenOpening, G, H},
sigma_proofs::canonical_scalar_from_slice, sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
UNIT_LEN,
}, },
rand::rngs::OsRng, rand::rngs::OsRng,
}; };
@ -32,6 +33,9 @@ use {
subtle::{Choice, ConditionallySelectable, ConstantTimeGreater}, subtle::{Choice, ConditionallySelectable, ConstantTimeGreater},
}; };
/// Byte length of a fee sigma proof.
const FEE_SIGMA_PROOF_LEN: usize = UNIT_LEN * 8;
/// Fee sigma proof. /// Fee sigma proof.
/// ///
/// The proof consists of two main components: `fee_max_proof` and `fee_equality_proof`. If the fee /// The proof consists of two main components: `fee_max_proof` and `fee_equality_proof`. If the fee
@ -387,33 +391,55 @@ impl FeeSigmaProof {
} }
} }
pub fn to_bytes(&self) -> [u8; 256] { pub fn to_bytes(&self) -> [u8; FEE_SIGMA_PROOF_LEN] {
let mut buf = [0_u8; 256]; let mut buf = [0_u8; FEE_SIGMA_PROOF_LEN];
buf[..32].copy_from_slice(self.fee_max_proof.Y_max_proof.as_bytes()); let mut chunks = buf.chunks_mut(UNIT_LEN);
buf[32..64].copy_from_slice(self.fee_max_proof.z_max_proof.as_bytes()); chunks
buf[64..96].copy_from_slice(self.fee_max_proof.c_max_proof.as_bytes()); .next()
buf[96..128].copy_from_slice(self.fee_equality_proof.Y_delta.as_bytes()); .unwrap()
buf[128..160].copy_from_slice(self.fee_equality_proof.Y_claimed.as_bytes()); .copy_from_slice(self.fee_max_proof.Y_max_proof.as_bytes());
buf[160..192].copy_from_slice(self.fee_equality_proof.z_x.as_bytes()); chunks
buf[192..224].copy_from_slice(self.fee_equality_proof.z_delta.as_bytes()); .next()
buf[224..256].copy_from_slice(self.fee_equality_proof.z_claimed.as_bytes()); .unwrap()
.copy_from_slice(self.fee_max_proof.z_max_proof.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(self.fee_max_proof.c_max_proof.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(self.fee_equality_proof.Y_delta.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(self.fee_equality_proof.Y_claimed.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(self.fee_equality_proof.z_x.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(self.fee_equality_proof.z_delta.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(self.fee_equality_proof.z_claimed.as_bytes());
buf buf
} }
pub fn from_bytes(bytes: &[u8]) -> Result<Self, FeeSigmaProofError> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, FeeSigmaProofError> {
if bytes.len() != 256 { let mut chunks = bytes.chunks(UNIT_LEN);
return Err(ProofVerificationError::Deserialization.into()); let Y_max_proof = ristretto_point_from_optional_slice(chunks.next())?;
} let z_max_proof = canonical_scalar_from_optional_slice(chunks.next())?;
let c_max_proof = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_max_proof = CompressedRistretto::from_slice(&bytes[..32]); let Y_delta = ristretto_point_from_optional_slice(chunks.next())?;
let z_max_proof = canonical_scalar_from_slice(&bytes[32..64])?; let Y_claimed = ristretto_point_from_optional_slice(chunks.next())?;
let c_max_proof = canonical_scalar_from_slice(&bytes[64..96])?; let z_x = canonical_scalar_from_optional_slice(chunks.next())?;
let z_delta = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_delta = CompressedRistretto::from_slice(&bytes[96..128]); let z_claimed = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_claimed = CompressedRistretto::from_slice(&bytes[128..160]);
let z_x = canonical_scalar_from_slice(&bytes[160..192])?;
let z_delta = canonical_scalar_from_slice(&bytes[192..224])?;
let z_claimed = canonical_scalar_from_slice(&bytes[224..256])?;
Ok(Self { Ok(Self {
fee_max_proof: FeeMaxProof { fee_max_proof: FeeMaxProof {

View File

@ -16,7 +16,8 @@ use {
pedersen::{PedersenCommitment, PedersenOpening, G, H}, pedersen::{PedersenCommitment, PedersenOpening, G, H},
}, },
errors::ProofVerificationError, errors::ProofVerificationError,
sigma_proofs::canonical_scalar_from_slice, sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
UNIT_LEN,
}, },
curve25519_dalek::traits::MultiscalarMul, curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng, rand::rngs::OsRng,
@ -32,6 +33,9 @@ use {
merlin::Transcript, merlin::Transcript,
}; };
/// Byte length of a grouped ciphertext validity proof for 2 handles
const GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = UNIT_LEN * 5;
/// The grouped ciphertext validity proof for 2 handles. /// The grouped ciphertext validity proof for 2 handles.
/// ///
/// Contains all the elliptic curve and scalar components that make up the sigma protocol. /// Contains all the elliptic curve and scalar components that make up the sigma protocol.
@ -194,26 +198,24 @@ impl GroupedCiphertext2HandlesValidityProof {
} }
} }
pub fn to_bytes(&self) -> [u8; 160] { pub fn to_bytes(&self) -> [u8; GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN] {
let mut buf = [0_u8; 160]; let mut buf = [0_u8; GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN];
buf[..32].copy_from_slice(self.Y_0.as_bytes()); let mut chunks = buf.chunks_mut(UNIT_LEN);
buf[32..64].copy_from_slice(self.Y_1.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_0.as_bytes());
buf[64..96].copy_from_slice(self.Y_2.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_1.as_bytes());
buf[96..128].copy_from_slice(self.z_r.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_2.as_bytes());
buf[128..160].copy_from_slice(self.z_x.as_bytes()); chunks.next().unwrap().copy_from_slice(self.z_r.as_bytes());
chunks.next().unwrap().copy_from_slice(self.z_x.as_bytes());
buf buf
} }
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofError> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, ValidityProofError> {
if bytes.len() != 160 { let mut chunks = bytes.chunks(UNIT_LEN);
return Err(ProofVerificationError::Deserialization.into()); let Y_0 = ristretto_point_from_optional_slice(chunks.next())?;
} let Y_1 = ristretto_point_from_optional_slice(chunks.next())?;
let Y_2 = ristretto_point_from_optional_slice(chunks.next())?;
let Y_0 = CompressedRistretto::from_slice(&bytes[..32]); let z_r = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_1 = CompressedRistretto::from_slice(&bytes[32..64]); let z_x = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_2 = CompressedRistretto::from_slice(&bytes[64..96]);
let z_r = canonical_scalar_from_slice(&bytes[96..128])?;
let z_x = canonical_scalar_from_slice(&bytes[128..160])?;
Ok(GroupedCiphertext2HandlesValidityProof { Ok(GroupedCiphertext2HandlesValidityProof {
Y_0, Y_0,

View File

@ -15,19 +15,36 @@ pub mod pubkey_proof;
pub mod zero_balance_proof; pub mod zero_balance_proof;
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
use {crate::errors::ProofVerificationError, curve25519_dalek::scalar::Scalar}; use {
crate::{errors::ProofVerificationError, RISTRETTO_POINT_LEN, SCALAR_LEN},
curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar},
};
/// Deserializes an optional slice of bytes to a compressed Ristretto point.
///
/// This is a helper function for deserializing byte encodings of sigma proofs. It is designed to
/// be used with `std::slice::Chunks`.
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
fn canonical_scalar_from_slice(bytes: &[u8]) -> Result<Scalar, ProofVerificationError> { fn ristretto_point_from_optional_slice(
if bytes.len() != 32 { optional_slice: Option<&[u8]>,
return Err(ProofVerificationError::Deserialization); ) -> Result<CompressedRistretto, ProofVerificationError> {
optional_slice
.and_then(|slice| (slice.len() == RISTRETTO_POINT_LEN).then_some(slice))
.map(CompressedRistretto::from_slice)
.ok_or(ProofVerificationError::Deserialization)
} }
let scalar_bytes = bytes[..32] /// Deserializes an optional slice of bytes to a scalar.
.try_into() ///
.map_err(|_| ProofVerificationError::Deserialization)?; /// This is a helper function for deserializing byte encodings of sigma proofs. It is designed to
/// be used with `std::slice::Chunks`.
let scalar = Scalar::from_canonical_bytes(scalar_bytes) #[cfg(not(target_os = "solana"))]
.ok_or(ProofVerificationError::Deserialization)?; fn canonical_scalar_from_optional_slice(
Ok(scalar) optional_slice: Option<&[u8]>,
) -> Result<Scalar, ProofVerificationError> {
optional_slice
.and_then(|slice| (slice.len() == SCALAR_LEN).then_some(slice)) // if chunk is the wrong length, convert to None
.and_then(|slice| slice.try_into().ok()) // convert to array
.and_then(Scalar::from_canonical_bytes)
.ok_or(ProofVerificationError::Deserialization)
} }

View File

@ -10,7 +10,8 @@ use {
elgamal::{ElGamalKeypair, ElGamalPubkey}, elgamal::{ElGamalKeypair, ElGamalPubkey},
pedersen::H, pedersen::H,
}, },
sigma_proofs::canonical_scalar_from_slice, sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
UNIT_LEN,
}, },
rand::rngs::OsRng, rand::rngs::OsRng,
zeroize::Zeroize, zeroize::Zeroize,
@ -28,6 +29,9 @@ use {
merlin::Transcript, merlin::Transcript,
}; };
/// Byte length of a public key validity proof.
const PUBKEY_VALIDITY_PROOF_LEN: usize = UNIT_LEN * 2;
/// Public-key proof. /// Public-key proof.
/// ///
/// Contains all the elliptic curve and scalar components that make up the sigma protocol. /// Contains all the elliptic curve and scalar components that make up the sigma protocol.
@ -116,21 +120,18 @@ impl PubkeyValidityProof {
} }
} }
pub fn to_bytes(&self) -> [u8; 64] { pub fn to_bytes(&self) -> [u8; PUBKEY_VALIDITY_PROOF_LEN] {
let mut buf = [0_u8; 64]; let mut buf = [0_u8; PUBKEY_VALIDITY_PROOF_LEN];
buf[..32].copy_from_slice(self.Y.as_bytes()); let mut chunks = buf.chunks_mut(UNIT_LEN);
buf[32..64].copy_from_slice(self.z.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y.as_bytes());
chunks.next().unwrap().copy_from_slice(self.z.as_bytes());
buf buf
} }
pub fn from_bytes(bytes: &[u8]) -> Result<Self, PubkeyValidityProofError> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, PubkeyValidityProofError> {
if bytes.len() != 64 { let mut chunks = bytes.chunks(UNIT_LEN);
return Err(ProofVerificationError::Deserialization.into()); let Y = ristretto_point_from_optional_slice(chunks.next())?;
} let z = canonical_scalar_from_optional_slice(chunks.next())?;
let Y = CompressedRistretto::from_slice(&bytes[..32]);
let z = canonical_scalar_from_slice(&bytes[32..64])?;
Ok(PubkeyValidityProof { Y, z }) Ok(PubkeyValidityProof { Y, z })
} }
} }

View File

@ -11,7 +11,8 @@ use {
pedersen::H, pedersen::H,
}, },
errors::ProofVerificationError, errors::ProofVerificationError,
sigma_proofs::canonical_scalar_from_slice, sigma_proofs::{canonical_scalar_from_optional_slice, ristretto_point_from_optional_slice},
UNIT_LEN,
}, },
curve25519_dalek::traits::MultiscalarMul, curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng, rand::rngs::OsRng,
@ -27,6 +28,9 @@ use {
merlin::Transcript, merlin::Transcript,
}; };
/// Byte length of a zero-balance proof.
const ZERO_BALANCE_PROOF_LEN: usize = UNIT_LEN * 3;
/// Zero-balance proof. /// Zero-balance proof.
/// ///
/// Contains all the elliptic curve and scalar components that make up the sigma protocol. /// Contains all the elliptic curve and scalar components that make up the sigma protocol.
@ -152,23 +156,20 @@ impl ZeroBalanceProof {
} }
} }
pub fn to_bytes(&self) -> [u8; 96] { pub fn to_bytes(&self) -> [u8; ZERO_BALANCE_PROOF_LEN] {
let mut buf = [0_u8; 96]; let mut buf = [0_u8; ZERO_BALANCE_PROOF_LEN];
buf[..32].copy_from_slice(self.Y_P.as_bytes()); let mut chunks = buf.chunks_mut(UNIT_LEN);
buf[32..64].copy_from_slice(self.Y_D.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_P.as_bytes());
buf[64..96].copy_from_slice(self.z.as_bytes()); chunks.next().unwrap().copy_from_slice(self.Y_D.as_bytes());
chunks.next().unwrap().copy_from_slice(self.z.as_bytes());
buf buf
} }
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ZeroBalanceProofError> { pub fn from_bytes(bytes: &[u8]) -> Result<Self, ZeroBalanceProofError> {
if bytes.len() != 96 { let mut chunks = bytes.chunks(UNIT_LEN);
return Err(ProofVerificationError::Deserialization.into()); let Y_P = ristretto_point_from_optional_slice(chunks.next())?;
} let Y_D = ristretto_point_from_optional_slice(chunks.next())?;
let z = canonical_scalar_from_optional_slice(chunks.next())?;
let Y_P = CompressedRistretto::from_slice(&bytes[..32]);
let Y_D = CompressedRistretto::from_slice(&bytes[32..64]);
let z = canonical_scalar_from_slice(&bytes[64..96])?;
Ok(ZeroBalanceProof { Y_P, Y_D, z }) Ok(ZeroBalanceProof { Y_P, Y_D, z })
} }
} }

View File

@ -8,10 +8,13 @@ use {
std::fmt, std::fmt,
}; };
/// Byte length of an authenticated encryption ciphertext
const AE_CIPHERTEXT_LEN: usize = 36;
/// The `AeCiphertext` type as a `Pod`. /// The `AeCiphertext` type as a `Pod`.
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct AeCiphertext(pub [u8; 36]); pub struct AeCiphertext(pub [u8; AE_CIPHERTEXT_LEN]);
// `AeCiphertext` is a wrapper type for a byte array, which is both `Pod` and `Zeroable`. However, // `AeCiphertext` is a wrapper type for a byte array, which is both `Pod` and `Zeroable`. However,
// the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two // the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two

View File

@ -1,20 +1,32 @@
//! Plain Old Data types for the ElGamal encryption scheme. //! Plain Old Data types for the ElGamal encryption scheme.
use {
crate::zk_token_elgamal::pod::{Pod, Zeroable},
base64::{prelude::BASE64_STANDARD, Engine},
std::fmt,
};
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
use { use {
crate::{encryption::elgamal as decoded, errors::ProofError}, crate::{encryption::elgamal as decoded, errors::ProofError},
curve25519_dalek::ristretto::CompressedRistretto, curve25519_dalek::ristretto::CompressedRistretto,
}; };
use {
crate::{
zk_token_elgamal::pod::{pedersen::PEDERSEN_COMMITMENT_LEN, Pod, Zeroable},
RISTRETTO_POINT_LEN,
},
base64::{prelude::BASE64_STANDARD, Engine},
std::fmt,
};
/// Byte length of an ElGamal public key
const ELGAMAL_PUBKEY_LEN: usize = RISTRETTO_POINT_LEN;
/// Byte length of a decrypt handle
pub(crate) const DECRYPT_HANDLE_LEN: usize = RISTRETTO_POINT_LEN;
/// Byte length of an ElGamal ciphertext
const ELGAMAL_CIPHERTEXT_LEN: usize = PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN;
/// The `ElGamalCiphertext` type as a `Pod`. /// The `ElGamalCiphertext` type as a `Pod`.
#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] #[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct ElGamalCiphertext(pub [u8; 64]); pub struct ElGamalCiphertext(pub [u8; ELGAMAL_CIPHERTEXT_LEN]);
impl fmt::Debug for ElGamalCiphertext { impl fmt::Debug for ElGamalCiphertext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -53,7 +65,7 @@ impl TryFrom<ElGamalCiphertext> for decoded::ElGamalCiphertext {
/// The `ElGamalPubkey` type as a `Pod`. /// The `ElGamalPubkey` type as a `Pod`.
#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] #[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct ElGamalPubkey(pub [u8; 32]); pub struct ElGamalPubkey(pub [u8; ELGAMAL_PUBKEY_LEN]);
impl fmt::Debug for ElGamalPubkey { impl fmt::Debug for ElGamalPubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -86,7 +98,7 @@ impl TryFrom<ElGamalPubkey> for decoded::ElGamalPubkey {
/// The `DecryptHandle` type as a `Pod`. /// The `DecryptHandle` type as a `Pod`.
#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] #[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct DecryptHandle(pub [u8; 32]); pub struct DecryptHandle(pub [u8; DECRYPT_HANDLE_LEN]);
impl fmt::Debug for DecryptHandle { impl fmt::Debug for DecryptHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

View File

@ -3,14 +3,24 @@
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
use crate::{encryption::grouped_elgamal::GroupedElGamalCiphertext, errors::ProofError}; use crate::{encryption::grouped_elgamal::GroupedElGamalCiphertext, errors::ProofError};
use { use {
crate::zk_token_elgamal::pod::{Pod, Zeroable}, crate::zk_token_elgamal::pod::{
elgamal::DECRYPT_HANDLE_LEN, pedersen::PEDERSEN_COMMITMENT_LEN, Pod, Zeroable,
},
std::fmt, std::fmt,
}; };
/// Byte length of a grouped ElGamal ciphertext with 2 handles
const GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES: usize =
PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN;
/// Byte length of a grouped ElGamal ciphertext with 3 handles
const GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES: usize =
PEDERSEN_COMMITMENT_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN + DECRYPT_HANDLE_LEN;
/// The `GroupedElGamalCiphertext` type with two decryption handles as a `Pod` /// The `GroupedElGamalCiphertext` type with two decryption handles as a `Pod`
#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] #[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct GroupedElGamalCiphertext2Handles(pub [u8; 96]); pub struct GroupedElGamalCiphertext2Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_2_HANDLES]);
impl fmt::Debug for GroupedElGamalCiphertext2Handles { impl fmt::Debug for GroupedElGamalCiphertext2Handles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -42,7 +52,7 @@ impl TryFrom<GroupedElGamalCiphertext2Handles> for GroupedElGamalCiphertext<2> {
/// The `GroupedElGamalCiphertext` type with three decryption handles as a `Pod` /// The `GroupedElGamalCiphertext` type with three decryption handles as a `Pod`
#[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)] #[derive(Clone, Copy, Pod, Zeroable, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct GroupedElGamalCiphertext3Handles(pub [u8; 128]); pub struct GroupedElGamalCiphertext3Handles(pub [u8; GROUPED_ELGAMAL_CIPHERTEXT_3_HANDLES]);
impl fmt::Debug for GroupedElGamalCiphertext3Handles { impl fmt::Debug for GroupedElGamalCiphertext3Handles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

View File

@ -1,19 +1,25 @@
//! Plain Old Data type for the Pedersen commitment scheme. //! Plain Old Data type for the Pedersen commitment scheme.
use {
crate::zk_token_elgamal::pod::{Pod, Zeroable},
std::fmt,
};
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
use { use {
crate::{encryption::pedersen as decoded, errors::ProofError}, crate::{encryption::pedersen as decoded, errors::ProofError},
curve25519_dalek::ristretto::CompressedRistretto, curve25519_dalek::ristretto::CompressedRistretto,
}; };
use {
crate::{
zk_token_elgamal::pod::{Pod, Zeroable},
RISTRETTO_POINT_LEN,
},
std::fmt,
};
/// Byte length of a Pedersen commitment
pub(crate) const PEDERSEN_COMMITMENT_LEN: usize = RISTRETTO_POINT_LEN;
/// The `PedersenCommitment` type as a `Pod`. /// The `PedersenCommitment` type as a `Pod`.
#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] #[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct PedersenCommitment(pub [u8; 32]); pub struct PedersenCommitment(pub [u8; PEDERSEN_COMMITMENT_LEN]);
impl fmt::Debug for PedersenCommitment { impl fmt::Debug for PedersenCommitment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

View File

@ -1,35 +1,58 @@
//! Plain Old Data types for range proofs. //! Plain Old Data types for range proofs.
use crate::zk_token_elgamal::pod::{Pod, Zeroable};
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
use crate::{ use crate::{
errors::ProofVerificationError, errors::ProofVerificationError,
range_proof::{self as decoded, errors::RangeProofError}, range_proof::{self as decoded, errors::RangeProofError},
UNIT_LEN,
}; };
use crate::{
zk_token_elgamal::pod::{Pod, Zeroable},
RISTRETTO_POINT_LEN, SCALAR_LEN,
};
/// Byte length of a range proof excluding the inner-product proof component
const RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN: usize = 5 * RISTRETTO_POINT_LEN + 2 * SCALAR_LEN;
/// Byte length of an inner-product proof for a vector of length 64
const INNER_PRODUCT_PROOF_U64_LEN: usize = 448;
/// Byte length of a range proof for an unsigned 64-bit number
const RANGE_PROOF_U64_LEN: usize =
INNER_PRODUCT_PROOF_U64_LEN + RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN;
/// Byte length of an inner-product proof for a vector of length 128
const INNER_PRODUCT_PROOF_U128_LEN: usize = 512;
/// Byte length of a range proof for an unsigned 128-bit number
const RANGE_PROOF_U128_LEN: usize =
INNER_PRODUCT_PROOF_U128_LEN + RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN;
/// Byte length of an inner-product proof for a vector of length 256
const INNER_PRODUCT_PROOF_U256_LEN: usize = 576;
/// Byte length of a range proof for an unsigned 256-bit number
const RANGE_PROOF_U256_LEN: usize =
INNER_PRODUCT_PROOF_U256_LEN + RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN;
/// The `RangeProof` type as a `Pod` restricted to proofs on 64-bit numbers. /// The `RangeProof` type as a `Pod` restricted to proofs on 64-bit numbers.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct RangeProofU64(pub [u8; 672]); pub struct RangeProofU64(pub [u8; RANGE_PROOF_U64_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl TryFrom<decoded::RangeProof> for RangeProofU64 { impl TryFrom<decoded::RangeProof> for RangeProofU64 {
type Error = RangeProofError; type Error = RangeProofError;
fn try_from(decoded_proof: decoded::RangeProof) -> Result<Self, Self::Error> { fn try_from(decoded_proof: decoded::RangeProof) -> Result<Self, Self::Error> {
if decoded_proof.ipp_proof.serialized_size() != 448 { if decoded_proof.ipp_proof.serialized_size() != INNER_PRODUCT_PROOF_U64_LEN {
return Err(ProofVerificationError::Deserialization.into()); return Err(ProofVerificationError::Deserialization.into());
} }
let mut buf = [0_u8; 672]; let mut buf = [0_u8; RANGE_PROOF_U64_LEN];
buf[..32].copy_from_slice(decoded_proof.A.as_bytes()); copy_range_proof_modulo_inner_product_proof(&decoded_proof, &mut buf);
buf[32..64].copy_from_slice(decoded_proof.S.as_bytes()); buf[RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN..RANGE_PROOF_U64_LEN]
buf[64..96].copy_from_slice(decoded_proof.T_1.as_bytes()); .copy_from_slice(&decoded_proof.ipp_proof.to_bytes());
buf[96..128].copy_from_slice(decoded_proof.T_2.as_bytes());
buf[128..160].copy_from_slice(decoded_proof.t_x.as_bytes());
buf[160..192].copy_from_slice(decoded_proof.t_x_blinding.as_bytes());
buf[192..224].copy_from_slice(decoded_proof.e_blinding.as_bytes());
buf[224..672].copy_from_slice(&decoded_proof.ipp_proof.to_bytes());
Ok(RangeProofU64(buf)) Ok(RangeProofU64(buf))
} }
} }
@ -46,26 +69,21 @@ impl TryFrom<RangeProofU64> for decoded::RangeProof {
/// The `RangeProof` type as a `Pod` restricted to proofs on 128-bit numbers. /// The `RangeProof` type as a `Pod` restricted to proofs on 128-bit numbers.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct RangeProofU128(pub [u8; 736]); pub struct RangeProofU128(pub [u8; RANGE_PROOF_U128_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl TryFrom<decoded::RangeProof> for RangeProofU128 { impl TryFrom<decoded::RangeProof> for RangeProofU128 {
type Error = RangeProofError; type Error = RangeProofError;
fn try_from(decoded_proof: decoded::RangeProof) -> Result<Self, Self::Error> { fn try_from(decoded_proof: decoded::RangeProof) -> Result<Self, Self::Error> {
if decoded_proof.ipp_proof.serialized_size() != 512 { if decoded_proof.ipp_proof.serialized_size() != INNER_PRODUCT_PROOF_U128_LEN {
return Err(ProofVerificationError::Deserialization.into()); return Err(ProofVerificationError::Deserialization.into());
} }
let mut buf = [0_u8; 736]; let mut buf = [0_u8; RANGE_PROOF_U128_LEN];
buf[..32].copy_from_slice(decoded_proof.A.as_bytes()); copy_range_proof_modulo_inner_product_proof(&decoded_proof, &mut buf);
buf[32..64].copy_from_slice(decoded_proof.S.as_bytes()); buf[RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN..RANGE_PROOF_U128_LEN]
buf[64..96].copy_from_slice(decoded_proof.T_1.as_bytes()); .copy_from_slice(&decoded_proof.ipp_proof.to_bytes());
buf[96..128].copy_from_slice(decoded_proof.T_2.as_bytes());
buf[128..160].copy_from_slice(decoded_proof.t_x.as_bytes());
buf[160..192].copy_from_slice(decoded_proof.t_x_blinding.as_bytes());
buf[192..224].copy_from_slice(decoded_proof.e_blinding.as_bytes());
buf[224..736].copy_from_slice(&decoded_proof.ipp_proof.to_bytes());
Ok(RangeProofU128(buf)) Ok(RangeProofU128(buf))
} }
} }
@ -82,26 +100,21 @@ impl TryFrom<RangeProofU128> for decoded::RangeProof {
/// The `RangeProof` type as a `Pod` restricted to proofs on 256-bit numbers. /// The `RangeProof` type as a `Pod` restricted to proofs on 256-bit numbers.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct RangeProofU256(pub [u8; 800]); pub struct RangeProofU256(pub [u8; RANGE_PROOF_U256_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl TryFrom<decoded::RangeProof> for RangeProofU256 { impl TryFrom<decoded::RangeProof> for RangeProofU256 {
type Error = RangeProofError; type Error = RangeProofError;
fn try_from(decoded_proof: decoded::RangeProof) -> Result<Self, Self::Error> { fn try_from(decoded_proof: decoded::RangeProof) -> Result<Self, Self::Error> {
if decoded_proof.ipp_proof.serialized_size() != 576 { if decoded_proof.ipp_proof.serialized_size() != INNER_PRODUCT_PROOF_U256_LEN {
return Err(ProofVerificationError::Deserialization.into()); return Err(ProofVerificationError::Deserialization.into());
} }
let mut buf = [0_u8; 800]; let mut buf = [0_u8; RANGE_PROOF_U256_LEN];
buf[..32].copy_from_slice(decoded_proof.A.as_bytes()); copy_range_proof_modulo_inner_product_proof(&decoded_proof, &mut buf);
buf[32..64].copy_from_slice(decoded_proof.S.as_bytes()); buf[RANGE_PROOF_MODULO_INNER_PRODUCT_PROOF_LEN..RANGE_PROOF_U256_LEN]
buf[64..96].copy_from_slice(decoded_proof.T_1.as_bytes()); .copy_from_slice(&decoded_proof.ipp_proof.to_bytes());
buf[96..128].copy_from_slice(decoded_proof.T_2.as_bytes());
buf[128..160].copy_from_slice(decoded_proof.t_x.as_bytes());
buf[160..192].copy_from_slice(decoded_proof.t_x_blinding.as_bytes());
buf[192..224].copy_from_slice(decoded_proof.e_blinding.as_bytes());
buf[224..800].copy_from_slice(&decoded_proof.ipp_proof.to_bytes());
Ok(RangeProofU256(buf)) Ok(RangeProofU256(buf))
} }
} }
@ -115,6 +128,24 @@ impl TryFrom<RangeProofU256> for decoded::RangeProof {
} }
} }
#[cfg(not(target_os = "solana"))]
fn copy_range_proof_modulo_inner_product_proof(proof: &decoded::RangeProof, buf: &mut [u8]) {
let mut chunks = buf.chunks_mut(UNIT_LEN);
chunks.next().unwrap().copy_from_slice(proof.A.as_bytes());
chunks.next().unwrap().copy_from_slice(proof.S.as_bytes());
chunks.next().unwrap().copy_from_slice(proof.T_1.as_bytes());
chunks.next().unwrap().copy_from_slice(proof.T_2.as_bytes());
chunks.next().unwrap().copy_from_slice(proof.t_x.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(proof.t_x_blinding.as_bytes());
chunks
.next()
.unwrap()
.copy_from_slice(proof.e_blinding.as_bytes());
}
// The range proof pod types are wrappers for byte arrays, which are both `Pod` and `Zeroable`. However, // The range proof pod types are wrappers for byte arrays, which are both `Pod` and `Zeroable`. However,
// the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two // the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two
// length byte arrays. Directly implement these traits for the range proof pod types. // length byte arrays. Directly implement these traits for the range proof pod types.

View File

@ -12,10 +12,31 @@ use crate::sigma_proofs::{
}; };
use crate::zk_token_elgamal::pod::{Pod, Zeroable}; use crate::zk_token_elgamal::pod::{Pod, Zeroable};
/// Byte length of a ciphertext-commitment equality proof
const CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN: usize = 192;
/// Byte length of a ciphertext-ciphertext equality proof
const CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN: usize = 224;
/// Byte length of a grouped ciphertext for 2 handles validity proof
const GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = 160;
/// Byte length of a batched grouped ciphertext for 2 handles validity proof
const BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN: usize = 160;
/// Byte length of a zero-balance proof
const ZERO_BALANCE_PROOF_LEN: usize = 96;
/// Byte length of a fee sigma proof
const FEE_SIGMA_PROOF_LEN: usize = 256;
/// Byte length of a public key validity proof
const PUBKEY_VALIDITY_PROOF_LEN: usize = 64;
/// The `CiphertextCommitmentEqualityProof` type as a `Pod`. /// The `CiphertextCommitmentEqualityProof` type as a `Pod`.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct CiphertextCommitmentEqualityProof(pub [u8; 192]); pub struct CiphertextCommitmentEqualityProof(pub [u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<DecodedCiphertextCommitmentEqualityProof> for CiphertextCommitmentEqualityProof { impl From<DecodedCiphertextCommitmentEqualityProof> for CiphertextCommitmentEqualityProof {
@ -36,7 +57,7 @@ impl TryFrom<CiphertextCommitmentEqualityProof> for DecodedCiphertextCommitmentE
/// The `CiphertextCiphertextEqualityProof` type as a `Pod`. /// The `CiphertextCiphertextEqualityProof` type as a `Pod`.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct CiphertextCiphertextEqualityProof(pub [u8; 224]); pub struct CiphertextCiphertextEqualityProof(pub [u8; CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<DecodedCiphertextCiphertextEqualityProof> for CiphertextCiphertextEqualityProof { impl From<DecodedCiphertextCiphertextEqualityProof> for CiphertextCiphertextEqualityProof {
@ -57,7 +78,9 @@ impl TryFrom<CiphertextCiphertextEqualityProof> for DecodedCiphertextCiphertextE
/// The `GroupedCiphertext2HandlesValidityProof` type as a `Pod`. /// The `GroupedCiphertext2HandlesValidityProof` type as a `Pod`.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct GroupedCiphertext2HandlesValidityProof(pub [u8; 160]); pub struct GroupedCiphertext2HandlesValidityProof(
pub [u8; GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN],
);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<DecodedGroupedCiphertext2HandlesValidityProof> impl From<DecodedGroupedCiphertext2HandlesValidityProof>
@ -82,7 +105,9 @@ impl TryFrom<GroupedCiphertext2HandlesValidityProof>
/// The `BatchedGroupedCiphertext2HandlesValidityProof` type as a `Pod`. /// The `BatchedGroupedCiphertext2HandlesValidityProof` type as a `Pod`.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct BatchedGroupedCiphertext2HandlesValidityProof(pub [u8; 160]); pub struct BatchedGroupedCiphertext2HandlesValidityProof(
pub [u8; BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN],
);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<DecodedBatchedGroupedCiphertext2HandlesValidityProof> impl From<DecodedBatchedGroupedCiphertext2HandlesValidityProof>
@ -109,7 +134,7 @@ impl TryFrom<BatchedGroupedCiphertext2HandlesValidityProof>
/// The `ZeroBalanceProof` type as a `Pod`. /// The `ZeroBalanceProof` type as a `Pod`.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct ZeroBalanceProof(pub [u8; 96]); pub struct ZeroBalanceProof(pub [u8; ZERO_BALANCE_PROOF_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<DecodedZeroBalanceProof> for ZeroBalanceProof { impl From<DecodedZeroBalanceProof> for ZeroBalanceProof {
@ -130,7 +155,7 @@ impl TryFrom<ZeroBalanceProof> for DecodedZeroBalanceProof {
/// The `FeeSigmaProof` type as a `Pod`. /// The `FeeSigmaProof` type as a `Pod`.
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
#[repr(transparent)] #[repr(transparent)]
pub struct FeeSigmaProof(pub [u8; 256]); pub struct FeeSigmaProof(pub [u8; FEE_SIGMA_PROOF_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<DecodedFeeSigmaProof> for FeeSigmaProof { impl From<DecodedFeeSigmaProof> for FeeSigmaProof {
@ -151,7 +176,7 @@ impl TryFrom<FeeSigmaProof> for DecodedFeeSigmaProof {
/// The `PubkeyValidityProof` type as a `Pod`. /// The `PubkeyValidityProof` type as a `Pod`.
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
#[repr(transparent)] #[repr(transparent)]
pub struct PubkeyValidityProof(pub [u8; 64]); pub struct PubkeyValidityProof(pub [u8; PUBKEY_VALIDITY_PROOF_LEN]);
#[cfg(not(target_os = "solana"))] #[cfg(not(target_os = "solana"))]
impl From<DecodedPubkeyValidityProof> for PubkeyValidityProof { impl From<DecodedPubkeyValidityProof> for PubkeyValidityProof {