Refactor sdk/src/pod.rs
This commit is contained in:
parent
d01d425e4b
commit
f3e7e62813
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
mod close_account;
|
||||
pub mod transfer;
|
||||
mod transfer;
|
||||
mod update_account_pk;
|
||||
mod withdraw;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
¤t_sk,
|
||||
|
@ -255,8 +255,8 @@ mod test {
|
|||
);
|
||||
assert!(proof.verify(¤t_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,
|
||||
¤t_sk,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod convert;
|
||||
pub mod ops;
|
||||
pub mod pod;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {}
|
Loading…
Reference in New Issue