Refactor sdk/src/pod.rs

This commit is contained in:
Michael Vines 2021-09-30 10:25:36 -07:00
parent d01d425e4b
commit f3e7e62813
12 changed files with 664 additions and 646 deletions

View File

@ -14,6 +14,6 @@ pub enum ProofError {
InvalidBitsize,
/// This error occurs when there are insufficient generators for the proof.
InvalidGeneratorsLength,
/// This error occurs when TODO
/// This error occurs a `zk_token_elgamal::pod::ElGamalCT` contains invalid ElGamalCT ciphertext
InconsistentCTData,
}

View File

@ -1,5 +1,5 @@
use {
crate::pod::*,
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_arch = "bpf"))]
@ -31,7 +31,7 @@ use {
#[repr(C)]
pub struct CloseAccountData {
/// The source account available balance in encrypted form
pub balance: PodElGamalCT, // 64 bytes
pub balance: pod::ElGamalCT, // 64 bytes
/// Proof that the source account available balance is zero
pub proof: CloseAccountProof, // 64 bytes
@ -63,8 +63,8 @@ impl Verifiable for CloseAccountData {
#[repr(C)]
#[allow(non_snake_case)]
pub struct CloseAccountProof {
pub R: PodCompressedRistretto, // 32 bytes
pub z: PodScalar, // 32 bytes
pub R: pod::CompressedRistretto, // 32 bytes
pub z: pod::Scalar, // 32 bytes
}
#[allow(non_snake_case)]
@ -156,7 +156,7 @@ mod test {
assert!(proof.verify(&balance).is_err());
// A zeroed cyphertext should be considered as an account balance of 0
let zeroed_ct: ElGamalCT = PodElGamalCT::zeroed().try_into().unwrap();
let zeroed_ct: ElGamalCT = pod::ElGamalCT::zeroed().try_into().unwrap();
let proof = CloseAccountProof::new(&source_sk, &zeroed_ct);
assert!(proof.verify(&zeroed_ct).is_ok());
}

View File

@ -1,5 +1,5 @@
mod close_account;
pub mod transfer;
mod transfer;
mod update_account_pk;
mod withdraw;

View File

@ -1,5 +1,5 @@
use {
crate::pod::*,
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_arch = "bpf"))]
@ -145,7 +145,7 @@ pub struct TransferRangeProofData {
/// 1. the source account has enough funds for the transfer (i.e. the final balance is a
/// 64-bit positive number)
/// 2. the transfer amount is a 64-bit positive number
pub proof: PodRangeProof128, // 736 bytes
pub proof: pod::RangeProof128, // 736 bytes
/// Ephemeral state between the two transfer instruction data
pub ephemeral_state: TransferEphemeralState, // 128 bytes
@ -185,7 +185,7 @@ pub struct TransferValidityProofData {
pub transfer_public_keys: TransferPubKeys, // 96 bytes
/// The final spendable ciphertext after the transfer
pub new_spendable_ct: PodElGamalCT, // 64 bytes
pub new_spendable_ct: pod::ElGamalCT, // 64 bytes
/// Proof that certifies that the decryption handles are generated correctly
pub proof: ValidityProof, // 160 bytes
@ -201,10 +201,10 @@ pub struct TransferValidityProofData {
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(C)]
pub struct TransferEphemeralState {
pub spendable_comm_verification: PodPedersenComm, // 32 bytes
pub x: PodScalar, // 32 bytes
pub z: PodScalar, // 32 bytes
pub t_x_blinding: PodScalar, // 32 bytes
pub spendable_comm_verification: pod::PedersenComm, // 32 bytes
pub x: pod::Scalar, // 32 bytes
pub z: pod::Scalar, // 32 bytes
pub t_x_blinding: pod::Scalar, // 32 bytes
}
#[cfg(not(target_arch = "bpf"))]
@ -222,8 +222,9 @@ impl Verifiable for TransferValidityProofData {
/// Just a grouping struct for the two proofs that are needed for a transfer instruction. The two
/// proofs have to be generated together as they share joint data.
#[cfg(not(target_arch = "bpf"))]
pub struct TransferProofs {
pub range_proof: PodRangeProof128,
pub range_proof: pod::RangeProof128,
pub validity_proof: ValidityProof,
}
@ -330,15 +331,15 @@ impl TransferProofs {
#[repr(C)]
pub struct ValidityProof {
// Proof component for the spendable ciphertext components: R
pub R: PodCompressedRistretto, // 32 bytes
pub R: pod::CompressedRistretto, // 32 bytes
// Proof component for the spendable ciphertext components: z
pub z: PodScalar, // 32 bytes
pub z: pod::Scalar, // 32 bytes
// Proof component for the transaction amount components: T_src
pub T_joint: PodCompressedRistretto, // 32 bytes
pub T_joint: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_1
pub T_1: PodCompressedRistretto, // 32 bytes
pub T_1: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_2
pub T_2: PodCompressedRistretto, // 32 bytes
pub T_2: pod::CompressedRistretto, // 32 bytes
}
#[allow(non_snake_case)]
@ -454,26 +455,26 @@ impl ValidityProof {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferPubKeys {
pub source_pk: PodElGamalPK, // 32 bytes
pub dest_pk: PodElGamalPK, // 32 bytes
pub auditor_pk: PodElGamalPK, // 32 bytes
pub source_pk: pod::ElGamalPK, // 32 bytes
pub dest_pk: pod::ElGamalPK, // 32 bytes
pub auditor_pk: pod::ElGamalPK, // 32 bytes
}
/// The transfer amount commitments needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferComms {
pub lo: PodPedersenComm, // 32 bytes
pub hi: PodPedersenComm, // 32 bytes
pub lo: pod::PedersenComm, // 32 bytes
pub hi: pod::PedersenComm, // 32 bytes
}
/// The decryption handles needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferHandles {
pub source: PodPedersenDecHandle, // 32 bytes
pub dest: PodPedersenDecHandle, // 32 bytes
pub auditor: PodPedersenDecHandle, // 32 bytes
pub source: pod::PedersenDecHandle, // 32 bytes
pub dest: pod::PedersenDecHandle, // 32 bytes
pub auditor: pod::PedersenDecHandle, // 32 bytes
}
/// Split u64 number into two u32 numbers
@ -502,10 +503,11 @@ pub fn combine_u32_handles(
handle_lo + handle_hi * Scalar::from(TWO_32)
}
#[cfg(not(target_arch = "bpf"))]
/*
pub fn combine_u32_ciphertexts(ct_lo: ElGamalCT, ct_hi: ElGamalCT) -> ElGamalCT {
ct_lo + ct_hi * Scalar::from(TWO_32)
}
*/
#[cfg(test)]
mod test {

View File

@ -1,5 +1,5 @@
use {
crate::pod::*,
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_arch = "bpf"))]
@ -34,16 +34,16 @@ use {
#[repr(C)]
pub struct UpdateAccountPkData {
/// Current ElGamal encryption key
pub current_pk: PodElGamalPK, // 32 bytes
pub current_pk: pod::ElGamalPK, // 32 bytes
/// Current encrypted available balance
pub current_ct: PodElGamalCT, // 64 bytes
pub current_ct: pod::ElGamalCT, // 64 bytes
/// New ElGamal encryption key
pub new_pk: PodElGamalPK, // 32 bytes
pub new_pk: pod::ElGamalPK, // 32 bytes
/// New encrypted available balance
pub new_ct: PodElGamalCT, // 64 bytes
pub new_ct: pod::ElGamalCT, // 64 bytes
/// Proof that the current and new ciphertexts are consistent
pub proof: UpdateAccountPkProof, // 160 bytes
@ -89,11 +89,11 @@ impl Verifiable for UpdateAccountPkData {
#[repr(C)]
#[allow(non_snake_case)]
pub struct UpdateAccountPkProof {
pub R_0: PodCompressedRistretto, // 32 bytes
pub R_1: PodCompressedRistretto, // 32 bytes
pub z_sk_0: PodScalar, // 32 bytes
pub z_sk_1: PodScalar, // 32 bytes
pub z_x: PodScalar, // 32 bytes
pub R_0: pod::CompressedRistretto, // 32 bytes
pub R_1: pod::CompressedRistretto, // 32 bytes
pub z_sk_0: pod::Scalar, // 32 bytes
pub z_sk_1: pod::Scalar, // 32 bytes
pub z_x: pod::Scalar, // 32 bytes
}
#[allow(non_snake_case)]
@ -233,7 +233,7 @@ mod test {
// A zeroed cipehrtext should be considered as an account balance of 0
let balance: u64 = 0;
let zeroed_ct_as_current_ct: ElGamalCT = PodElGamalCT::zeroed().try_into().unwrap();
let zeroed_ct_as_current_ct: ElGamalCT = pod::ElGamalCT::zeroed().try_into().unwrap();
let new_ct: ElGamalCT = new_pk.encrypt(balance);
let proof = UpdateAccountPkProof::new(
balance,
@ -244,8 +244,8 @@ mod test {
);
assert!(proof.verify(&zeroed_ct_as_current_ct, &new_ct).is_ok());
let current_ct: ElGamalCT = PodElGamalCT::zeroed().try_into().unwrap();
let zeroed_ct_as_new_ct: ElGamalCT = PodElGamalCT::zeroed().try_into().unwrap();
let current_ct: ElGamalCT = pod::ElGamalCT::zeroed().try_into().unwrap();
let zeroed_ct_as_new_ct: ElGamalCT = pod::ElGamalCT::zeroed().try_into().unwrap();
let proof = UpdateAccountPkProof::new(
balance,
&current_sk,
@ -255,8 +255,8 @@ mod test {
);
assert!(proof.verify(&current_ct, &zeroed_ct_as_new_ct).is_ok());
let zeroed_ct_as_current_ct: ElGamalCT = PodElGamalCT::zeroed().try_into().unwrap();
let zeroed_ct_as_new_ct: ElGamalCT = PodElGamalCT::zeroed().try_into().unwrap();
let zeroed_ct_as_current_ct: ElGamalCT = pod::ElGamalCT::zeroed().try_into().unwrap();
let zeroed_ct_as_new_ct: ElGamalCT = pod::ElGamalCT::zeroed().try_into().unwrap();
let proof = UpdateAccountPkProof::new(
balance,
&current_sk,

View File

@ -1,5 +1,5 @@
use {
crate::pod::*,
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_arch = "bpf"))]
@ -32,7 +32,7 @@ use {
pub struct WithdrawData {
/// The source account available balance *after* the withdraw (encrypted by
/// `source_pk`
pub final_balance_ct: PodElGamalCT, // 64 bytes
pub final_balance_ct: pod::ElGamalCT, // 64 bytes
/// Proof that the account is solvent
pub proof: WithdrawProof, // 736 bytes
@ -81,11 +81,11 @@ impl Verifiable for WithdrawData {
#[allow(non_snake_case)]
pub struct WithdrawProof {
/// Wrapper for range proof: R component
pub R: PodCompressedRistretto, // 32 bytes
pub R: pod::CompressedRistretto, // 32 bytes
/// Wrapper for range proof: z component
pub z: PodScalar, // 32 bytes
pub z: pod::Scalar, // 32 bytes
/// Associated range proof
pub range_proof: PodRangeProof64, // 672 bytes
pub range_proof: pod::RangeProof64, // 672 bytes
}
#[allow(non_snake_case)]

View File

@ -14,6 +14,6 @@ mod range_proof;
mod transcript;
mod instruction;
pub mod pod;
pub mod zk_token_elgamal;
pub mod zk_token_proof_instruction;
pub mod zk_token_proof_program;

View File

@ -1,596 +0,0 @@
//! Plain Old Data wrappers for types that need to be sent over the wire
use bytemuck::{Pod, Zeroable};
#[cfg(not(target_arch = "bpf"))]
use {
crate::{
encryption::elgamal::{ElGamalCT, ElGamalPK},
encryption::pedersen::{PedersenComm, PedersenDecHandle},
errors::ProofError,
range_proof::RangeProof,
},
curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_COMPRESSED, ristretto::CompressedRistretto, scalar::Scalar,
},
std::{
convert::{TryFrom, TryInto},
fmt,
},
};
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PodScalar([u8; 32]);
#[cfg(not(target_arch = "bpf"))]
impl From<Scalar> for PodScalar {
fn from(scalar: Scalar) -> Self {
Self(scalar.to_bytes())
}
}
#[cfg(not(target_arch = "bpf"))]
impl From<PodScalar> for Scalar {
fn from(pod: PodScalar) -> Self {
Scalar::from_bits(pod.0)
}
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct PodCompressedRistretto([u8; 32]);
#[cfg(not(target_arch = "bpf"))]
impl From<CompressedRistretto> for PodCompressedRistretto {
fn from(cr: CompressedRistretto) -> Self {
Self(cr.to_bytes())
}
}
#[cfg(not(target_arch = "bpf"))]
impl From<PodCompressedRistretto> for CompressedRistretto {
fn from(pod: PodCompressedRistretto) -> Self {
Self(pod.0)
}
}
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PodElGamalCT([u8; 64]);
#[cfg(not(target_arch = "bpf"))]
impl From<ElGamalCT> for PodElGamalCT {
fn from(ct: ElGamalCT) -> Self {
Self(ct.to_bytes())
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<PodElGamalCT> for ElGamalCT {
type Error = ProofError;
fn try_from(pod: PodElGamalCT) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData)
}
}
impl From<(PodPedersenComm, PodPedersenDecHandle)> for PodElGamalCT {
fn from((pod_comm, pod_decrypt_handle): (PodPedersenComm, PodPedersenDecHandle)) -> Self {
let mut buf = [0_u8; 64];
buf[..32].copy_from_slice(&pod_comm.0);
buf[32..].copy_from_slice(&pod_decrypt_handle.0);
PodElGamalCT(buf)
}
}
#[cfg(not(target_arch = "bpf"))]
impl fmt::Debug for PodElGamalCT {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PodElGamalPK([u8; 32]);
#[cfg(not(target_arch = "bpf"))]
impl From<ElGamalPK> for PodElGamalPK {
fn from(pk: ElGamalPK) -> Self {
Self(pk.to_bytes())
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<PodElGamalPK> for ElGamalPK {
type Error = ProofError;
fn try_from(pod: PodElGamalPK) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData)
}
}
#[cfg(not(target_arch = "bpf"))]
impl fmt::Debug for PodElGamalPK {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PodPedersenComm([u8; 32]);
#[cfg(not(target_arch = "bpf"))]
impl From<PedersenComm> for PodPedersenComm {
fn from(comm: PedersenComm) -> Self {
Self(comm.to_bytes())
}
}
// For proof verification, interpret PodPedersenComm directly as CompressedRistretto
#[cfg(not(target_arch = "bpf"))]
impl From<PodPedersenComm> for CompressedRistretto {
fn from(pod: PodPedersenComm) -> Self {
Self(pod.0)
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<PodPedersenComm> for PedersenComm {
type Error = ProofError;
fn try_from(pod: PodPedersenComm) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData)
}
}
#[cfg(not(target_arch = "bpf"))]
impl fmt::Debug for PodPedersenComm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PodPedersenDecHandle([u8; 32]);
#[cfg(not(target_arch = "bpf"))]
impl From<PedersenDecHandle> for PodPedersenDecHandle {
fn from(handle: PedersenDecHandle) -> Self {
Self(handle.to_bytes())
}
}
// For proof verification, interpret PodPedersenDecHandle as CompressedRistretto
#[cfg(not(target_arch = "bpf"))]
impl From<PodPedersenDecHandle> for CompressedRistretto {
fn from(pod: PodPedersenDecHandle) -> Self {
Self(pod.0)
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<PodPedersenDecHandle> for PedersenDecHandle {
type Error = ProofError;
fn try_from(pod: PodPedersenDecHandle) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData)
}
}
#[cfg(not(target_arch = "bpf"))]
impl fmt::Debug for PodPedersenDecHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
/// Serialization of range proofs for 64-bit numbers (for `Withdraw` instruction)
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct PodRangeProof64([u8; 672]);
// `PodRangeProof64` is a Pod and Zeroable.
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
unsafe impl Zeroable for PodRangeProof64 {}
unsafe impl Pod for PodRangeProof64 {}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<RangeProof> for PodRangeProof64 {
type Error = ProofError;
fn try_from(proof: RangeProof) -> Result<Self, Self::Error> {
if proof.ipp_proof.serialized_size() != 448 {
return Err(ProofError::VerificationError);
}
let mut buf = [0_u8; 672];
buf[..32].copy_from_slice(proof.A.as_bytes());
buf[32..64].copy_from_slice(proof.S.as_bytes());
buf[64..96].copy_from_slice(proof.T_1.as_bytes());
buf[96..128].copy_from_slice(proof.T_2.as_bytes());
buf[128..160].copy_from_slice(proof.t_x.as_bytes());
buf[160..192].copy_from_slice(proof.t_x_blinding.as_bytes());
buf[192..224].copy_from_slice(proof.e_blinding.as_bytes());
buf[224..672].copy_from_slice(&proof.ipp_proof.to_bytes());
Ok(PodRangeProof64(buf))
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<PodRangeProof64> for RangeProof {
type Error = ProofError;
fn try_from(pod: PodRangeProof64) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
/// Serialization of range proofs for 128-bit numbers (for `TransferRangeProof` instruction)
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct PodRangeProof128([u8; 736]);
// `PodRangeProof128` is a Pod and Zeroable.
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
unsafe impl Zeroable for PodRangeProof128 {}
unsafe impl Pod for PodRangeProof128 {}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<RangeProof> for PodRangeProof128 {
type Error = ProofError;
fn try_from(proof: RangeProof) -> Result<Self, Self::Error> {
if proof.ipp_proof.serialized_size() != 512 {
return Err(ProofError::VerificationError);
}
let mut buf = [0_u8; 736];
buf[..32].copy_from_slice(proof.A.as_bytes());
buf[32..64].copy_from_slice(proof.S.as_bytes());
buf[64..96].copy_from_slice(proof.T_1.as_bytes());
buf[96..128].copy_from_slice(proof.T_2.as_bytes());
buf[128..160].copy_from_slice(proof.t_x.as_bytes());
buf[160..192].copy_from_slice(proof.t_x_blinding.as_bytes());
buf[192..224].copy_from_slice(proof.e_blinding.as_bytes());
buf[224..736].copy_from_slice(&proof.ipp_proof.to_bytes());
Ok(PodRangeProof128(buf))
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<PodRangeProof128> for RangeProof {
type Error = ProofError;
fn try_from(pod: PodRangeProof128) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
pub struct PodElGamalArithmetic;
#[cfg(not(target_arch = "bpf"))]
impl PodElGamalArithmetic {
const TWO_32: u64 = 4294967296;
// On input two scalars x0, x1 and two ciphertexts ct0, ct1,
// returns `Some(x0*ct0 + x1*ct1)` or `None` if the input was invalid
fn add_pod_ciphertexts(
scalar_0: Scalar,
pod_ct_0: PodElGamalCT,
scalar_1: Scalar,
pod_ct_1: PodElGamalCT,
) -> Option<PodElGamalCT> {
let ct_0: ElGamalCT = pod_ct_0.try_into().ok()?;
let ct_1: ElGamalCT = pod_ct_1.try_into().ok()?;
let ct_sum = ct_0 * scalar_0 + ct_1 * scalar_1;
Some(PodElGamalCT::from(ct_sum))
}
fn combine_lo_hi(pod_ct_lo: PodElGamalCT, pod_ct_hi: PodElGamalCT) -> Option<PodElGamalCT> {
Self::add_pod_ciphertexts(
Scalar::one(),
pod_ct_lo,
Scalar::from(Self::TWO_32),
pod_ct_hi,
)
}
pub fn add(pod_ct_0: PodElGamalCT, pod_ct_1: PodElGamalCT) -> Option<PodElGamalCT> {
Self::add_pod_ciphertexts(Scalar::one(), pod_ct_0, Scalar::one(), pod_ct_1)
}
pub fn add_with_lo_hi(
pod_ct_0: PodElGamalCT,
pod_ct_1_lo: PodElGamalCT,
pod_ct_1_hi: PodElGamalCT,
) -> Option<PodElGamalCT> {
let pod_ct_1 = Self::combine_lo_hi(pod_ct_1_lo, pod_ct_1_hi)?;
Self::add_pod_ciphertexts(Scalar::one(), pod_ct_0, Scalar::one(), pod_ct_1)
}
pub fn subtract(pod_ct_0: PodElGamalCT, pod_ct_1: PodElGamalCT) -> Option<PodElGamalCT> {
Self::add_pod_ciphertexts(Scalar::one(), pod_ct_0, -Scalar::one(), pod_ct_1)
}
pub fn subtract_with_lo_hi(
pod_ct_0: PodElGamalCT,
pod_ct_1_lo: PodElGamalCT,
pod_ct_1_hi: PodElGamalCT,
) -> Option<PodElGamalCT> {
let pod_ct_1 = Self::combine_lo_hi(pod_ct_1_lo, pod_ct_1_hi)?;
Self::add_pod_ciphertexts(Scalar::one(), pod_ct_0, -Scalar::one(), pod_ct_1)
}
pub fn add_to(pod_ct: PodElGamalCT, amount: u64) -> Option<PodElGamalCT> {
let mut amount_as_pod_ct = [0_u8; 64];
amount_as_pod_ct[..32].copy_from_slice(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
Self::add_pod_ciphertexts(
Scalar::one(),
pod_ct,
Scalar::from(amount),
PodElGamalCT(amount_as_pod_ct),
)
}
pub fn subtract_from(pod_ct: PodElGamalCT, amount: u64) -> Option<PodElGamalCT> {
let mut amount_as_pod_ct = [0_u8; 64];
amount_as_pod_ct[..32].copy_from_slice(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
Self::add_pod_ciphertexts(
Scalar::one(),
pod_ct,
-Scalar::from(amount),
PodElGamalCT(amount_as_pod_ct),
)
}
}
#[cfg(target_arch = "bpf")]
#[allow(unused_variables)]
impl PodElGamalArithmetic {
pub fn add(pod_ct_0: PodElGamalCT, pod_ct_1: PodElGamalCT) -> Option<PodElGamalCT> {
None
}
pub fn add_with_lo_hi(
pod_ct_0: PodElGamalCT,
pod_ct_1_lo: PodElGamalCT,
pod_ct_1_hi: PodElGamalCT,
) -> Option<PodElGamalCT> {
None
}
pub fn subtract(pod_ct_0: PodElGamalCT, pod_ct_1: PodElGamalCT) -> Option<PodElGamalCT> {
None
}
pub fn subtract_with_lo_hi(
pod_ct_0: PodElGamalCT,
pod_ct_1_lo: PodElGamalCT,
pod_ct_1_hi: PodElGamalCT,
) -> Option<PodElGamalCT> {
None
}
pub fn add_to(pod_ct: PodElGamalCT, amount: u64) -> Option<PodElGamalCT> {
None
}
pub fn subtract_from(pod_ct: PodElGamalCT, amount: u64) -> Option<PodElGamalCT> {
None
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::encryption::{
elgamal::{ElGamal, ElGamalCT},
pedersen::{Pedersen, PedersenOpen},
},
crate::instruction::transfer::split_u64_into_u32,
merlin::Transcript,
rand::rngs::OsRng,
std::convert::TryInto,
};
#[test]
fn test_zero_ct() {
let spendable_balance = PodElGamalCT::zeroed();
let spendable_ct: ElGamalCT = spendable_balance.try_into().unwrap();
// spendable_ct should be an encryption of 0 for any public key when
// `PedersenOpen::default()` is used
let (pk, _) = ElGamal::keygen();
let balance: u64 = 0;
assert_eq!(
spendable_ct,
pk.encrypt_with(balance, &PedersenOpen::default())
);
// homomorphism should work like any other ciphertext
let open = PedersenOpen::random(&mut OsRng);
let transfer_amount_ct = pk.encrypt_with(55_u64, &open);
let transfer_amount_pod: PodElGamalCT = transfer_amount_ct.into();
let sum = PodElGamalArithmetic::add(spendable_balance, transfer_amount_pod).unwrap();
let expected: PodElGamalCT = pk.encrypt_with(55_u64, &open).into();
assert_eq!(expected, sum);
}
#[test]
fn test_add_to() {
let spendable_balance = PodElGamalCT::zeroed();
let added_ct = PodElGamalArithmetic::add_to(spendable_balance, 55).unwrap();
let (pk, _) = ElGamal::keygen();
let expected: PodElGamalCT = pk.encrypt_with(55_u64, &PedersenOpen::default()).into();
assert_eq!(expected, added_ct);
}
#[test]
fn test_subtract_from() {
let amount = 77_u64;
let (pk, _) = ElGamal::keygen();
let open = PedersenOpen::random(&mut OsRng);
let encrypted_amount: PodElGamalCT = pk.encrypt_with(amount, &open).into();
let subtracted_ct = PodElGamalArithmetic::subtract_from(encrypted_amount, 55).unwrap();
let expected: PodElGamalCT = pk.encrypt_with(22_u64, &open).into();
assert_eq!(expected, subtracted_ct);
}
#[test]
fn test_pod_range_proof_64() {
let (comm, open) = Pedersen::commit(55_u64);
let mut transcript_create = Transcript::new(b"Test");
let mut transcript_verify = Transcript::new(b"Test");
let proof = RangeProof::create(vec![55], vec![64], vec![&open], &mut transcript_create);
let proof_serialized: PodRangeProof64 = proof.try_into().unwrap();
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
assert!(proof_deserialized
.verify(
vec![&comm.get_point().compress()],
vec![64],
&mut transcript_verify
)
.is_ok());
// should fail to serialize to PodRangeProof128
let proof = RangeProof::create(vec![55], vec![64], vec![&open], &mut transcript_create);
assert!(TryInto::<PodRangeProof128>::try_into(proof).is_err());
}
#[test]
fn test_pod_range_proof_128() {
let (comm_1, open_1) = Pedersen::commit(55_u64);
let (comm_2, open_2) = Pedersen::commit(77_u64);
let (comm_3, open_3) = Pedersen::commit(99_u64);
let mut transcript_create = Transcript::new(b"Test");
let mut transcript_verify = Transcript::new(b"Test");
let proof = RangeProof::create(
vec![55, 77, 99],
vec![64, 32, 32],
vec![&open_1, &open_2, &open_3],
&mut transcript_create,
);
let comm_1_point = comm_1.get_point().compress();
let comm_2_point = comm_2.get_point().compress();
let comm_3_point = comm_3.get_point().compress();
let proof_serialized: PodRangeProof128 = proof.try_into().unwrap();
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
assert!(proof_deserialized
.verify(
vec![&comm_1_point, &comm_2_point, &comm_3_point],
vec![64, 32, 32],
&mut transcript_verify,
)
.is_ok());
// should fail to serialize to PodRangeProof64
let proof = RangeProof::create(
vec![55, 77, 99],
vec![64, 32, 32],
vec![&open_1, &open_2, &open_3],
&mut transcript_create,
);
assert!(TryInto::<PodRangeProof64>::try_into(proof).is_err());
}
#[test]
fn test_transfer_arithmetic() {
// setup
// transfer amount
let transfer_amount: u64 = 55;
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
// generate public keys
let (source_pk, _) = ElGamal::keygen();
let (dest_pk, _) = ElGamal::keygen();
let (auditor_pk, _) = ElGamal::keygen();
// commitments associated with TransferRangeProof
let (comm_lo, open_lo) = Pedersen::commit(amount_lo);
let (comm_hi, open_hi) = Pedersen::commit(amount_hi);
let comm_lo: PodPedersenComm = comm_lo.into();
let comm_hi: PodPedersenComm = comm_hi.into();
// decryption handles associated with TransferValidityProof
let handle_source_lo: PodPedersenDecHandle = source_pk.gen_decrypt_handle(&open_lo).into();
let handle_dest_lo: PodPedersenDecHandle = dest_pk.gen_decrypt_handle(&open_lo).into();
let _handle_auditor_lo: PodPedersenDecHandle =
auditor_pk.gen_decrypt_handle(&open_lo).into();
let handle_source_hi: PodPedersenDecHandle = source_pk.gen_decrypt_handle(&open_hi).into();
let handle_dest_hi: PodPedersenDecHandle = dest_pk.gen_decrypt_handle(&open_hi).into();
let _handle_auditor_hi: PodPedersenDecHandle =
auditor_pk.gen_decrypt_handle(&open_hi).into();
// source spendable and recipient pending
let source_open = PedersenOpen::random(&mut OsRng);
let dest_open = PedersenOpen::random(&mut OsRng);
let source_spendable_ct: PodElGamalCT = source_pk.encrypt_with(77_u64, &source_open).into();
let dest_pending_ct: PodElGamalCT = dest_pk.encrypt_with(77_u64, &dest_open).into();
// program arithmetic for the source account
// 1. Combine commitments and handles
let source_lo_ct: PodElGamalCT = (comm_lo, handle_source_lo).into();
let source_hi_ct: PodElGamalCT = (comm_hi, handle_source_hi).into();
// 2. Combine lo and hi ciphertexts
let source_combined_ct =
PodElGamalArithmetic::combine_lo_hi(source_lo_ct, source_hi_ct).unwrap();
// 3. Subtract from available balance
let final_source_spendable =
PodElGamalArithmetic::subtract(source_spendable_ct, source_combined_ct).unwrap();
// test
let final_source_open = source_open
- (open_lo.clone() + open_hi.clone() * Scalar::from(PodElGamalArithmetic::TWO_32));
let expected_source: PodElGamalCT =
source_pk.encrypt_with(22_u64, &final_source_open).into();
assert_eq!(expected_source, final_source_spendable);
// same for the destination account
// 1. Combine commitments and handles
let dest_lo_ct: PodElGamalCT = (comm_lo, handle_dest_lo).into();
let dest_hi_ct: PodElGamalCT = (comm_hi, handle_dest_hi).into();
// 2. Combine lo and hi ciphertexts
let dest_combined_ct = PodElGamalArithmetic::combine_lo_hi(dest_lo_ct, dest_hi_ct).unwrap();
// 3. Add to pending balance
let final_dest_pending =
PodElGamalArithmetic::add(dest_pending_ct, dest_combined_ct).unwrap();
let final_dest_open =
dest_open + (open_lo + open_hi * Scalar::from(PodElGamalArithmetic::TWO_32));
let expected_dest_ct: PodElGamalCT = dest_pk.encrypt_with(132_u64, &final_dest_open).into();
assert_eq!(expected_dest_ct, final_dest_pending);
}
}

View File

@ -0,0 +1,292 @@
use super::pod;
pub use target_arch::*;
impl From<(pod::PedersenComm, pod::PedersenDecHandle)> for pod::ElGamalCT {
fn from((comm, decrypt_handle): (pod::PedersenComm, pod::PedersenDecHandle)) -> Self {
let mut buf = [0_u8; 64];
buf[..32].copy_from_slice(&comm.0);
buf[32..].copy_from_slice(&decrypt_handle.0);
pod::ElGamalCT(buf)
}
}
#[cfg(not(target_arch = "bpf"))]
mod target_arch {
use {
super::pod,
crate::{
encryption::elgamal::{ElGamalCT, ElGamalPK},
encryption::pedersen::{PedersenComm, PedersenDecHandle},
errors::ProofError,
range_proof::RangeProof,
},
curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar},
std::{convert::TryFrom, fmt},
};
impl From<Scalar> for pod::Scalar {
fn from(scalar: Scalar) -> Self {
Self(scalar.to_bytes())
}
}
impl From<pod::Scalar> for Scalar {
fn from(pod: pod::Scalar) -> Self {
Scalar::from_bits(pod.0)
}
}
impl From<ElGamalCT> for pod::ElGamalCT {
fn from(ct: ElGamalCT) -> Self {
Self(ct.to_bytes())
}
}
impl TryFrom<pod::ElGamalCT> for ElGamalCT {
type Error = ProofError;
fn try_from(ct: pod::ElGamalCT) -> Result<Self, Self::Error> {
Self::from_bytes(&ct.0).ok_or(ProofError::InconsistentCTData)
}
}
impl fmt::Debug for pod::ElGamalCT {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl From<ElGamalPK> for pod::ElGamalPK {
fn from(pk: ElGamalPK) -> Self {
Self(pk.to_bytes())
}
}
impl TryFrom<pod::ElGamalPK> for ElGamalPK {
type Error = ProofError;
fn try_from(pk: pod::ElGamalPK) -> Result<Self, Self::Error> {
Self::from_bytes(&pk.0).ok_or(ProofError::InconsistentCTData)
}
}
impl fmt::Debug for pod::ElGamalPK {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl From<CompressedRistretto> for pod::CompressedRistretto {
fn from(cr: CompressedRistretto) -> Self {
Self(cr.to_bytes())
}
}
impl From<pod::CompressedRistretto> for CompressedRistretto {
fn from(pod: pod::CompressedRistretto) -> Self {
Self(pod.0)
}
}
impl From<PedersenComm> for pod::PedersenComm {
fn from(comm: PedersenComm) -> Self {
Self(comm.to_bytes())
}
}
// For proof verification, interpret pod::PedersenComm directly as CompressedRistretto
#[cfg(not(target_arch = "bpf"))]
impl From<pod::PedersenComm> for CompressedRistretto {
fn from(pod: pod::PedersenComm) -> Self {
Self(pod.0)
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<pod::PedersenComm> for PedersenComm {
type Error = ProofError;
fn try_from(pod: pod::PedersenComm) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData)
}
}
#[cfg(not(target_arch = "bpf"))]
impl fmt::Debug for pod::PedersenComm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
#[cfg(not(target_arch = "bpf"))]
impl From<PedersenDecHandle> for pod::PedersenDecHandle {
fn from(handle: PedersenDecHandle) -> Self {
Self(handle.to_bytes())
}
}
// For proof verification, interpret pod::PedersenDecHandle as CompressedRistretto
#[cfg(not(target_arch = "bpf"))]
impl From<pod::PedersenDecHandle> for CompressedRistretto {
fn from(pod: pod::PedersenDecHandle) -> Self {
Self(pod.0)
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<pod::PedersenDecHandle> for PedersenDecHandle {
type Error = ProofError;
fn try_from(pod: pod::PedersenDecHandle) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0).ok_or(ProofError::InconsistentCTData)
}
}
#[cfg(not(target_arch = "bpf"))]
impl fmt::Debug for pod::PedersenDecHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl TryFrom<RangeProof> for pod::RangeProof64 {
type Error = ProofError;
fn try_from(proof: RangeProof) -> Result<Self, Self::Error> {
if proof.ipp_proof.serialized_size() != 448 {
return Err(ProofError::VerificationError);
}
let mut buf = [0_u8; 672];
buf[..32].copy_from_slice(proof.A.as_bytes());
buf[32..64].copy_from_slice(proof.S.as_bytes());
buf[64..96].copy_from_slice(proof.T_1.as_bytes());
buf[96..128].copy_from_slice(proof.T_2.as_bytes());
buf[128..160].copy_from_slice(proof.t_x.as_bytes());
buf[160..192].copy_from_slice(proof.t_x_blinding.as_bytes());
buf[192..224].copy_from_slice(proof.e_blinding.as_bytes());
buf[224..672].copy_from_slice(&proof.ipp_proof.to_bytes());
Ok(pod::RangeProof64(buf))
}
}
impl TryFrom<pod::RangeProof64> for RangeProof {
type Error = ProofError;
fn try_from(pod: pod::RangeProof64) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
#[cfg(not(target_arch = "bpf"))]
impl TryFrom<RangeProof> for pod::RangeProof128 {
type Error = ProofError;
fn try_from(proof: RangeProof) -> Result<Self, Self::Error> {
if proof.ipp_proof.serialized_size() != 512 {
return Err(ProofError::VerificationError);
}
let mut buf = [0_u8; 736];
buf[..32].copy_from_slice(proof.A.as_bytes());
buf[32..64].copy_from_slice(proof.S.as_bytes());
buf[64..96].copy_from_slice(proof.T_1.as_bytes());
buf[96..128].copy_from_slice(proof.T_2.as_bytes());
buf[128..160].copy_from_slice(proof.t_x.as_bytes());
buf[160..192].copy_from_slice(proof.t_x_blinding.as_bytes());
buf[192..224].copy_from_slice(proof.e_blinding.as_bytes());
buf[224..736].copy_from_slice(&proof.ipp_proof.to_bytes());
Ok(pod::RangeProof128(buf))
}
}
impl TryFrom<pod::RangeProof128> for RangeProof {
type Error = ProofError;
fn try_from(pod: pod::RangeProof128) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
}
#[cfg(target_arch = "bpf")]
#[allow(unused_variables)]
mod target_arch {}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{encryption::pedersen::Pedersen, range_proof::RangeProof},
merlin::Transcript,
std::convert::TryInto,
};
#[test]
fn test_pod_range_proof_64() {
let (comm, open) = Pedersen::commit(55_u64);
let mut transcript_create = Transcript::new(b"Test");
let mut transcript_verify = Transcript::new(b"Test");
let proof = RangeProof::create(vec![55], vec![64], vec![&open], &mut transcript_create);
let proof_serialized: pod::RangeProof64 = proof.try_into().unwrap();
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
assert!(proof_deserialized
.verify(
vec![&comm.get_point().compress()],
vec![64],
&mut transcript_verify
)
.is_ok());
// should fail to serialize to pod::RangeProof128
let proof = RangeProof::create(vec![55], vec![64], vec![&open], &mut transcript_create);
assert!(TryInto::<pod::RangeProof128>::try_into(proof).is_err());
}
#[test]
fn test_pod_range_proof_128() {
let (comm_1, open_1) = Pedersen::commit(55_u64);
let (comm_2, open_2) = Pedersen::commit(77_u64);
let (comm_3, open_3) = Pedersen::commit(99_u64);
let mut transcript_create = Transcript::new(b"Test");
let mut transcript_verify = Transcript::new(b"Test");
let proof = RangeProof::create(
vec![55, 77, 99],
vec![64, 32, 32],
vec![&open_1, &open_2, &open_3],
&mut transcript_create,
);
let comm_1_point = comm_1.get_point().compress();
let comm_2_point = comm_2.get_point().compress();
let comm_3_point = comm_3.get_point().compress();
let proof_serialized: pod::RangeProof128 = proof.try_into().unwrap();
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
assert!(proof_deserialized
.verify(
vec![&comm_1_point, &comm_2_point, &comm_3_point],
vec![64, 32, 32],
&mut transcript_verify,
)
.is_ok());
// should fail to serialize to pod::RangeProof64
let proof = RangeProof::create(
vec![55, 77, 99],
vec![64, 32, 32],
vec![&open_1, &open_2, &open_3],
&mut transcript_create,
);
assert!(TryInto::<pod::RangeProof64>::try_into(proof).is_err());
}
}

View File

@ -0,0 +1,3 @@
pub mod convert;
pub mod ops;
pub mod pod;

View File

@ -0,0 +1,272 @@
pub use target_arch::*;
#[cfg(not(target_arch = "bpf"))]
mod target_arch {
use {
crate::{encryption::elgamal::ElGamalCT, zk_token_elgamal::pod},
curve25519_dalek::{constants::RISTRETTO_BASEPOINT_COMPRESSED, scalar::Scalar},
std::convert::TryInto,
};
pub const TWO_32: u64 = 4294967296;
// On input two scalars x0, x1 and two ciphertexts ct0, ct1,
// returns `Some(x0*ct0 + x1*ct1)` or `None` if the input was invalid
fn add_ciphertexts(
scalar_0: Scalar,
ct_0: pod::ElGamalCT,
scalar_1: Scalar,
ct_1: pod::ElGamalCT,
) -> Option<pod::ElGamalCT> {
let ct_0: ElGamalCT = ct_0.try_into().ok()?;
let ct_1: ElGamalCT = ct_1.try_into().ok()?;
let ct_sum = ct_0 * scalar_0 + ct_1 * scalar_1;
Some(pod::ElGamalCT::from(ct_sum))
}
pub(crate) fn combine_lo_hi(
ct_lo: pod::ElGamalCT,
ct_hi: pod::ElGamalCT,
) -> Option<pod::ElGamalCT> {
add_ciphertexts(Scalar::one(), ct_lo, Scalar::from(TWO_32), ct_hi)
}
pub fn add(ct_0: pod::ElGamalCT, ct_1: pod::ElGamalCT) -> Option<pod::ElGamalCT> {
add_ciphertexts(Scalar::one(), ct_0, Scalar::one(), ct_1)
}
pub fn add_with_lo_hi(
ct_0: pod::ElGamalCT,
ct_1_lo: pod::ElGamalCT,
ct_1_hi: pod::ElGamalCT,
) -> Option<pod::ElGamalCT> {
let ct_1 = combine_lo_hi(ct_1_lo, ct_1_hi)?;
add_ciphertexts(Scalar::one(), ct_0, Scalar::one(), ct_1)
}
pub fn subtract(ct_0: pod::ElGamalCT, ct_1: pod::ElGamalCT) -> Option<pod::ElGamalCT> {
add_ciphertexts(Scalar::one(), ct_0, -Scalar::one(), ct_1)
}
pub fn subtract_with_lo_hi(
ct_0: pod::ElGamalCT,
ct_1_lo: pod::ElGamalCT,
ct_1_hi: pod::ElGamalCT,
) -> Option<pod::ElGamalCT> {
let ct_1 = combine_lo_hi(ct_1_lo, ct_1_hi)?;
add_ciphertexts(Scalar::one(), ct_0, -Scalar::one(), ct_1)
}
pub fn add_to(ct: pod::ElGamalCT, amount: u64) -> Option<pod::ElGamalCT> {
let mut amount_as_ct = [0_u8; 64];
amount_as_ct[..32].copy_from_slice(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
add_ciphertexts(
Scalar::one(),
ct,
Scalar::from(amount),
pod::ElGamalCT(amount_as_ct),
)
}
pub fn subtract_from(ct: pod::ElGamalCT, amount: u64) -> Option<pod::ElGamalCT> {
let mut amount_as_ct = [0_u8; 64];
amount_as_ct[..32].copy_from_slice(RISTRETTO_BASEPOINT_COMPRESSED.as_bytes());
add_ciphertexts(
Scalar::one(),
ct,
-Scalar::from(amount),
pod::ElGamalCT(amount_as_ct),
)
}
}
#[cfg(target_arch = "bpf")]
#[allow(unused_variables)]
mod target_arch {
use crate::zk_token_elgamal::pod;
pub fn add(ct_0: pod::ElGamalCT, ct_1: pod::ElGamalCT) -> Option<pod::ElGamalCT> {
None
}
pub fn add_with_lo_hi(
ct_0: pod::ElGamalCT,
ct_1_lo: pod::ElGamalCT,
ct_1_hi: pod::ElGamalCT,
) -> Option<pod::ElGamalCT> {
None
}
pub fn subtract(ct_0: pod::ElGamalCT, ct_1: pod::ElGamalCT) -> Option<pod::ElGamalCT> {
None
}
pub fn subtract_with_lo_hi(
ct_0: pod::ElGamalCT,
ct_1_lo: pod::ElGamalCT,
ct_1_hi: pod::ElGamalCT,
) -> Option<pod::ElGamalCT> {
None
}
pub fn add_to(ct: pod::ElGamalCT, amount: u64) -> Option<pod::ElGamalCT> {
None
}
pub fn subtract_from(ct: pod::ElGamalCT, amount: u64) -> Option<pod::ElGamalCT> {
None
}
}
#[cfg(test)]
mod tests {
use {
crate::{
encryption::{
elgamal::{ElGamal, ElGamalCT},
pedersen::{Pedersen, PedersenOpen},
},
zk_token_elgamal::{ops, pod},
},
bytemuck::Zeroable,
curve25519_dalek::scalar::Scalar,
rand::rngs::OsRng,
std::convert::TryInto,
};
#[test]
fn test_zero_ct() {
let spendable_balance = pod::ElGamalCT::zeroed();
let spendable_ct: ElGamalCT = spendable_balance.try_into().unwrap();
// spendable_ct should be an encryption of 0 for any public key when
// `PedersenOpen::default()` is used
let (pk, _) = ElGamal::keygen();
let balance: u64 = 0;
assert_eq!(
spendable_ct,
pk.encrypt_with(balance, &PedersenOpen::default())
);
// homomorphism should work like any other ciphertext
let open = PedersenOpen::random(&mut OsRng);
let transfer_amount_ct = pk.encrypt_with(55_u64, &open);
let transfer_amount_pod: pod::ElGamalCT = transfer_amount_ct.into();
let sum = ops::add(spendable_balance, transfer_amount_pod).unwrap();
let expected: pod::ElGamalCT = pk.encrypt_with(55_u64, &open).into();
assert_eq!(expected, sum);
}
#[test]
fn test_add_to() {
let spendable_balance = pod::ElGamalCT::zeroed();
let added_ct = ops::add_to(spendable_balance, 55).unwrap();
let (pk, _) = ElGamal::keygen();
let expected: pod::ElGamalCT = pk.encrypt_with(55_u64, &PedersenOpen::default()).into();
assert_eq!(expected, added_ct);
}
#[test]
fn test_subtract_from() {
let amount = 77_u64;
let (pk, _) = ElGamal::keygen();
let open = PedersenOpen::random(&mut OsRng);
let encrypted_amount: pod::ElGamalCT = pk.encrypt_with(amount, &open).into();
let subtracted_ct = ops::subtract_from(encrypted_amount, 55).unwrap();
let expected: pod::ElGamalCT = pk.encrypt_with(22_u64, &open).into();
assert_eq!(expected, subtracted_ct);
}
/// Split u64 number into two u32 numbers
fn split_u64_into_u32(amt: u64) -> (u32, u32) {
let lo = amt as u32;
let hi = (amt >> 32) as u32;
(lo, hi)
}
#[test]
fn test_transfer_arithmetic() {
// transfer amount
let transfer_amount: u64 = 55;
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
// generate public keys
let (source_pk, _) = ElGamal::keygen();
let (dest_pk, _) = ElGamal::keygen();
let (auditor_pk, _) = ElGamal::keygen();
// commitments associated with TransferRangeProof
let (comm_lo, open_lo) = Pedersen::commit(amount_lo);
let (comm_hi, open_hi) = Pedersen::commit(amount_hi);
let comm_lo: pod::PedersenComm = comm_lo.into();
let comm_hi: pod::PedersenComm = comm_hi.into();
// decryption handles associated with TransferValidityProof
let handle_source_lo: pod::PedersenDecHandle =
source_pk.gen_decrypt_handle(&open_lo).into();
let handle_dest_lo: pod::PedersenDecHandle = dest_pk.gen_decrypt_handle(&open_lo).into();
let _handle_auditor_lo: pod::PedersenDecHandle =
auditor_pk.gen_decrypt_handle(&open_lo).into();
let handle_source_hi: pod::PedersenDecHandle =
source_pk.gen_decrypt_handle(&open_hi).into();
let handle_dest_hi: pod::PedersenDecHandle = dest_pk.gen_decrypt_handle(&open_hi).into();
let _handle_auditor_hi: pod::PedersenDecHandle =
auditor_pk.gen_decrypt_handle(&open_hi).into();
// source spendable and recipient pending
let source_open = PedersenOpen::random(&mut OsRng);
let dest_open = PedersenOpen::random(&mut OsRng);
let source_spendable_ct: pod::ElGamalCT =
source_pk.encrypt_with(77_u64, &source_open).into();
let dest_pending_ct: pod::ElGamalCT = dest_pk.encrypt_with(77_u64, &dest_open).into();
// program arithmetic for the source account
// 1. Combine commitments and handles
let source_lo_ct: pod::ElGamalCT = (comm_lo, handle_source_lo).into();
let source_hi_ct: pod::ElGamalCT = (comm_hi, handle_source_hi).into();
// 2. Combine lo and hi ciphertexts
let source_combined_ct = ops::combine_lo_hi(source_lo_ct, source_hi_ct).unwrap();
// 3. Subtract from available balance
let final_source_spendable =
ops::subtract(source_spendable_ct, source_combined_ct).unwrap();
// test
let final_source_open =
source_open - (open_lo.clone() + open_hi.clone() * Scalar::from(ops::TWO_32));
let expected_source: pod::ElGamalCT =
source_pk.encrypt_with(22_u64, &final_source_open).into();
assert_eq!(expected_source, final_source_spendable);
// same for the destination account
// 1. Combine commitments and handles
let dest_lo_ct: pod::ElGamalCT = (comm_lo, handle_dest_lo).into();
let dest_hi_ct: pod::ElGamalCT = (comm_hi, handle_dest_hi).into();
// 2. Combine lo and hi ciphertexts
let dest_combined_ct = ops::combine_lo_hi(dest_lo_ct, dest_hi_ct).unwrap();
// 3. Add to pending balance
let final_dest_pending = ops::add(dest_pending_ct, dest_combined_ct).unwrap();
let final_dest_open = dest_open + (open_lo + open_hi * Scalar::from(ops::TWO_32));
let expected_dest_ct: pod::ElGamalCT =
dest_pk.encrypt_with(132_u64, &final_dest_open).into();
assert_eq!(expected_dest_ct, final_dest_pending);
}
}

View File

@ -0,0 +1,45 @@
use bytemuck::{Pod, Zeroable};
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct Scalar(pub [u8; 32]);
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct CompressedRistretto(pub [u8; 32]);
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct ElGamalCT(pub [u8; 64]);
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct ElGamalPK(pub [u8; 32]);
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PedersenComm(pub [u8; 32]);
#[derive(Clone, Copy, Pod, Zeroable, PartialEq)]
#[repr(transparent)]
pub struct PedersenDecHandle(pub [u8; 32]);
/// Serialization of range proofs for 64-bit numbers (for `Withdraw` instruction)
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RangeProof64(pub [u8; 672]);
// `PodRangeProof64` is a Pod and Zeroable.
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
unsafe impl Zeroable for RangeProof64 {}
unsafe impl Pod for RangeProof64 {}
/// Serialization of range proofs for 128-bit numbers (for `TransferRangeProof` instruction)
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RangeProof128(pub [u8; 736]);
// `PodRangeProof128` is a Pod and Zeroable.
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
unsafe impl Zeroable for RangeProof128 {}
unsafe impl Pod for RangeProof128 {}