Zk instructions pass (#22851)
* zk-token-sdk: re-organize transcript * zk-token-sdk: add pod ElGamal group encryption * zk-token-sdk: add transcript domain separators for sigma proofs * zk-token-sdk: clean up transfer tx decryption * zk-token-sdk: resolve encoding issues for transfer * zk-token-sdk: fix transfer test * zk-token-sdk: clean up transcript for close account and withdraw instructions * zk-token-sdk: add transfer with fee instruction * zk-token-sdk: add transfer with fee instruction * zk-token-sdk: add pod for cryptographic structs needed for fee * zk-token-sdk: add pod for fee sigma proof * zk-token-sdk: fix test for transfer with fee instruction * zk-token-sdk: add range proof verification for transfer with fee * zk-token-sdk: add transfer amount decryption for transfer-with-fee * zk-token-sdk: add proof generation error for instruction * zk-token-sdk: cargo fmt and clippy * zk-token-sdk: fix bpf build
This commit is contained in:
parent
c631a3e0e4
commit
65f8f43665
|
@ -23,6 +23,7 @@ use {
|
||||||
curve25519_dalek::{
|
curve25519_dalek::{
|
||||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||||
scalar::Scalar,
|
scalar::Scalar,
|
||||||
|
traits::Identity,
|
||||||
},
|
},
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
@ -49,7 +50,7 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Algorithm handle for the twisted ElGamal encryption scheme
|
/// Algorithm handle for the twisted ElGamal encryption scheme
|
||||||
struct ElGamal;
|
pub struct ElGamal;
|
||||||
impl ElGamal {
|
impl ElGamal {
|
||||||
/// Generates an ElGamal keypair.
|
/// Generates an ElGamal keypair.
|
||||||
///
|
///
|
||||||
|
@ -93,6 +94,7 @@ impl ElGamal {
|
||||||
|
|
||||||
/// On input a public key, message, and Pedersen opening, the function
|
/// On input a public key, message, and Pedersen opening, the function
|
||||||
/// returns the corresponding ElGamal ciphertext.
|
/// returns the corresponding ElGamal ciphertext.
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
fn encrypt_with<T: Into<Scalar>>(
|
fn encrypt_with<T: Into<Scalar>>(
|
||||||
amount: T,
|
amount: T,
|
||||||
public: &ElGamalPubkey,
|
public: &ElGamalPubkey,
|
||||||
|
@ -104,10 +106,22 @@ impl ElGamal {
|
||||||
ElGamalCiphertext { commitment, handle }
|
ElGamalCiphertext { commitment, handle }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// On input a message, the function returns a twisted ElGamal ciphertext where the associated
|
||||||
|
/// Pedersen opening is always zero. Since the opening is zero, any twisted ElGamal ciphertext
|
||||||
|
/// of this form is a valid ciphertext under any ElGamal public key.
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
pub fn encode<T: Into<Scalar>>(amount: T) -> ElGamalCiphertext {
|
||||||
|
let commitment = Pedersen::encode(amount);
|
||||||
|
let handle = DecryptHandle(RistrettoPoint::identity());
|
||||||
|
|
||||||
|
ElGamalCiphertext { commitment, handle }
|
||||||
|
}
|
||||||
|
|
||||||
/// On input a secret key and a ciphertext, the function returns the decrypted message.
|
/// On input a secret key and a ciphertext, the function returns the decrypted message.
|
||||||
///
|
///
|
||||||
/// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted
|
/// The output of this function is of type `DiscreteLog`. To recover, the originally encrypted
|
||||||
/// message, use `DiscreteLog::decode`.
|
/// message, use `DiscreteLog::decode`.
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
fn decrypt(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
|
fn decrypt(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> DiscreteLog {
|
||||||
DiscreteLog {
|
DiscreteLog {
|
||||||
generator: *G,
|
generator: *G,
|
||||||
|
@ -117,6 +131,7 @@ impl ElGamal {
|
||||||
|
|
||||||
/// On input a secret key and a ciphertext, the function returns the decrypted message
|
/// On input a secret key and a ciphertext, the function returns the decrypted message
|
||||||
/// interpretted as type `u32`.
|
/// interpretted as type `u32`.
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
fn decrypt_u32(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> Option<u32> {
|
fn decrypt_u32(secret: &ElGamalSecretKey, ciphertext: &ElGamalCiphertext) -> Option<u32> {
|
||||||
let discrete_log_instance = Self::decrypt(secret, ciphertext);
|
let discrete_log_instance = Self::decrypt(secret, ciphertext);
|
||||||
discrete_log_instance.decode_u32()
|
discrete_log_instance.decode_u32()
|
||||||
|
@ -124,6 +139,7 @@ impl ElGamal {
|
||||||
|
|
||||||
/// On input a secret key, a ciphertext, and a pre-computed hashmap, the function returns the
|
/// On input a secret key, a ciphertext, and a pre-computed hashmap, the function returns the
|
||||||
/// decrypted message interpretted as type `u32`.
|
/// decrypted message interpretted as type `u32`.
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
fn decrypt_u32_online(
|
fn decrypt_u32_online(
|
||||||
secret: &ElGamalSecretKey,
|
secret: &ElGamalSecretKey,
|
||||||
ciphertext: &ElGamalCiphertext,
|
ciphertext: &ElGamalCiphertext,
|
||||||
|
|
|
@ -52,6 +52,13 @@ impl Pedersen {
|
||||||
|
|
||||||
PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, *r], &[*G, *H]))
|
PedersenCommitment(RistrettoPoint::multiscalar_mul(&[x, *r], &[*G, *H]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// On input a message, the function returns a Pedersen commitment with zero as the opening.
|
||||||
|
///
|
||||||
|
/// This function is deterministic.
|
||||||
|
pub fn encode<T: Into<Scalar>>(amount: T) -> PedersenCommitment {
|
||||||
|
PedersenCommitment(amount.into() * &(*G))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pedersen opening type.
|
/// Pedersen opening type.
|
||||||
|
|
|
@ -5,6 +5,8 @@ use thiserror::Error;
|
||||||
// TODO: clean up errors for encryption
|
// TODO: clean up errors for encryption
|
||||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ProofError {
|
pub enum ProofError {
|
||||||
|
#[error("proof generation failed")]
|
||||||
|
Generation,
|
||||||
#[error("proof failed to verify")]
|
#[error("proof failed to verify")]
|
||||||
Verification,
|
Verification,
|
||||||
#[error("range proof failed to verify")]
|
#[error("range proof failed to verify")]
|
||||||
|
@ -41,8 +43,8 @@ impl From<EqualityProofError> for ProofError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FeeProofError> for ProofError {
|
impl From<FeeSigmaProofError> for ProofError {
|
||||||
fn from(_err: FeeProofError) -> Self {
|
fn from(_err: FeeSigmaProofError) -> Self {
|
||||||
Self::FeeProof
|
Self::FeeProof
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,10 @@ use {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct CloseAccountData {
|
pub struct CloseAccountData {
|
||||||
/// The source account ElGamal pubkey
|
/// The source account ElGamal pubkey
|
||||||
pub elgamal_pubkey: pod::ElGamalPubkey, // 32 bytes
|
pub pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||||
|
|
||||||
/// The source account available balance in encrypted form
|
/// The source account available balance in encrypted form
|
||||||
pub balance: pod::ElGamalCiphertext, // 64 bytes
|
pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||||
|
|
||||||
/// Proof that the source account available balance is zero
|
/// Proof that the source account available balance is zero
|
||||||
pub proof: CloseAccountProof, // 64 bytes
|
pub proof: CloseAccountProof, // 64 bytes
|
||||||
|
@ -37,23 +37,33 @@ pub struct CloseAccountData {
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl CloseAccountData {
|
impl CloseAccountData {
|
||||||
pub fn new(source_keypair: &ElGamalKeypair, balance: ElGamalCiphertext) -> Self {
|
pub fn new(
|
||||||
let proof = CloseAccountProof::new(source_keypair, &balance);
|
keypair: &ElGamalKeypair,
|
||||||
|
ciphertext: &ElGamalCiphertext,
|
||||||
|
) -> Result<Self, ProofError> {
|
||||||
|
let pod_pubkey = pod::ElGamalPubkey((&keypair.public).to_bytes());
|
||||||
|
let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
|
||||||
|
|
||||||
CloseAccountData {
|
let mut transcript = CloseAccountProof::transcript_new(&pod_pubkey, &pod_ciphertext);
|
||||||
elgamal_pubkey: source_keypair.public.into(),
|
|
||||||
balance: balance.into(),
|
let proof = CloseAccountProof::new(keypair, ciphertext, &mut transcript);
|
||||||
|
|
||||||
|
Ok(CloseAccountData {
|
||||||
|
pubkey: pod_pubkey,
|
||||||
|
ciphertext: pod_ciphertext,
|
||||||
proof,
|
proof,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl Verifiable for CloseAccountData {
|
impl Verifiable for CloseAccountData {
|
||||||
fn verify(&self) -> Result<(), ProofError> {
|
fn verify(&self) -> Result<(), ProofError> {
|
||||||
let elgamal_pubkey = self.elgamal_pubkey.try_into()?;
|
let mut transcript = CloseAccountProof::transcript_new(&self.pubkey, &self.ciphertext);
|
||||||
let balance = self.balance.try_into()?;
|
|
||||||
self.proof.verify(&elgamal_pubkey, &balance)
|
let pubkey = self.pubkey.try_into()?;
|
||||||
|
let ciphertext = self.ciphertext.try_into()?;
|
||||||
|
self.proof.verify(&pubkey, &ciphertext, &mut transcript)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,18 +79,24 @@ pub struct CloseAccountProof {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl CloseAccountProof {
|
impl CloseAccountProof {
|
||||||
fn transcript_new() -> Transcript {
|
fn transcript_new(
|
||||||
Transcript::new(b"CloseAccountProof")
|
pubkey: &pod::ElGamalPubkey,
|
||||||
|
ciphertext: &pod::ElGamalCiphertext,
|
||||||
|
) -> Transcript {
|
||||||
|
let mut transcript = Transcript::new(b"CloseAccountProof");
|
||||||
|
|
||||||
|
transcript.append_pubkey(b"pubkey", pubkey);
|
||||||
|
transcript.append_ciphertext(b"ciphertext", ciphertext);
|
||||||
|
|
||||||
|
transcript
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(source_keypair: &ElGamalKeypair, balance: &ElGamalCiphertext) -> Self {
|
pub fn new(
|
||||||
let mut transcript = Self::transcript_new();
|
keypair: &ElGamalKeypair,
|
||||||
// TODO: Add ciphertext to transcript
|
ciphertext: &ElGamalCiphertext,
|
||||||
|
transcript: &mut Transcript,
|
||||||
// add a domain separator to record the start of the protocol
|
) -> Self {
|
||||||
transcript.close_account_proof_domain_sep();
|
let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
|
||||||
|
|
||||||
let proof = ZeroBalanceProof::new(source_keypair, balance, &mut transcript);
|
|
||||||
|
|
||||||
CloseAccountProof {
|
CloseAccountProof {
|
||||||
proof: proof.into(),
|
proof: proof.into(),
|
||||||
|
@ -89,72 +105,33 @@ impl CloseAccountProof {
|
||||||
|
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
&self,
|
&self,
|
||||||
elgamal_pubkey: &ElGamalPubkey,
|
pubkey: &ElGamalPubkey,
|
||||||
balance: &ElGamalCiphertext,
|
ciphertext: &ElGamalCiphertext,
|
||||||
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), ProofError> {
|
) -> Result<(), ProofError> {
|
||||||
let mut transcript = Self::transcript_new();
|
|
||||||
|
|
||||||
// add a domain separator to record the start of the protocol
|
|
||||||
transcript.close_account_proof_domain_sep();
|
|
||||||
|
|
||||||
// verify zero balance proof
|
|
||||||
let proof: ZeroBalanceProof = self.proof.try_into()?;
|
let proof: ZeroBalanceProof = self.proof.try_into()?;
|
||||||
proof.verify(elgamal_pubkey, balance, &mut transcript)?;
|
proof.verify(pubkey, ciphertext, transcript)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {
|
use super::*;
|
||||||
super::*,
|
|
||||||
crate::encryption::{
|
|
||||||
elgamal::{DecryptHandle, ElGamalKeypair},
|
|
||||||
pedersen::{Pedersen, PedersenOpening},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_close_account_correctness() {
|
fn test_close_account_correctness() {
|
||||||
let source_keypair = ElGamalKeypair::new_rand();
|
let keypair = ElGamalKeypair::new_rand();
|
||||||
|
|
||||||
// general case: encryption of 0
|
// general case: encryption of 0
|
||||||
let balance = source_keypair.public.encrypt(0_u64);
|
let ciphertext = keypair.public.encrypt(0_u64);
|
||||||
let proof = CloseAccountProof::new(&source_keypair, &balance);
|
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
|
||||||
assert!(proof.verify(&source_keypair.public, &balance).is_ok());
|
assert!(close_account_data.verify().is_ok());
|
||||||
|
|
||||||
// general case: encryption of > 0
|
// general case: encryption of > 0
|
||||||
let balance = source_keypair.public.encrypt(1_u64);
|
let ciphertext = keypair.public.encrypt(1_u64);
|
||||||
let proof = CloseAccountProof::new(&source_keypair, &balance);
|
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
|
||||||
assert!(proof.verify(&source_keypair.public, &balance).is_err());
|
assert!(close_account_data.verify().is_err());
|
||||||
|
|
||||||
// // edge case: all zero ciphertext - such ciphertext should always be a valid encryption of 0
|
|
||||||
let zeroed_ct: ElGamalCiphertext = pod::ElGamalCiphertext::zeroed().try_into().unwrap();
|
|
||||||
let proof = CloseAccountProof::new(&source_keypair, &zeroed_ct);
|
|
||||||
assert!(proof.verify(&source_keypair.public, &zeroed_ct).is_ok());
|
|
||||||
|
|
||||||
// edge cases: only C or D is zero - such ciphertext is always invalid
|
|
||||||
let zeroed_comm = Pedersen::with(0_u64, &PedersenOpening::default());
|
|
||||||
let handle = balance.handle;
|
|
||||||
|
|
||||||
let zeroed_comm_ciphertext = ElGamalCiphertext {
|
|
||||||
commitment: zeroed_comm,
|
|
||||||
handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
let proof = CloseAccountProof::new(&source_keypair, &zeroed_comm_ciphertext);
|
|
||||||
assert!(proof
|
|
||||||
.verify(&source_keypair.public, &zeroed_comm_ciphertext)
|
|
||||||
.is_err());
|
|
||||||
|
|
||||||
let zeroed_handle_ciphertext = ElGamalCiphertext {
|
|
||||||
commitment: balance.commitment,
|
|
||||||
handle: DecryptHandle::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let proof = CloseAccountProof::new(&source_keypair, &zeroed_handle_ciphertext);
|
|
||||||
assert!(proof
|
|
||||||
.verify(&source_keypair.public, &zeroed_handle_ciphertext)
|
|
||||||
.is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
mod close_account;
|
pub mod close_account;
|
||||||
mod transfer;
|
pub mod transfer;
|
||||||
mod withdraw;
|
pub mod transfer_with_fee;
|
||||||
|
pub mod withdraw;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
use crate::errors::ProofError;
|
use {
|
||||||
pub use {
|
crate::{
|
||||||
close_account::CloseAccountData,
|
encryption::{
|
||||||
transfer::{TransferCommitments, TransferData, TransferPubkeys},
|
elgamal::ElGamalCiphertext,
|
||||||
withdraw::WithdrawData,
|
pedersen::{PedersenCommitment, PedersenOpening},
|
||||||
|
},
|
||||||
|
errors::ProofError,
|
||||||
|
},
|
||||||
|
curve25519_dalek::scalar::Scalar,
|
||||||
};
|
};
|
||||||
|
pub use {close_account::CloseAccountData, transfer::TransferData, withdraw::WithdrawData};
|
||||||
|
|
||||||
|
/// Constant for 2^32
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
const TWO_32: u64 = 4294967296;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub trait Verifiable {
|
pub trait Verifiable {
|
||||||
|
@ -22,3 +32,36 @@ pub enum Role {
|
||||||
Dest,
|
Dest,
|
||||||
Auditor,
|
Auditor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Split u64 number into two u32 numbers
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
pub fn split_u64_into_u32(amount: u64) -> (u32, u32) {
|
||||||
|
let lo = amount as u32;
|
||||||
|
let hi = (amount >> 32) as u32;
|
||||||
|
|
||||||
|
(lo, hi)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
fn combine_u32_ciphertexts(
|
||||||
|
ciphertext_lo: &ElGamalCiphertext,
|
||||||
|
ciphertext_hi: &ElGamalCiphertext,
|
||||||
|
) -> ElGamalCiphertext {
|
||||||
|
ciphertext_lo + &(ciphertext_hi * &Scalar::from(TWO_32))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
pub fn combine_u32_commitments(
|
||||||
|
comm_lo: &PedersenCommitment,
|
||||||
|
comm_hi: &PedersenCommitment,
|
||||||
|
) -> PedersenCommitment {
|
||||||
|
comm_lo + comm_hi * &Scalar::from(TWO_32)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
pub fn combine_u32_openings(
|
||||||
|
opening_lo: &PedersenOpening,
|
||||||
|
opening_hi: &PedersenOpening,
|
||||||
|
) -> PedersenOpening {
|
||||||
|
opening_lo + opening_hi * &Scalar::from(TWO_32)
|
||||||
|
}
|
||||||
|
|
|
@ -13,188 +13,197 @@ use {
|
||||||
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
|
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
|
||||||
},
|
},
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
instruction::{Role, Verifiable},
|
instruction::{combine_u32_ciphertexts, split_u64_into_u32, Role, Verifiable, TWO_32},
|
||||||
range_proof::RangeProof,
|
range_proof::RangeProof,
|
||||||
sigma_proofs::{equality_proof::EqualityProof, validity_proof::AggregatedValidityProof},
|
sigma_proofs::{equality_proof::EqualityProof, validity_proof::AggregatedValidityProof},
|
||||||
transcript::TranscriptProtocol,
|
transcript::TranscriptProtocol,
|
||||||
},
|
},
|
||||||
curve25519_dalek::scalar::Scalar,
|
arrayref::{array_ref, array_refs},
|
||||||
merlin::Transcript,
|
merlin::Transcript,
|
||||||
std::convert::TryInto,
|
std::convert::TryInto,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
pub struct TransferAmountEncryption {
|
||||||
|
pub commitment: PedersenCommitment,
|
||||||
|
pub source: DecryptHandle,
|
||||||
|
pub dest: DecryptHandle,
|
||||||
|
pub auditor: DecryptHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl TransferAmountEncryption {
|
||||||
|
pub fn new(
|
||||||
|
amount: u32,
|
||||||
|
pubkey_source: &ElGamalPubkey,
|
||||||
|
pubkey_dest: &ElGamalPubkey,
|
||||||
|
pubkey_auditor: &ElGamalPubkey,
|
||||||
|
) -> (Self, PedersenOpening) {
|
||||||
|
let (commitment, opening) = Pedersen::new(amount);
|
||||||
|
let transfer_amount_encryption = Self {
|
||||||
|
commitment,
|
||||||
|
source: pubkey_source.decrypt_handle(&opening),
|
||||||
|
dest: pubkey_dest.decrypt_handle(&opening),
|
||||||
|
auditor: pubkey_auditor.decrypt_handle(&opening),
|
||||||
|
};
|
||||||
|
|
||||||
|
(transfer_amount_encryption, opening)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 128] {
|
||||||
|
let mut bytes = [0u8; 128];
|
||||||
|
bytes[..32].copy_from_slice(&self.commitment.to_bytes());
|
||||||
|
bytes[32..64].copy_from_slice(&self.source.to_bytes());
|
||||||
|
bytes[64..96].copy_from_slice(&self.dest.to_bytes());
|
||||||
|
bytes[96..128].copy_from_slice(&self.auditor.to_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||||
|
let bytes = array_ref![bytes, 0, 128];
|
||||||
|
let (commitment, source, dest, auditor) = array_refs![bytes, 32, 32, 32, 32];
|
||||||
|
|
||||||
|
let commitment =
|
||||||
|
PedersenCommitment::from_bytes(commitment).ok_or(ProofError::Verification)?;
|
||||||
|
let source = DecryptHandle::from_bytes(source).ok_or(ProofError::Verification)?;
|
||||||
|
let dest = DecryptHandle::from_bytes(dest).ok_or(ProofError::Verification)?;
|
||||||
|
let auditor = DecryptHandle::from_bytes(auditor).ok_or(ProofError::Verification)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
commitment,
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
auditor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TransferData {
|
pub struct TransferData {
|
||||||
/// The encrypted transfer amount
|
/// Group encryption of the low 32 bits of the transfer amount
|
||||||
pub encrypted_transfer_amount: EncryptedTransferAmount,
|
pub ciphertext_lo: pod::TransferAmountEncryption,
|
||||||
|
|
||||||
|
/// Group encryption of the high 32 bits of the transfer amount
|
||||||
|
pub ciphertext_hi: pod::TransferAmountEncryption,
|
||||||
|
|
||||||
/// The public encryption keys associated with the transfer: source, dest, and auditor
|
/// The public encryption keys associated with the transfer: source, dest, and auditor
|
||||||
pub transfer_public_keys: TransferPubkeys, // 128 bytes
|
pub transfer_pubkeys: pod::TransferPubkeys,
|
||||||
|
|
||||||
/// The final spendable ciphertext after the transfer
|
/// The final spendable ciphertext after the transfer
|
||||||
pub new_spendable_ct: pod::ElGamalCiphertext, // 64 bytes
|
pub ciphertext_new_source: pod::ElGamalCiphertext,
|
||||||
|
|
||||||
// pub fee: EncryptedTransferFee,
|
|
||||||
/// Zero-knowledge proofs for Transfer
|
/// Zero-knowledge proofs for Transfer
|
||||||
pub proof: TransferProof,
|
pub proof: TransferProof,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct FeeParameters {
|
|
||||||
/// Fee rate expressed as basis points of the transfer amount, i.e. increments of 0.01%
|
|
||||||
pub fee_rate_basis_points: u16,
|
|
||||||
/// Maximum fee assessed on transfers, expressed as an amount of tokens
|
|
||||||
pub maximum_fee: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn calculate_fee(transfer_amount: u64, fee_parameters: FeeParameters) -> u64 {
|
|
||||||
// TODO: temporary way to calculate fees for now. Should account for overflows/compiler
|
|
||||||
// optimizations
|
|
||||||
let fee = (transfer_amount * (fee_parameters.fee_rate_basis_points as u64)) / 10000;
|
|
||||||
if fee % 10000 > 0 {
|
|
||||||
fee + 1
|
|
||||||
} else {
|
|
||||||
fee
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl TransferData {
|
impl TransferData {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
// amount of the transfer
|
|
||||||
transfer_amount: u64,
|
transfer_amount: u64,
|
||||||
|
(spendable_balance, ciphertext_old_source): (u64, &ElGamalCiphertext),
|
||||||
// available balance in the source account as u64
|
keypair_source: &ElGamalKeypair,
|
||||||
spendable_balance: u64,
|
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
|
) -> Result<Self, ProofError> {
|
||||||
// available balance in the source account as ElGamalCiphertext
|
|
||||||
spendable_balance_ciphertext: ElGamalCiphertext,
|
|
||||||
|
|
||||||
// source account ElGamal keypair
|
|
||||||
source_keypair: &ElGamalKeypair,
|
|
||||||
|
|
||||||
// destination account ElGamal pubkey
|
|
||||||
dest_pk: ElGamalPubkey,
|
|
||||||
|
|
||||||
// auditor ElGamal pubkey
|
|
||||||
auditor_pk: ElGamalPubkey,
|
|
||||||
// // fee collector ElGamal pubkey
|
|
||||||
// fee_collector_pk: ElGamalPubkey,
|
|
||||||
|
|
||||||
// // fee rate and cap value
|
|
||||||
// fee_parameters: FeeParameters,
|
|
||||||
) -> Self {
|
|
||||||
// split and encrypt transfer amount
|
// split and encrypt transfer amount
|
||||||
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
|
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
|
||||||
|
|
||||||
let (comm_lo, open_lo) = Pedersen::new(amount_lo);
|
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
||||||
let (comm_hi, open_hi) = Pedersen::new(amount_hi);
|
amount_lo,
|
||||||
|
&keypair_source.public,
|
||||||
let handle_source_lo = source_keypair.public.decrypt_handle(&open_lo);
|
pubkey_dest,
|
||||||
let handle_dest_lo = dest_pk.decrypt_handle(&open_lo);
|
pubkey_auditor,
|
||||||
let handle_auditor_lo = auditor_pk.decrypt_handle(&open_lo);
|
);
|
||||||
|
let (ciphertext_hi, opening_hi) = TransferAmountEncryption::new(
|
||||||
let handle_source_hi = source_keypair.public.decrypt_handle(&open_hi);
|
amount_hi,
|
||||||
let handle_dest_hi = dest_pk.decrypt_handle(&open_hi);
|
&keypair_source.public,
|
||||||
let handle_auditor_hi = auditor_pk.decrypt_handle(&open_hi);
|
pubkey_dest,
|
||||||
|
pubkey_auditor,
|
||||||
// organize transfer amount commitments and decrypt handles
|
|
||||||
let decrypt_handles_lo = TransferDecryptHandles {
|
|
||||||
source: handle_source_lo.into(),
|
|
||||||
dest: handle_dest_lo.into(),
|
|
||||||
auditor: handle_auditor_lo.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let decrypt_handles_hi = TransferDecryptHandles {
|
|
||||||
source: handle_source_hi.into(),
|
|
||||||
dest: handle_dest_hi.into(),
|
|
||||||
auditor: handle_auditor_hi.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let encrypted_transfer_amount = EncryptedTransferAmount {
|
|
||||||
amount_comm_lo: comm_lo.into(),
|
|
||||||
amount_comm_hi: comm_hi.into(),
|
|
||||||
decrypt_handles_lo,
|
|
||||||
decrypt_handles_hi,
|
|
||||||
};
|
|
||||||
|
|
||||||
// group public keys for transfer
|
|
||||||
let transfer_public_keys = TransferPubkeys {
|
|
||||||
source_pk: source_keypair.public.into(),
|
|
||||||
dest_pk: dest_pk.into(),
|
|
||||||
auditor_pk: auditor_pk.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// subtract transfer amount from the spendable ciphertext
|
|
||||||
let spendable_comm = spendable_balance_ciphertext.commitment;
|
|
||||||
let spendable_handle = spendable_balance_ciphertext.handle;
|
|
||||||
|
|
||||||
let new_spendable_balance = spendable_balance - transfer_amount;
|
|
||||||
let new_spendable_comm = spendable_comm - combine_u32_comms(comm_lo, comm_hi);
|
|
||||||
let new_spendable_handle =
|
|
||||||
spendable_handle - combine_u32_handles(handle_source_lo, handle_source_hi);
|
|
||||||
|
|
||||||
let new_spendable_ct = ElGamalCiphertext {
|
|
||||||
commitment: new_spendable_comm,
|
|
||||||
handle: new_spendable_handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
// range_proof and validity_proof should be generated together
|
|
||||||
let proof = TransferProof::new(
|
|
||||||
source_keypair,
|
|
||||||
&dest_pk,
|
|
||||||
&auditor_pk,
|
|
||||||
(amount_lo as u64, amount_hi as u64),
|
|
||||||
(&open_lo, &open_hi),
|
|
||||||
new_spendable_balance,
|
|
||||||
&new_spendable_ct,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
// subtract transfer amount from the spendable ciphertext
|
||||||
encrypted_transfer_amount,
|
let new_spendable_balance = spendable_balance
|
||||||
new_spendable_ct: new_spendable_ct.into(),
|
.checked_sub(transfer_amount)
|
||||||
transfer_public_keys,
|
.ok_or(ProofError::Generation)?;
|
||||||
|
|
||||||
|
let transfer_amount_lo_source = ElGamalCiphertext {
|
||||||
|
commitment: ciphertext_lo.commitment,
|
||||||
|
handle: ciphertext_lo.source,
|
||||||
|
};
|
||||||
|
|
||||||
|
let transfer_amount_hi_source = ElGamalCiphertext {
|
||||||
|
commitment: ciphertext_hi.commitment,
|
||||||
|
handle: ciphertext_hi.source,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ciphertext_new_source = ciphertext_old_source
|
||||||
|
- combine_u32_ciphertexts(&transfer_amount_lo_source, &transfer_amount_hi_source);
|
||||||
|
|
||||||
|
// generate transcript and append all public inputs
|
||||||
|
let pod_transfer_pubkeys =
|
||||||
|
pod::TransferPubkeys::new(&keypair_source.public, pubkey_dest, pubkey_auditor);
|
||||||
|
let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.into();
|
||||||
|
let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.into();
|
||||||
|
let pod_ciphertext_new_source: pod::ElGamalCiphertext = ciphertext_new_source.into();
|
||||||
|
|
||||||
|
let mut transcript = TransferProof::transcript_new(
|
||||||
|
&pod_transfer_pubkeys,
|
||||||
|
&pod_ciphertext_lo,
|
||||||
|
&pod_ciphertext_hi,
|
||||||
|
&pod_ciphertext_new_source,
|
||||||
|
);
|
||||||
|
|
||||||
|
let proof = TransferProof::new(
|
||||||
|
(amount_lo, amount_hi),
|
||||||
|
keypair_source,
|
||||||
|
(pubkey_dest, pubkey_auditor),
|
||||||
|
&opening_lo,
|
||||||
|
&opening_hi,
|
||||||
|
(new_spendable_balance, &ciphertext_new_source),
|
||||||
|
&mut transcript,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
ciphertext_lo: pod_ciphertext_lo,
|
||||||
|
ciphertext_hi: pod_ciphertext_hi,
|
||||||
|
transfer_pubkeys: pod_transfer_pubkeys,
|
||||||
|
ciphertext_new_source: pod_ciphertext_new_source,
|
||||||
proof,
|
proof,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the lo ciphertexts associated with a transfer data
|
/// Extracts the lo ciphertexts associated with a transfer data
|
||||||
fn ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
|
fn ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
|
||||||
let transfer_comm_lo: PedersenCommitment =
|
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
|
||||||
self.encrypted_transfer_amount.amount_comm_lo.try_into()?;
|
|
||||||
|
|
||||||
let decryption_handle_lo = match role {
|
let handle_lo = match role {
|
||||||
Role::Source => self.encrypted_transfer_amount.decrypt_handles_lo.source,
|
Role::Source => ciphertext_lo.source,
|
||||||
Role::Dest => self.encrypted_transfer_amount.decrypt_handles_lo.dest,
|
Role::Dest => ciphertext_lo.dest,
|
||||||
Role::Auditor => self.encrypted_transfer_amount.decrypt_handles_lo.auditor,
|
Role::Auditor => ciphertext_lo.auditor,
|
||||||
}
|
};
|
||||||
.try_into()?;
|
|
||||||
|
|
||||||
Ok(ElGamalCiphertext {
|
Ok(ElGamalCiphertext {
|
||||||
commitment: transfer_comm_lo,
|
commitment: ciphertext_lo.commitment,
|
||||||
handle: decryption_handle_lo,
|
handle: handle_lo,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the lo ciphertexts associated with a transfer data
|
/// Extracts the lo ciphertexts associated with a transfer data
|
||||||
fn ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
|
fn ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
|
||||||
let transfer_comm_hi: PedersenCommitment =
|
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
|
||||||
self.encrypted_transfer_amount.amount_comm_hi.try_into()?;
|
|
||||||
|
|
||||||
let decryption_handle_hi = match role {
|
let handle_hi = match role {
|
||||||
Role::Source => self.encrypted_transfer_amount.decrypt_handles_hi.source,
|
Role::Source => ciphertext_hi.source,
|
||||||
Role::Dest => self.encrypted_transfer_amount.decrypt_handles_hi.dest,
|
Role::Dest => ciphertext_hi.dest,
|
||||||
Role::Auditor => self.encrypted_transfer_amount.decrypt_handles_hi.auditor,
|
Role::Auditor => ciphertext_hi.auditor,
|
||||||
}
|
};
|
||||||
.try_into()?;
|
|
||||||
|
|
||||||
Ok(ElGamalCiphertext {
|
Ok(ElGamalCiphertext {
|
||||||
commitment: transfer_comm_hi,
|
commitment: ciphertext_hi.commitment,
|
||||||
handle: decryption_handle_hi,
|
handle: handle_hi,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,17 +231,25 @@ impl TransferData {
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl Verifiable for TransferData {
|
impl Verifiable for TransferData {
|
||||||
fn verify(&self) -> Result<(), ProofError> {
|
fn verify(&self) -> Result<(), ProofError> {
|
||||||
let transfer_commitments = TransferCommitments {
|
// generate transcript and append all public inputs
|
||||||
lo: self.encrypted_transfer_amount.amount_comm_lo,
|
let mut transcript = TransferProof::transcript_new(
|
||||||
hi: self.encrypted_transfer_amount.amount_comm_hi,
|
&self.transfer_pubkeys,
|
||||||
};
|
&self.ciphertext_lo,
|
||||||
|
&self.ciphertext_hi,
|
||||||
|
&self.ciphertext_new_source,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ciphertext_lo = self.ciphertext_lo.try_into()?;
|
||||||
|
let ciphertext_hi = self.ciphertext_hi.try_into()?;
|
||||||
|
let transfer_pubkeys = self.transfer_pubkeys.try_into()?;
|
||||||
|
let new_spendable_ciphertext = self.ciphertext_new_source.try_into()?;
|
||||||
|
|
||||||
self.proof.verify(
|
self.proof.verify(
|
||||||
&transfer_commitments,
|
&ciphertext_lo,
|
||||||
&self.encrypted_transfer_amount.decrypt_handles_lo,
|
&ciphertext_hi,
|
||||||
&self.encrypted_transfer_amount.decrypt_handles_hi,
|
&transfer_pubkeys,
|
||||||
&self.new_spendable_ct,
|
&new_spendable_ciphertext,
|
||||||
&self.transfer_public_keys,
|
&mut transcript,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,7 +259,7 @@ impl Verifiable for TransferData {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TransferProof {
|
pub struct TransferProof {
|
||||||
/// New Pedersen commitment for the remaining balance in source
|
/// New Pedersen commitment for the remaining balance in source
|
||||||
pub source_commitment: pod::PedersenCommitment,
|
pub commitment_new_source: pod::PedersenCommitment,
|
||||||
|
|
||||||
/// Associated equality proof
|
/// Associated equality proof
|
||||||
pub equality_proof: pod::EqualityProof,
|
pub equality_proof: pod::EqualityProof,
|
||||||
|
@ -257,143 +274,118 @@ pub struct TransferProof {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl TransferProof {
|
impl TransferProof {
|
||||||
fn transcript_new() -> Transcript {
|
fn transcript_new(
|
||||||
Transcript::new(b"TransferProof")
|
transfer_pubkeys: &pod::TransferPubkeys,
|
||||||
|
ciphertext_lo: &pod::TransferAmountEncryption,
|
||||||
|
ciphertext_hi: &pod::TransferAmountEncryption,
|
||||||
|
ciphertext_new_source: &pod::ElGamalCiphertext,
|
||||||
|
) -> Transcript {
|
||||||
|
let mut transcript = Transcript::new(b"transfer-proof");
|
||||||
|
|
||||||
|
transcript.append_message(b"transfer-pubkeys", &transfer_pubkeys.0);
|
||||||
|
transcript.append_message(b"ciphertext-lo", &ciphertext_lo.0);
|
||||||
|
transcript.append_message(b"ciphertext-hi", &ciphertext_hi.0);
|
||||||
|
transcript.append_message(b"ciphertext-new-source", &ciphertext_new_source.0);
|
||||||
|
|
||||||
|
transcript
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
#[allow(clippy::many_single_char_names)]
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
source_keypair: &ElGamalKeypair,
|
(transfer_amount_lo, transfer_amount_hi): (u32, u32),
|
||||||
dest_pk: &ElGamalPubkey,
|
keypair_source: &ElGamalKeypair,
|
||||||
auditor_pk: &ElGamalPubkey,
|
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
transfer_amt: (u64, u64),
|
opening_lo: &PedersenOpening,
|
||||||
openings: (&PedersenOpening, &PedersenOpening),
|
opening_hi: &PedersenOpening,
|
||||||
source_new_balance: u64,
|
(source_new_balance, ciphertext_new_source): (u64, &ElGamalCiphertext),
|
||||||
source_new_balance_ct: &ElGamalCiphertext,
|
transcript: &mut Transcript,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut transcript = Self::transcript_new();
|
|
||||||
|
|
||||||
// add a domain separator to record the start of the protocol
|
|
||||||
transcript.transfer_proof_domain_sep();
|
|
||||||
|
|
||||||
// generate a Pedersen commitment for the remaining balance in source
|
// generate a Pedersen commitment for the remaining balance in source
|
||||||
let (source_commitment, source_open) = Pedersen::new(source_new_balance);
|
let (commitment_new_source, opening_source) = Pedersen::new(source_new_balance);
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
let pod_commitment_new_source: pod::PedersenCommitment = commitment_new_source.into();
|
||||||
let P_EG = source_keypair.public.get_point();
|
transcript.append_commitment(b"commitment-new-source", &pod_commitment_new_source);
|
||||||
let C_EG = source_new_balance_ct.commitment.get_point();
|
|
||||||
let D_EG = source_new_balance_ct.handle.get_point();
|
|
||||||
let C_Ped = source_commitment.get_point();
|
|
||||||
|
|
||||||
// append all current state to the transcript
|
|
||||||
transcript.append_point(b"P_EG", &P_EG.compress());
|
|
||||||
transcript.append_point(b"C_EG", &C_EG.compress());
|
|
||||||
transcript.append_point(b"D_EG", &D_EG.compress());
|
|
||||||
transcript.append_point(b"C_Ped", &C_Ped.compress());
|
|
||||||
|
|
||||||
// generate equality_proof
|
// generate equality_proof
|
||||||
let equality_proof = EqualityProof::new(
|
let equality_proof = EqualityProof::new(
|
||||||
source_keypair,
|
keypair_source,
|
||||||
source_new_balance_ct,
|
ciphertext_new_source,
|
||||||
source_new_balance,
|
source_new_balance,
|
||||||
&source_open,
|
&opening_source,
|
||||||
&mut transcript,
|
transcript,
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate ciphertext validity proof
|
// generate ciphertext validity proof
|
||||||
let validity_proof = AggregatedValidityProof::new(
|
let validity_proof = AggregatedValidityProof::new(
|
||||||
(dest_pk, auditor_pk),
|
(pubkey_dest, pubkey_auditor),
|
||||||
transfer_amt,
|
(transfer_amount_lo, transfer_amount_hi),
|
||||||
openings,
|
(opening_lo, opening_hi),
|
||||||
&mut transcript,
|
transcript,
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate the range proof
|
// generate the range proof
|
||||||
let range_proof = RangeProof::new(
|
let range_proof = RangeProof::new(
|
||||||
vec![source_new_balance, transfer_amt.0, transfer_amt.1],
|
vec![
|
||||||
|
source_new_balance,
|
||||||
|
transfer_amount_lo as u64,
|
||||||
|
transfer_amount_hi as u64,
|
||||||
|
],
|
||||||
vec![64, 32, 32],
|
vec![64, 32, 32],
|
||||||
vec![&source_open, openings.0, openings.1],
|
vec![&opening_source, opening_lo, opening_hi],
|
||||||
&mut transcript,
|
transcript,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
source_commitment: source_commitment.into(),
|
commitment_new_source: pod_commitment_new_source,
|
||||||
equality_proof: equality_proof.try_into().expect("equality proof"),
|
equality_proof: equality_proof.into(),
|
||||||
validity_proof: validity_proof.try_into().expect("validity proof"),
|
validity_proof: validity_proof.into(),
|
||||||
range_proof: range_proof.try_into().expect("range proof"),
|
range_proof: range_proof.try_into().expect("range proof: length error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
self,
|
&self,
|
||||||
amount_comms: &TransferCommitments,
|
ciphertext_lo: &TransferAmountEncryption,
|
||||||
decryption_handles_lo: &TransferDecryptHandles,
|
ciphertext_hi: &TransferAmountEncryption,
|
||||||
decryption_handles_hi: &TransferDecryptHandles,
|
transfer_pubkeys: &TransferPubkeys,
|
||||||
new_spendable_ct: &pod::ElGamalCiphertext,
|
new_spendable_ciphertext: &ElGamalCiphertext,
|
||||||
transfer_public_keys: &TransferPubkeys,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), ProofError> {
|
) -> Result<(), ProofError> {
|
||||||
let mut transcript = Self::transcript_new();
|
transcript.append_commitment(b"commitment-new-source", &self.commitment_new_source);
|
||||||
|
|
||||||
let commitment: PedersenCommitment = self.source_commitment.try_into()?;
|
let commitment: PedersenCommitment = self.commitment_new_source.try_into()?;
|
||||||
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
|
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
|
||||||
let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
|
let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
|
||||||
let range_proof: RangeProof = self.range_proof.try_into()?;
|
let range_proof: RangeProof = self.range_proof.try_into()?;
|
||||||
|
|
||||||
// add a domain separator to record the start of the protocol
|
|
||||||
transcript.transfer_proof_domain_sep();
|
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
|
||||||
let source_pk: ElGamalPubkey = transfer_public_keys.source_pk.try_into()?;
|
|
||||||
let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?;
|
|
||||||
|
|
||||||
let P_EG = source_pk.get_point();
|
|
||||||
let C_EG = new_spendable_ct.commitment.get_point();
|
|
||||||
let D_EG = new_spendable_ct.handle.get_point();
|
|
||||||
let C_Ped = commitment.get_point();
|
|
||||||
|
|
||||||
// append all current state to the transcript
|
|
||||||
transcript.append_point(b"P_EG", &P_EG.compress());
|
|
||||||
transcript.append_point(b"C_EG", &C_EG.compress());
|
|
||||||
transcript.append_point(b"D_EG", &D_EG.compress());
|
|
||||||
transcript.append_point(b"C_Ped", &C_Ped.compress());
|
|
||||||
|
|
||||||
// verify equality proof
|
// verify equality proof
|
||||||
//
|
//
|
||||||
// TODO: we can also consider verifying equality and range proof in a batch
|
// TODO: we can also consider verifying equality and range proof in a batch
|
||||||
equality_proof.verify(&source_pk, &new_spendable_ct, &commitment, &mut transcript)?;
|
equality_proof.verify(
|
||||||
|
&transfer_pubkeys.source,
|
||||||
|
new_spendable_ciphertext,
|
||||||
|
&commitment,
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
|
||||||
// TODO: record destination and auditor public keys to transcript
|
// verify validity proof
|
||||||
let dest_elgamal_pubkey: ElGamalPubkey = transfer_public_keys.dest_pk.try_into()?;
|
|
||||||
let auditor_elgamal_pubkey: ElGamalPubkey = transfer_public_keys.auditor_pk.try_into()?;
|
|
||||||
|
|
||||||
let amount_comm_lo: PedersenCommitment = amount_comms.lo.try_into()?;
|
|
||||||
let amount_comm_hi: PedersenCommitment = amount_comms.hi.try_into()?;
|
|
||||||
|
|
||||||
let handle_lo_dest: DecryptHandle = decryption_handles_lo.dest.try_into()?;
|
|
||||||
let handle_hi_dest: DecryptHandle = decryption_handles_hi.dest.try_into()?;
|
|
||||||
|
|
||||||
let handle_lo_auditor: DecryptHandle = decryption_handles_lo.auditor.try_into()?;
|
|
||||||
let handle_hi_auditor: DecryptHandle = decryption_handles_hi.auditor.try_into()?;
|
|
||||||
|
|
||||||
// TODO: validity proof
|
|
||||||
aggregated_validity_proof.verify(
|
aggregated_validity_proof.verify(
|
||||||
(&dest_elgamal_pubkey, &auditor_elgamal_pubkey),
|
(&transfer_pubkeys.dest, &transfer_pubkeys.auditor),
|
||||||
(&amount_comm_lo, &amount_comm_hi),
|
(&ciphertext_lo.commitment, &ciphertext_hi.commitment),
|
||||||
(&handle_lo_dest, &handle_hi_dest),
|
(&ciphertext_lo.dest, &ciphertext_hi.dest),
|
||||||
(&handle_lo_auditor, &handle_hi_auditor),
|
(&ciphertext_lo.auditor, &ciphertext_hi.auditor),
|
||||||
&mut transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// verify range proof
|
// verify range proof
|
||||||
|
let commitment_new_source = self.commitment_new_source.try_into()?;
|
||||||
range_proof.verify(
|
range_proof.verify(
|
||||||
vec![
|
vec![
|
||||||
&self.source_commitment.into(),
|
&commitment_new_source,
|
||||||
&amount_comms.lo.into(),
|
&ciphertext_lo.commitment,
|
||||||
&amount_comms.hi.into(),
|
&ciphertext_hi.commitment,
|
||||||
],
|
],
|
||||||
vec![64_usize, 32_usize, 32_usize],
|
vec![64_usize, 32_usize, 32_usize],
|
||||||
&mut transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -401,85 +393,52 @@ impl TransferProof {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ElGamal public keys needed for a transfer
|
/// The ElGamal public keys needed for a transfer
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub struct TransferPubkeys {
|
pub struct TransferPubkeys {
|
||||||
pub source_pk: pod::ElGamalPubkey, // 32 bytes
|
pub source: ElGamalPubkey,
|
||||||
pub dest_pk: pod::ElGamalPubkey, // 32 bytes
|
pub dest: ElGamalPubkey,
|
||||||
pub auditor_pk: pod::ElGamalPubkey, // 32 bytes
|
pub auditor: ElGamalPubkey,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct EncryptedTransferAmount {
|
|
||||||
pub amount_comm_lo: pod::PedersenCommitment,
|
|
||||||
|
|
||||||
pub amount_comm_hi: pod::PedersenCommitment,
|
|
||||||
|
|
||||||
/// The decryption handles that allow decryption of the lo-bits of the transfer amount
|
|
||||||
pub decrypt_handles_lo: TransferDecryptHandles,
|
|
||||||
|
|
||||||
/// The decryption handles that allow decryption of the hi-bits of the transfer amount
|
|
||||||
pub decrypt_handles_hi: TransferDecryptHandles,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The decryption handles needed for a transfer
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct TransferDecryptHandles {
|
|
||||||
pub source: pod::DecryptHandle, // 32 bytes
|
|
||||||
pub dest: pod::DecryptHandle, // 32 bytes
|
|
||||||
pub auditor: pod::DecryptHandle, // 32 bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct TransferCommitments {
|
|
||||||
pub lo: pod::PedersenCommitment,
|
|
||||||
pub hi: pod::PedersenCommitment,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct EncryptedTransferFee {
|
|
||||||
/// The transfer fee commitment
|
|
||||||
pub fee_comm: pod::PedersenCommitment,
|
|
||||||
/// The decryption handle for destination ElGamal pubkey
|
|
||||||
pub decrypt_handle_dest: pod::DecryptHandle,
|
|
||||||
/// The decryption handle for fee collector ElGamal pubkey
|
|
||||||
pub decrypt_handle_fee_collector: pod::DecryptHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Split u64 number into two u32 numbers
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
|
||||||
pub fn split_u64_into_u32(amt: u64) -> (u32, u32) {
|
|
||||||
let lo = amt as u32;
|
|
||||||
let hi = (amt >> 32) as u32;
|
|
||||||
|
|
||||||
(lo, hi)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constant for 2^32
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
|
||||||
const TWO_32: u64 = 4294967296;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
|
||||||
pub fn combine_u32_comms(
|
|
||||||
comm_lo: PedersenCommitment,
|
|
||||||
comm_hi: PedersenCommitment,
|
|
||||||
) -> PedersenCommitment {
|
|
||||||
comm_lo + comm_hi * Scalar::from(TWO_32)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn combine_u32_handles(handle_lo: DecryptHandle, handle_hi: DecryptHandle) -> DecryptHandle {
|
impl TransferPubkeys {
|
||||||
handle_lo + handle_hi * Scalar::from(TWO_32)
|
// TODO: use constructor instead
|
||||||
|
pub fn to_bytes(&self) -> [u8; 96] {
|
||||||
|
let mut bytes = [0u8; 96];
|
||||||
|
bytes[..32].copy_from_slice(&self.source.to_bytes());
|
||||||
|
bytes[32..64].copy_from_slice(&self.dest.to_bytes());
|
||||||
|
bytes[64..96].copy_from_slice(&self.auditor.to_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||||
|
let bytes = array_ref![bytes, 0, 96];
|
||||||
|
let (source, dest, auditor) = array_refs![bytes, 32, 32, 32];
|
||||||
|
|
||||||
|
let source = ElGamalPubkey::from_bytes(source).ok_or(ProofError::Verification)?;
|
||||||
|
let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?;
|
||||||
|
let auditor = ElGamalPubkey::from_bytes(auditor).ok_or(ProofError::Verification)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
auditor,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn combine_u32_ciphertexts(ct_lo: ElGamalCiphertext, ct_hi: ElGamalCiphertext) -> ElGamalCiphertext {
|
impl pod::TransferPubkeys {
|
||||||
ct_lo + ct_hi * Scalar::from(TWO_32)
|
pub fn new(source: &ElGamalPubkey, dest: &ElGamalPubkey, auditor: &ElGamalPubkey) -> Self {
|
||||||
}*/
|
let mut bytes = [0u8; 96];
|
||||||
|
bytes[..32].copy_from_slice(&source.to_bytes());
|
||||||
|
bytes[32..64].copy_from_slice(&dest.to_bytes());
|
||||||
|
bytes[64..96].copy_from_slice(&auditor.to_bytes());
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
@ -494,7 +453,7 @@ mod test {
|
||||||
|
|
||||||
// create source account spendable ciphertext
|
// create source account spendable ciphertext
|
||||||
let spendable_balance: u64 = 77;
|
let spendable_balance: u64 = 77;
|
||||||
let spendable_ct = source_keypair.public.encrypt(spendable_balance);
|
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
||||||
|
|
||||||
// transfer amount
|
// transfer amount
|
||||||
let transfer_amount: u64 = 55;
|
let transfer_amount: u64 = 55;
|
||||||
|
@ -502,12 +461,11 @@ mod test {
|
||||||
// create transfer data
|
// create transfer data
|
||||||
let transfer_data = TransferData::new(
|
let transfer_data = TransferData::new(
|
||||||
transfer_amount,
|
transfer_amount,
|
||||||
spendable_balance,
|
(spendable_balance, &spendable_ciphertext),
|
||||||
spendable_ct,
|
|
||||||
&source_keypair,
|
&source_keypair,
|
||||||
dest_pk,
|
(&dest_pk, &auditor_pk),
|
||||||
auditor_pk,
|
)
|
||||||
);
|
.unwrap();
|
||||||
|
|
||||||
assert!(transfer_data.verify().is_ok());
|
assert!(transfer_data.verify().is_ok());
|
||||||
}
|
}
|
||||||
|
@ -529,7 +487,7 @@ mod test {
|
||||||
|
|
||||||
// create source account spendable ciphertext
|
// create source account spendable ciphertext
|
||||||
let spendable_balance: u64 = 77;
|
let spendable_balance: u64 = 77;
|
||||||
let spendable_ct = source_keypair.public.encrypt(spendable_balance);
|
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
||||||
|
|
||||||
// transfer amount
|
// transfer amount
|
||||||
let transfer_amount: u64 = 55;
|
let transfer_amount: u64 = 55;
|
||||||
|
@ -537,12 +495,11 @@ mod test {
|
||||||
// create transfer data
|
// create transfer data
|
||||||
let transfer_data = TransferData::new(
|
let transfer_data = TransferData::new(
|
||||||
transfer_amount,
|
transfer_amount,
|
||||||
spendable_balance,
|
(spendable_balance, &spendable_ciphertext),
|
||||||
spendable_ct,
|
|
||||||
&source_keypair,
|
&source_keypair,
|
||||||
dest_pk,
|
(&dest_pk, &auditor_pk),
|
||||||
auditor_pk,
|
)
|
||||||
);
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
transfer_data
|
transfer_data
|
||||||
|
|
|
@ -0,0 +1,690 @@
|
||||||
|
use {
|
||||||
|
crate::zk_token_elgamal::pod,
|
||||||
|
bytemuck::{Pod, Zeroable},
|
||||||
|
};
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
encryption::{
|
||||||
|
discrete_log::*,
|
||||||
|
elgamal::{
|
||||||
|
DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey,
|
||||||
|
},
|
||||||
|
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
|
||||||
|
},
|
||||||
|
errors::ProofError,
|
||||||
|
instruction::{
|
||||||
|
combine_u32_ciphertexts, combine_u32_commitments, combine_u32_openings,
|
||||||
|
split_u64_into_u32, transfer::TransferAmountEncryption, Role, Verifiable, TWO_32,
|
||||||
|
},
|
||||||
|
range_proof::RangeProof,
|
||||||
|
sigma_proofs::{
|
||||||
|
equality_proof::EqualityProof,
|
||||||
|
fee_proof::FeeSigmaProof,
|
||||||
|
validity_proof::{AggregatedValidityProof, ValidityProof},
|
||||||
|
},
|
||||||
|
transcript::TranscriptProtocol,
|
||||||
|
},
|
||||||
|
arrayref::{array_ref, array_refs},
|
||||||
|
curve25519_dalek::scalar::Scalar,
|
||||||
|
merlin::Transcript,
|
||||||
|
std::convert::TryInto,
|
||||||
|
subtle::{ConditionallySelectable, ConstantTimeGreater},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
const FEE_DENOMINATOR: u64 = 10000;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref COMMITMENT_FEE_DENOMINATOR: PedersenCommitment = Pedersen::encode(FEE_DENOMINATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct TransferWithFeeData {
|
||||||
|
/// Group encryption of the low 32 bites of the transfer amount
|
||||||
|
pub ciphertext_lo: pod::TransferAmountEncryption,
|
||||||
|
|
||||||
|
/// Group encryption of the high 32 bits of the transfer amount
|
||||||
|
pub ciphertext_hi: pod::TransferAmountEncryption,
|
||||||
|
|
||||||
|
/// The public encryption keys associated with the transfer: source, dest, and auditor
|
||||||
|
pub transfer_with_fee_pubkeys: pod::TransferWithFeePubkeys,
|
||||||
|
|
||||||
|
/// The final spendable ciphertext after the transfer,
|
||||||
|
pub ciphertext_new_source: pod::ElGamalCiphertext,
|
||||||
|
|
||||||
|
// transfer fee encryption
|
||||||
|
pub ciphertext_fee: pod::FeeEncryption,
|
||||||
|
|
||||||
|
// fee parameters
|
||||||
|
pub fee_parameters: pod::FeeParameters,
|
||||||
|
|
||||||
|
// transfer fee proof
|
||||||
|
pub proof: TransferWithFeeProof,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl TransferWithFeeData {
|
||||||
|
pub fn new(
|
||||||
|
transfer_amount: u64,
|
||||||
|
(spendable_balance, ciphertext_old_source): (u64, &ElGamalCiphertext),
|
||||||
|
keypair_source: &ElGamalKeypair,
|
||||||
|
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
|
fee_parameters: FeeParameters,
|
||||||
|
pubkey_fee_collector: &ElGamalPubkey,
|
||||||
|
) -> Result<Self, ProofError> {
|
||||||
|
// split and encrypt transfer amount
|
||||||
|
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
|
||||||
|
|
||||||
|
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
||||||
|
amount_lo,
|
||||||
|
&keypair_source.public,
|
||||||
|
pubkey_dest,
|
||||||
|
pubkey_auditor,
|
||||||
|
);
|
||||||
|
let (ciphertext_hi, opening_hi) = TransferAmountEncryption::new(
|
||||||
|
amount_hi,
|
||||||
|
&keypair_source.public,
|
||||||
|
pubkey_dest,
|
||||||
|
pubkey_auditor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// subtract transfer amount from the spendable ciphertext
|
||||||
|
let new_spendable_balance = spendable_balance
|
||||||
|
.checked_sub(transfer_amount)
|
||||||
|
.ok_or(ProofError::Generation)?;
|
||||||
|
|
||||||
|
let transfer_amount_lo_source = ElGamalCiphertext {
|
||||||
|
commitment: ciphertext_lo.commitment,
|
||||||
|
handle: ciphertext_lo.source,
|
||||||
|
};
|
||||||
|
|
||||||
|
let transfer_amount_hi_source = ElGamalCiphertext {
|
||||||
|
commitment: ciphertext_hi.commitment,
|
||||||
|
handle: ciphertext_hi.source,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ciphertext_new_source = ciphertext_old_source
|
||||||
|
- combine_u32_ciphertexts(&transfer_amount_lo_source, &transfer_amount_hi_source);
|
||||||
|
|
||||||
|
// calculate and encrypt fee
|
||||||
|
let (fee_amount, delta_fee) =
|
||||||
|
calculate_fee(transfer_amount, fee_parameters.fee_rate_basis_points);
|
||||||
|
|
||||||
|
let below_max = u64::ct_gt(&fee_parameters.maximum_fee, &fee_amount);
|
||||||
|
let fee_to_encrypt =
|
||||||
|
u64::conditional_select(&fee_parameters.maximum_fee, &fee_amount, below_max);
|
||||||
|
// u64::conditional_select(&fee_amount, &fee_parameters.maximum_fee, below_max);
|
||||||
|
|
||||||
|
let (ciphertext_fee, opening_fee) =
|
||||||
|
FeeEncryption::new(fee_to_encrypt, pubkey_dest, pubkey_fee_collector);
|
||||||
|
|
||||||
|
// generate transcript and append all public inputs
|
||||||
|
let pod_transfer_with_fee_pubkeys = pod::TransferWithFeePubkeys::new(
|
||||||
|
&keypair_source.public,
|
||||||
|
pubkey_dest,
|
||||||
|
pubkey_auditor,
|
||||||
|
pubkey_fee_collector,
|
||||||
|
);
|
||||||
|
let pod_ciphertext_lo = pod::TransferAmountEncryption(ciphertext_lo.to_bytes());
|
||||||
|
let pod_ciphertext_hi = pod::TransferAmountEncryption(ciphertext_hi.to_bytes());
|
||||||
|
let pod_ciphertext_new_source: pod::ElGamalCiphertext = ciphertext_new_source.into();
|
||||||
|
let pod_ciphertext_fee = pod::FeeEncryption(ciphertext_fee.to_bytes());
|
||||||
|
|
||||||
|
let mut transcript = TransferWithFeeProof::transcript_new(
|
||||||
|
&pod_transfer_with_fee_pubkeys,
|
||||||
|
&pod_ciphertext_lo,
|
||||||
|
&pod_ciphertext_hi,
|
||||||
|
&pod_ciphertext_fee,
|
||||||
|
);
|
||||||
|
|
||||||
|
let proof = TransferWithFeeProof::new(
|
||||||
|
(amount_lo, &ciphertext_lo, &opening_lo),
|
||||||
|
(amount_hi, &ciphertext_hi, &opening_hi),
|
||||||
|
keypair_source,
|
||||||
|
(pubkey_dest, pubkey_auditor),
|
||||||
|
(new_spendable_balance, &ciphertext_new_source),
|
||||||
|
(fee_amount, &ciphertext_fee, &opening_fee),
|
||||||
|
delta_fee,
|
||||||
|
pubkey_fee_collector,
|
||||||
|
fee_parameters,
|
||||||
|
&mut transcript,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
ciphertext_lo: pod_ciphertext_lo,
|
||||||
|
ciphertext_hi: pod_ciphertext_hi,
|
||||||
|
transfer_with_fee_pubkeys: pod_transfer_with_fee_pubkeys,
|
||||||
|
ciphertext_new_source: pod_ciphertext_new_source,
|
||||||
|
ciphertext_fee: pod_ciphertext_fee,
|
||||||
|
fee_parameters: fee_parameters.into(),
|
||||||
|
proof,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the lo ciphertexts associated with a transfer-with-fee data
|
||||||
|
fn ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
|
||||||
|
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
|
||||||
|
|
||||||
|
let handle_lo = match role {
|
||||||
|
Role::Source => ciphertext_lo.source,
|
||||||
|
Role::Dest => ciphertext_lo.dest,
|
||||||
|
Role::Auditor => ciphertext_lo.auditor,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ElGamalCiphertext {
|
||||||
|
commitment: ciphertext_lo.commitment,
|
||||||
|
handle: handle_lo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the lo ciphertexts associated with a transfer-with-fee data
|
||||||
|
fn ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
|
||||||
|
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
|
||||||
|
|
||||||
|
let handle_hi = match role {
|
||||||
|
Role::Source => ciphertext_hi.source,
|
||||||
|
Role::Dest => ciphertext_hi.dest,
|
||||||
|
Role::Auditor => ciphertext_hi.auditor,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ElGamalCiphertext {
|
||||||
|
commitment: ciphertext_hi.commitment,
|
||||||
|
handle: handle_hi,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts transfer amount from transfer-with-fee data
|
||||||
|
///
|
||||||
|
/// TODO: This function should run in constant time. Use `subtle::Choice` for the if statement
|
||||||
|
/// and make sure that the function does not terminate prematurely due to errors
|
||||||
|
///
|
||||||
|
/// TODO: Define specific error type for decryption error
|
||||||
|
pub fn decrypt_amount(&self, role: Role, sk: &ElGamalSecretKey) -> Result<u64, ProofError> {
|
||||||
|
let ciphertext_lo = self.ciphertext_lo(role)?;
|
||||||
|
let ciphertext_hi = self.ciphertext_hi(role)?;
|
||||||
|
|
||||||
|
let amount_lo = ciphertext_lo.decrypt_u32_online(sk, &DECODE_U32_PRECOMPUTATION_FOR_G);
|
||||||
|
let amount_hi = ciphertext_hi.decrypt_u32_online(sk, &DECODE_U32_PRECOMPUTATION_FOR_G);
|
||||||
|
|
||||||
|
if let (Some(amount_lo), Some(amount_hi)) = (amount_lo, amount_hi) {
|
||||||
|
Ok((amount_lo as u64) + (TWO_32 * amount_hi as u64))
|
||||||
|
} else {
|
||||||
|
Err(ProofError::Verification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl Verifiable for TransferWithFeeData {
|
||||||
|
fn verify(&self) -> Result<(), ProofError> {
|
||||||
|
let mut transcript = TransferWithFeeProof::transcript_new(
|
||||||
|
&self.transfer_with_fee_pubkeys,
|
||||||
|
&self.ciphertext_lo,
|
||||||
|
&self.ciphertext_hi,
|
||||||
|
&self.ciphertext_fee,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ciphertext_lo = self.ciphertext_lo.try_into()?;
|
||||||
|
let ciphertext_hi = self.ciphertext_hi.try_into()?;
|
||||||
|
let transfer_with_fee_pubkeys = self.transfer_with_fee_pubkeys.try_into()?;
|
||||||
|
let new_spendable_ciphertext = self.ciphertext_new_source.try_into()?;
|
||||||
|
|
||||||
|
let ciphertext_fee = self.ciphertext_fee.try_into()?;
|
||||||
|
let fee_parameters = self.fee_parameters.into();
|
||||||
|
|
||||||
|
self.proof.verify(
|
||||||
|
&ciphertext_lo,
|
||||||
|
&ciphertext_hi,
|
||||||
|
&transfer_with_fee_pubkeys,
|
||||||
|
&new_spendable_ciphertext,
|
||||||
|
&ciphertext_fee,
|
||||||
|
fee_parameters,
|
||||||
|
&mut transcript,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
pub struct TransferWithFeeProof {
|
||||||
|
pub commitment_new_source: pod::PedersenCommitment,
|
||||||
|
pub commitment_claimed: pod::PedersenCommitment,
|
||||||
|
pub equality_proof: pod::EqualityProof,
|
||||||
|
pub ciphertext_amount_validity_proof: pod::AggregatedValidityProof,
|
||||||
|
pub fee_sigma_proof: pod::FeeSigmaProof,
|
||||||
|
pub ciphertext_fee_validity_proof: pod::ValidityProof,
|
||||||
|
pub range_proof: pod::RangeProof256,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl TransferWithFeeProof {
|
||||||
|
fn transcript_new(
|
||||||
|
transfer_with_fee_pubkeys: &pod::TransferWithFeePubkeys,
|
||||||
|
ciphertext_lo: &pod::TransferAmountEncryption,
|
||||||
|
ciphertext_hi: &pod::TransferAmountEncryption,
|
||||||
|
ciphertext_fee: &pod::FeeEncryption,
|
||||||
|
) -> Transcript {
|
||||||
|
let mut transcript = Transcript::new(b"FeeProof");
|
||||||
|
|
||||||
|
transcript.append_message(b"transfer-with-fee-pubkeys", &transfer_with_fee_pubkeys.0);
|
||||||
|
transcript.append_message(b"ciphertext-lo", &ciphertext_lo.0);
|
||||||
|
transcript.append_message(b"ciphertext-hi", &ciphertext_hi.0);
|
||||||
|
transcript.append_message(b"ciphertext-fee", &ciphertext_fee.0);
|
||||||
|
|
||||||
|
transcript
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[allow(clippy::many_single_char_names)]
|
||||||
|
pub fn new(
|
||||||
|
transfer_amount_lo_data: (u32, &TransferAmountEncryption, &PedersenOpening),
|
||||||
|
transfer_amount_hi_data: (u32, &TransferAmountEncryption, &PedersenOpening),
|
||||||
|
keypair_source: &ElGamalKeypair,
|
||||||
|
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
|
(source_new_balance, ciphertext_new_source): (u64, &ElGamalCiphertext),
|
||||||
|
|
||||||
|
(fee_amount, ciphertext_fee, opening_fee): (u64, &FeeEncryption, &PedersenOpening),
|
||||||
|
delta_fee: u64,
|
||||||
|
pubkey_fee_collector: &ElGamalPubkey,
|
||||||
|
fee_parameters: FeeParameters,
|
||||||
|
transcript: &mut Transcript,
|
||||||
|
) -> Self {
|
||||||
|
let (transfer_amount_lo, ciphertext_lo, opening_lo) = transfer_amount_lo_data;
|
||||||
|
let (transfer_amount_hi, ciphertext_hi, opening_hi) = transfer_amount_hi_data;
|
||||||
|
|
||||||
|
// generate a Pedersen commitment for the remaining balance in source
|
||||||
|
let (commitment_new_source, opening_source) = Pedersen::new(source_new_balance);
|
||||||
|
let (commitment_claimed, opening_claimed) = Pedersen::new(delta_fee);
|
||||||
|
|
||||||
|
let pod_commitment_new_source: pod::PedersenCommitment = commitment_new_source.into();
|
||||||
|
let pod_commitment_claimed: pod::PedersenCommitment = commitment_claimed.into();
|
||||||
|
|
||||||
|
transcript.append_commitment(b"commitment-new-source", &pod_commitment_new_source);
|
||||||
|
transcript.append_commitment(b"commitment-claimed", &pod_commitment_claimed);
|
||||||
|
|
||||||
|
// generate equality_proof
|
||||||
|
let equality_proof = EqualityProof::new(
|
||||||
|
keypair_source,
|
||||||
|
ciphertext_new_source,
|
||||||
|
source_new_balance,
|
||||||
|
&opening_source,
|
||||||
|
transcript,
|
||||||
|
);
|
||||||
|
|
||||||
|
// generate ciphertext validity proof
|
||||||
|
let ciphertext_amount_validity_proof = AggregatedValidityProof::new(
|
||||||
|
(pubkey_dest, pubkey_auditor),
|
||||||
|
(transfer_amount_lo, transfer_amount_hi),
|
||||||
|
(opening_lo, opening_hi),
|
||||||
|
transcript,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (commitment_delta, opening_delta) = compute_delta_commitment_and_opening(
|
||||||
|
(&ciphertext_lo.commitment, opening_lo),
|
||||||
|
(&ciphertext_hi.commitment, opening_hi),
|
||||||
|
(&ciphertext_fee.commitment, opening_fee),
|
||||||
|
fee_parameters.fee_rate_basis_points,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fee_sigma_proof = FeeSigmaProof::new(
|
||||||
|
(fee_amount, &ciphertext_fee.commitment, opening_fee),
|
||||||
|
(delta_fee, &commitment_delta, &opening_delta),
|
||||||
|
(&commitment_claimed, &opening_claimed),
|
||||||
|
fee_parameters.maximum_fee,
|
||||||
|
transcript,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ciphertext_fee_validity_proof = ValidityProof::new(
|
||||||
|
(pubkey_dest, pubkey_fee_collector),
|
||||||
|
fee_amount,
|
||||||
|
opening_fee,
|
||||||
|
transcript,
|
||||||
|
);
|
||||||
|
|
||||||
|
let opening_claimed_negated = &PedersenOpening::default() - &opening_claimed;
|
||||||
|
let range_proof = RangeProof::new(
|
||||||
|
vec![
|
||||||
|
source_new_balance,
|
||||||
|
transfer_amount_lo as u64,
|
||||||
|
transfer_amount_hi as u64,
|
||||||
|
delta_fee,
|
||||||
|
FEE_DENOMINATOR - delta_fee,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
64, 32, 32, 64, // double check
|
||||||
|
64,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
&opening_source,
|
||||||
|
opening_lo,
|
||||||
|
opening_hi,
|
||||||
|
&opening_claimed,
|
||||||
|
&opening_claimed_negated,
|
||||||
|
],
|
||||||
|
transcript,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
commitment_new_source: pod_commitment_new_source,
|
||||||
|
commitment_claimed: pod_commitment_claimed,
|
||||||
|
equality_proof: equality_proof.into(),
|
||||||
|
ciphertext_amount_validity_proof: ciphertext_amount_validity_proof.into(),
|
||||||
|
fee_sigma_proof: fee_sigma_proof.into(),
|
||||||
|
ciphertext_fee_validity_proof: ciphertext_fee_validity_proof.into(),
|
||||||
|
range_proof: range_proof.try_into().expect("range proof: length error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(
|
||||||
|
&self,
|
||||||
|
ciphertext_lo: &TransferAmountEncryption,
|
||||||
|
ciphertext_hi: &TransferAmountEncryption,
|
||||||
|
transfer_with_fee_pubkeys: &TransferWithFeePubkeys,
|
||||||
|
new_spendable_ciphertext: &ElGamalCiphertext,
|
||||||
|
|
||||||
|
ciphertext_fee: &FeeEncryption,
|
||||||
|
fee_parameters: FeeParameters,
|
||||||
|
transcript: &mut Transcript,
|
||||||
|
) -> Result<(), ProofError> {
|
||||||
|
transcript.append_commitment(b"commitment-new-source", &self.commitment_new_source);
|
||||||
|
transcript.append_commitment(b"commitment-claimed", &self.commitment_claimed);
|
||||||
|
|
||||||
|
let commitment_new_source: PedersenCommitment = self.commitment_new_source.try_into()?;
|
||||||
|
let commitment_claimed: PedersenCommitment = self.commitment_claimed.try_into()?;
|
||||||
|
|
||||||
|
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
|
||||||
|
let ciphertext_amount_validity_proof: AggregatedValidityProof =
|
||||||
|
self.ciphertext_amount_validity_proof.try_into()?;
|
||||||
|
let fee_sigma_proof: FeeSigmaProof = self.fee_sigma_proof.try_into()?;
|
||||||
|
let ciphertext_fee_validity_proof: ValidityProof =
|
||||||
|
self.ciphertext_fee_validity_proof.try_into()?;
|
||||||
|
let range_proof: RangeProof = self.range_proof.try_into()?;
|
||||||
|
|
||||||
|
// verify equality proof
|
||||||
|
equality_proof.verify(
|
||||||
|
&transfer_with_fee_pubkeys.source,
|
||||||
|
new_spendable_ciphertext,
|
||||||
|
&commitment_new_source,
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// verify that the transfer amount is encrypted correctly
|
||||||
|
ciphertext_amount_validity_proof.verify(
|
||||||
|
(
|
||||||
|
&transfer_with_fee_pubkeys.dest,
|
||||||
|
&transfer_with_fee_pubkeys.auditor,
|
||||||
|
),
|
||||||
|
(&ciphertext_lo.commitment, &ciphertext_hi.commitment),
|
||||||
|
(&ciphertext_lo.dest, &ciphertext_hi.dest),
|
||||||
|
(&ciphertext_lo.auditor, &ciphertext_hi.auditor),
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// verify fee sigma proof
|
||||||
|
let commitment_delta = compute_delta_commitment(
|
||||||
|
&ciphertext_lo.commitment,
|
||||||
|
&ciphertext_hi.commitment,
|
||||||
|
&ciphertext_fee.commitment,
|
||||||
|
fee_parameters.fee_rate_basis_points,
|
||||||
|
);
|
||||||
|
|
||||||
|
fee_sigma_proof.verify(
|
||||||
|
&ciphertext_fee.commitment,
|
||||||
|
&commitment_delta,
|
||||||
|
&commitment_claimed,
|
||||||
|
fee_parameters.maximum_fee,
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ciphertext_fee_validity_proof.verify(
|
||||||
|
&ciphertext_fee.commitment,
|
||||||
|
(
|
||||||
|
&transfer_with_fee_pubkeys.dest,
|
||||||
|
&transfer_with_fee_pubkeys.fee_collector,
|
||||||
|
),
|
||||||
|
(&ciphertext_fee.dest, &ciphertext_fee.fee_collector),
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let commitment_claimed_negated = &(*COMMITMENT_FEE_DENOMINATOR) - &commitment_claimed;
|
||||||
|
range_proof.verify(
|
||||||
|
vec![
|
||||||
|
&commitment_new_source,
|
||||||
|
&ciphertext_lo.commitment,
|
||||||
|
&ciphertext_hi.commitment,
|
||||||
|
&commitment_claimed,
|
||||||
|
&commitment_claimed_negated,
|
||||||
|
],
|
||||||
|
vec![64, 32, 32, 64, 64],
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ElGamal public keys needed for a transfer with fee
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
pub struct TransferWithFeePubkeys {
|
||||||
|
pub source: ElGamalPubkey,
|
||||||
|
pub dest: ElGamalPubkey,
|
||||||
|
pub auditor: ElGamalPubkey,
|
||||||
|
pub fee_collector: ElGamalPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl TransferWithFeePubkeys {
|
||||||
|
pub fn to_bytes(&self) -> [u8; 128] {
|
||||||
|
let mut bytes = [0u8; 128];
|
||||||
|
bytes[..32].copy_from_slice(&self.source.to_bytes());
|
||||||
|
bytes[32..64].copy_from_slice(&self.dest.to_bytes());
|
||||||
|
bytes[64..96].copy_from_slice(&self.auditor.to_bytes());
|
||||||
|
bytes[96..128].copy_from_slice(&self.fee_collector.to_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||||
|
let bytes = array_ref![bytes, 0, 128];
|
||||||
|
let (source, dest, auditor, fee_collector) = array_refs![bytes, 32, 32, 32, 32];
|
||||||
|
|
||||||
|
let source = ElGamalPubkey::from_bytes(source).ok_or(ProofError::Verification)?;
|
||||||
|
let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?;
|
||||||
|
let auditor = ElGamalPubkey::from_bytes(auditor).ok_or(ProofError::Verification)?;
|
||||||
|
let fee_collector =
|
||||||
|
ElGamalPubkey::from_bytes(fee_collector).ok_or(ProofError::Verification)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
auditor,
|
||||||
|
fee_collector,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl pod::TransferWithFeePubkeys {
|
||||||
|
pub fn new(
|
||||||
|
source: &ElGamalPubkey,
|
||||||
|
dest: &ElGamalPubkey,
|
||||||
|
auditor: &ElGamalPubkey,
|
||||||
|
fee_collector: &ElGamalPubkey,
|
||||||
|
) -> Self {
|
||||||
|
let mut bytes = [0u8; 128];
|
||||||
|
bytes[..32].copy_from_slice(&source.to_bytes());
|
||||||
|
bytes[32..64].copy_from_slice(&dest.to_bytes());
|
||||||
|
bytes[64..96].copy_from_slice(&auditor.to_bytes());
|
||||||
|
bytes[96..128].copy_from_slice(&fee_collector.to_bytes());
|
||||||
|
Self(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
pub struct FeeEncryption {
|
||||||
|
pub commitment: PedersenCommitment,
|
||||||
|
pub dest: DecryptHandle,
|
||||||
|
pub fee_collector: DecryptHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl FeeEncryption {
|
||||||
|
pub fn new(
|
||||||
|
amount: u64,
|
||||||
|
pubkey_dest: &ElGamalPubkey,
|
||||||
|
pubkey_fee_collector: &ElGamalPubkey,
|
||||||
|
) -> (Self, PedersenOpening) {
|
||||||
|
let (commitment, opening) = Pedersen::new(amount);
|
||||||
|
let fee_encryption = Self {
|
||||||
|
commitment,
|
||||||
|
dest: pubkey_dest.decrypt_handle(&opening),
|
||||||
|
fee_collector: pubkey_fee_collector.decrypt_handle(&opening),
|
||||||
|
};
|
||||||
|
|
||||||
|
(fee_encryption, opening)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 96] {
|
||||||
|
let mut bytes = [0u8; 96];
|
||||||
|
bytes[..32].copy_from_slice(&self.commitment.to_bytes());
|
||||||
|
bytes[32..64].copy_from_slice(&self.dest.to_bytes());
|
||||||
|
bytes[64..96].copy_from_slice(&self.fee_collector.to_bytes());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||||
|
let bytes = array_ref![bytes, 0, 96];
|
||||||
|
let (commitment, dest, fee_collector) = array_refs![bytes, 32, 32, 32];
|
||||||
|
|
||||||
|
let commitment =
|
||||||
|
PedersenCommitment::from_bytes(commitment).ok_or(ProofError::Verification)?;
|
||||||
|
let dest = DecryptHandle::from_bytes(dest).ok_or(ProofError::Verification)?;
|
||||||
|
let fee_collector =
|
||||||
|
DecryptHandle::from_bytes(fee_collector).ok_or(ProofError::Verification)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
commitment,
|
||||||
|
dest,
|
||||||
|
fee_collector,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FeeParameters {
|
||||||
|
/// Fee rate expressed as basis points of the transfer amount, i.e. increments of 0.01%
|
||||||
|
pub fee_rate_basis_points: u16,
|
||||||
|
/// Maximum fee assessed on transfers, expressed as an amount of tokens
|
||||||
|
pub maximum_fee: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl FeeParameters {
|
||||||
|
pub fn to_bytes(&self) -> [u8; 10] {
|
||||||
|
let mut bytes = [0u8; 10];
|
||||||
|
bytes[..2].copy_from_slice(&self.fee_rate_basis_points.to_le_bytes());
|
||||||
|
bytes[2..10].copy_from_slice(&self.maximum_fee.to_le_bytes());
|
||||||
|
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Self {
|
||||||
|
let bytes = array_ref![bytes, 0, 10];
|
||||||
|
let (fee_rate_basis_points, maximum_fee) = array_refs![bytes, 2, 8];
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fee_rate_basis_points: u16::from_le_bytes(*fee_rate_basis_points),
|
||||||
|
maximum_fee: u64::from_le_bytes(*maximum_fee),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
fn calculate_fee(transfer_amount: u64, fee_rate_basis_points: u16) -> (u64, u64) {
|
||||||
|
let fee_scaled = (transfer_amount as u128) * (fee_rate_basis_points as u128);
|
||||||
|
|
||||||
|
let fee = (fee_scaled / FEE_DENOMINATOR as u128) as u64;
|
||||||
|
let rem = (fee_scaled % FEE_DENOMINATOR as u128) as u64;
|
||||||
|
|
||||||
|
if rem == 0 {
|
||||||
|
(fee, rem)
|
||||||
|
} else {
|
||||||
|
(fee + 1, rem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
fn compute_delta_commitment_and_opening(
|
||||||
|
(commitment_lo, opening_lo): (&PedersenCommitment, &PedersenOpening),
|
||||||
|
(commitment_hi, opening_hi): (&PedersenCommitment, &PedersenOpening),
|
||||||
|
(commitment_fee, opening_fee): (&PedersenCommitment, &PedersenOpening),
|
||||||
|
fee_rate_basis_points: u16,
|
||||||
|
) -> (PedersenCommitment, PedersenOpening) {
|
||||||
|
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
|
||||||
|
|
||||||
|
let commitment_delta = commitment_fee * Scalar::from(FEE_DENOMINATOR)
|
||||||
|
- &(&combine_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar);
|
||||||
|
|
||||||
|
let opening_delta = opening_fee * Scalar::from(FEE_DENOMINATOR)
|
||||||
|
- &(&combine_u32_openings(opening_lo, opening_hi) * &fee_rate_scalar);
|
||||||
|
|
||||||
|
(commitment_delta, opening_delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
fn compute_delta_commitment(
|
||||||
|
commitment_lo: &PedersenCommitment,
|
||||||
|
commitment_hi: &PedersenCommitment,
|
||||||
|
commitment_fee: &PedersenCommitment,
|
||||||
|
fee_rate_basis_points: u16,
|
||||||
|
) -> PedersenCommitment {
|
||||||
|
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
|
||||||
|
|
||||||
|
commitment_fee * Scalar::from(FEE_DENOMINATOR)
|
||||||
|
- &(&combine_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fee_correctness() {
|
||||||
|
let keypair_source = ElGamalKeypair::new_rand();
|
||||||
|
let pubkey_dest = ElGamalKeypair::new_rand().public;
|
||||||
|
let pubkey_auditor = ElGamalKeypair::new_rand().public;
|
||||||
|
let pubkey_fee_collector = ElGamalKeypair::new_rand().public;
|
||||||
|
|
||||||
|
let spendable_balance: u64 = 120;
|
||||||
|
let spendable_ciphertext = keypair_source.public.encrypt(spendable_balance);
|
||||||
|
|
||||||
|
let transfer_amount: u64 = 100;
|
||||||
|
|
||||||
|
let fee_parameters = FeeParameters {
|
||||||
|
fee_rate_basis_points: 100,
|
||||||
|
maximum_fee: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fee_data = TransferWithFeeData::new(
|
||||||
|
transfer_amount,
|
||||||
|
(spendable_balance, &spendable_ciphertext),
|
||||||
|
&keypair_source,
|
||||||
|
(&pubkey_dest, &pubkey_auditor),
|
||||||
|
fee_parameters,
|
||||||
|
&pubkey_fee_collector,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(fee_data.verify().is_ok());
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,8 @@ use {
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
encryption::{
|
encryption::{
|
||||||
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
|
elgamal::{ElGamal, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
|
||||||
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
|
pedersen::{Pedersen, PedersenCommitment},
|
||||||
},
|
},
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
instruction::Verifiable,
|
instruction::Verifiable,
|
||||||
|
@ -30,11 +30,11 @@ use {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct WithdrawData {
|
pub struct WithdrawData {
|
||||||
/// The source account ElGamal pubkey
|
/// The source account ElGamal pubkey
|
||||||
pub elgamal_pubkey: pod::ElGamalPubkey, // 32 bytes
|
pub pubkey: pod::ElGamalPubkey, // 32 bytes
|
||||||
|
|
||||||
/// The source account available balance *after* the withdraw (encrypted by
|
/// The source account available balance *after* the withdraw (encrypted by
|
||||||
/// `source_pk`
|
/// `source_pk`
|
||||||
pub final_balance_ct: pod::ElGamalCiphertext, // 64 bytes
|
pub final_ciphertext: pod::ElGamalCiphertext, // 64 bytes
|
||||||
|
|
||||||
/// Range proof
|
/// Range proof
|
||||||
pub proof: WithdrawProof, // 736 bytes
|
pub proof: WithdrawProof, // 736 bytes
|
||||||
|
@ -44,38 +44,43 @@ impl WithdrawData {
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
amount: u64,
|
amount: u64,
|
||||||
source_keypair: &ElGamalKeypair,
|
keypair: &ElGamalKeypair,
|
||||||
current_balance: u64,
|
current_balance: u64,
|
||||||
current_balance_ct: ElGamalCiphertext,
|
current_ciphertext: &ElGamalCiphertext,
|
||||||
) -> Self {
|
) -> Result<Self, ProofError> {
|
||||||
// subtract withdraw amount from current balance
|
// subtract withdraw amount from current balance
|
||||||
//
|
//
|
||||||
// panics if current_balance < amount
|
// errors if current_balance < amount
|
||||||
let final_balance = current_balance - amount;
|
let final_balance = current_balance
|
||||||
|
.checked_sub(amount)
|
||||||
|
.ok_or(ProofError::Generation)?;
|
||||||
|
|
||||||
// encode withdraw amount as an ElGamal ciphertext and subtract it from
|
// encode withdraw amount as an ElGamal ciphertext and subtract it from
|
||||||
// current source balance
|
// current source balance
|
||||||
let amount_encoded = source_keypair
|
let final_ciphertext = current_ciphertext - &ElGamal::encode(amount);
|
||||||
.public
|
|
||||||
.encrypt_with(amount, &PedersenOpening::default());
|
|
||||||
let final_balance_ct = current_balance_ct - amount_encoded;
|
|
||||||
|
|
||||||
let proof = WithdrawProof::new(source_keypair, final_balance, &final_balance_ct);
|
let pod_pubkey = pod::ElGamalPubkey((&keypair.public).to_bytes());
|
||||||
|
let pod_final_ciphertext: pod::ElGamalCiphertext = final_ciphertext.into();
|
||||||
|
let mut transcript = WithdrawProof::transcript_new(&pod_pubkey, &pod_final_ciphertext);
|
||||||
|
let proof = WithdrawProof::new(keypair, final_balance, &final_ciphertext, &mut transcript);
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
elgamal_pubkey: source_keypair.public.into(),
|
pubkey: pod_pubkey,
|
||||||
final_balance_ct: final_balance_ct.into(),
|
final_ciphertext: pod_final_ciphertext,
|
||||||
proof,
|
proof,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl Verifiable for WithdrawData {
|
impl Verifiable for WithdrawData {
|
||||||
fn verify(&self) -> Result<(), ProofError> {
|
fn verify(&self) -> Result<(), ProofError> {
|
||||||
let elgamal_pubkey = self.elgamal_pubkey.try_into()?;
|
let mut transcript = WithdrawProof::transcript_new(&self.pubkey, &self.final_ciphertext);
|
||||||
let final_balance_ct = self.final_balance_ct.try_into()?;
|
|
||||||
self.proof.verify(&elgamal_pubkey, &final_balance_ct)
|
let elgamal_pubkey = self.pubkey.try_into()?;
|
||||||
|
let final_balance_ciphertext = self.final_ciphertext.try_into()?;
|
||||||
|
self.proof
|
||||||
|
.verify(&elgamal_pubkey, &final_balance_ciphertext, &mut transcript)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,53 +103,44 @@ pub struct WithdrawProof {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl WithdrawProof {
|
impl WithdrawProof {
|
||||||
fn transcript_new() -> Transcript {
|
fn transcript_new(
|
||||||
Transcript::new(b"WithdrawProof")
|
pubkey: &pod::ElGamalPubkey,
|
||||||
|
ciphertext: &pod::ElGamalCiphertext,
|
||||||
|
) -> Transcript {
|
||||||
|
let mut transcript = Transcript::new(b"WithdrawProof");
|
||||||
|
|
||||||
|
transcript.append_pubkey(b"pubkey", pubkey);
|
||||||
|
transcript.append_ciphertext(b"ciphertext", ciphertext);
|
||||||
|
|
||||||
|
transcript
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
source_keypair: &ElGamalKeypair,
|
keypair: &ElGamalKeypair,
|
||||||
final_balance: u64,
|
final_balance: u64,
|
||||||
final_balance_ct: &ElGamalCiphertext,
|
final_ciphertext: &ElGamalCiphertext,
|
||||||
|
transcript: &mut Transcript,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut transcript = Self::transcript_new();
|
|
||||||
|
|
||||||
// add a domain separator to record the start of the protocol
|
|
||||||
transcript.withdraw_proof_domain_sep();
|
|
||||||
|
|
||||||
// generate a Pedersen commitment for `final_balance`
|
// generate a Pedersen commitment for `final_balance`
|
||||||
let (commitment, opening) = Pedersen::new(final_balance);
|
let (commitment, opening) = Pedersen::new(final_balance);
|
||||||
|
let pod_commitment: pod::PedersenCommitment = commitment.into();
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
transcript.append_commitment(b"commitment", &pod_commitment);
|
||||||
let P_EG = source_keypair.public.get_point();
|
|
||||||
let C_EG = final_balance_ct.commitment.get_point();
|
|
||||||
let D_EG = final_balance_ct.handle.get_point();
|
|
||||||
let C_Ped = commitment.get_point();
|
|
||||||
|
|
||||||
// append all current state to the transcript
|
|
||||||
transcript.append_point(b"P_EG", &P_EG.compress());
|
|
||||||
transcript.append_point(b"C_EG", &C_EG.compress());
|
|
||||||
transcript.append_point(b"D_EG", &D_EG.compress());
|
|
||||||
transcript.append_point(b"C_Ped", &C_Ped.compress());
|
|
||||||
|
|
||||||
// generate equality_proof
|
// generate equality_proof
|
||||||
let equality_proof = EqualityProof::new(
|
let equality_proof = EqualityProof::new(
|
||||||
source_keypair,
|
keypair,
|
||||||
final_balance_ct,
|
final_ciphertext,
|
||||||
final_balance,
|
final_balance,
|
||||||
&opening,
|
&opening,
|
||||||
&mut transcript,
|
transcript,
|
||||||
);
|
);
|
||||||
|
|
||||||
let range_proof = RangeProof::new(
|
let range_proof =
|
||||||
vec![final_balance],
|
RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript);
|
||||||
vec![64],
|
|
||||||
vec![&opening],
|
|
||||||
&mut transcript,
|
|
||||||
);
|
|
||||||
|
|
||||||
WithdrawProof {
|
WithdrawProof {
|
||||||
commitment: commitment.into(),
|
commitment: pod_commitment,
|
||||||
equality_proof: equality_proof.try_into().expect("equality proof"),
|
equality_proof: equality_proof.try_into().expect("equality proof"),
|
||||||
range_proof: range_proof.try_into().expect("range proof"),
|
range_proof: range_proof.try_into().expect("range proof"),
|
||||||
}
|
}
|
||||||
|
@ -152,43 +148,25 @@ impl WithdrawProof {
|
||||||
|
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
&self,
|
&self,
|
||||||
source_pk: &ElGamalPubkey,
|
pubkey: &ElGamalPubkey,
|
||||||
final_balance_ct: &ElGamalCiphertext,
|
final_ciphertext: &ElGamalCiphertext,
|
||||||
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), ProofError> {
|
) -> Result<(), ProofError> {
|
||||||
let mut transcript = Self::transcript_new();
|
transcript.append_commitment(b"commitment", &self.commitment);
|
||||||
|
|
||||||
let commitment: PedersenCommitment = self.commitment.try_into()?;
|
let commitment: PedersenCommitment = self.commitment.try_into()?;
|
||||||
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
|
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
|
||||||
let range_proof: RangeProof = self.range_proof.try_into()?;
|
let range_proof: RangeProof = self.range_proof.try_into()?;
|
||||||
|
|
||||||
// add a domain separator to record the start of the protocol
|
|
||||||
transcript.withdraw_proof_domain_sep();
|
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
|
||||||
let P_EG = source_pk.get_point();
|
|
||||||
let C_EG = final_balance_ct.commitment.get_point();
|
|
||||||
let D_EG = final_balance_ct.handle.get_point();
|
|
||||||
let C_Ped = commitment.get_point();
|
|
||||||
|
|
||||||
// append all current state to the transcript
|
|
||||||
transcript.append_point(b"P_EG", &P_EG.compress());
|
|
||||||
transcript.append_point(b"C_EG", &C_EG.compress());
|
|
||||||
transcript.append_point(b"D_EG", &D_EG.compress());
|
|
||||||
transcript.append_point(b"C_Ped", &C_Ped.compress());
|
|
||||||
|
|
||||||
// verify equality proof
|
// verify equality proof
|
||||||
//
|
//
|
||||||
// TODO: we can also consider verifying equality and range proof in a batch
|
// TODO: we can also consider verifying equality and range proof in a batch
|
||||||
equality_proof.verify(source_pk, final_balance_ct, &commitment, &mut transcript)?;
|
equality_proof.verify(pubkey, final_ciphertext, &commitment, transcript)?;
|
||||||
|
|
||||||
// verify range proof
|
// verify range proof
|
||||||
//
|
//
|
||||||
// TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment`
|
// TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment`
|
||||||
range_proof.verify(
|
range_proof.verify(vec![&commitment], vec![64_usize], transcript)?;
|
||||||
vec![&commitment.get_point().compress()],
|
|
||||||
vec![64_usize],
|
|
||||||
&mut transcript,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -201,29 +179,31 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_withdraw_correctness() {
|
fn test_withdraw_correctness() {
|
||||||
// generate and verify proof for the proper setting
|
// generate and verify proof for the proper setting
|
||||||
let elgamal_keypair = ElGamalKeypair::new_rand();
|
let keypair = ElGamalKeypair::new_rand();
|
||||||
|
|
||||||
let current_balance: u64 = 77;
|
let current_balance: u64 = 77;
|
||||||
let current_balance_ct = elgamal_keypair.public.encrypt(current_balance);
|
let current_ciphertext = keypair.public.encrypt(current_balance);
|
||||||
|
|
||||||
let withdraw_amount: u64 = 55;
|
let withdraw_amount: u64 = 55;
|
||||||
|
|
||||||
let data = WithdrawData::new(
|
let data = WithdrawData::new(
|
||||||
withdraw_amount,
|
withdraw_amount,
|
||||||
&elgamal_keypair,
|
&keypair,
|
||||||
current_balance,
|
current_balance,
|
||||||
current_balance_ct,
|
¤t_ciphertext,
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
assert!(data.verify().is_ok());
|
assert!(data.verify().is_ok());
|
||||||
|
|
||||||
// generate and verify proof with wrong balance
|
// generate and verify proof with wrong balance
|
||||||
let wrong_balance: u64 = 99;
|
let wrong_balance: u64 = 99;
|
||||||
let data = WithdrawData::new(
|
let data = WithdrawData::new(
|
||||||
withdraw_amount,
|
withdraw_amount,
|
||||||
&elgamal_keypair,
|
&keypair,
|
||||||
wrong_balance,
|
wrong_balance,
|
||||||
current_balance_ct,
|
¤t_ciphertext,
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
assert!(data.verify().is_err());
|
assert!(data.verify().is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,8 @@ mod sigma_proofs;
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
mod transcript;
|
mod transcript;
|
||||||
|
|
||||||
mod instruction;
|
// TODO: re-organize visibility
|
||||||
|
pub mod instruction;
|
||||||
pub mod zk_token_elgamal;
|
pub mod zk_token_elgamal;
|
||||||
pub mod zk_token_proof_instruction;
|
pub mod zk_token_proof_instruction;
|
||||||
pub mod zk_token_proof_program;
|
pub mod zk_token_proof_program;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
use {
|
use {
|
||||||
crate::encryption::pedersen::{Pedersen, PedersenOpening},
|
crate::encryption::pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
|
||||||
curve25519_dalek::traits::MultiscalarMul,
|
curve25519_dalek::traits::MultiscalarMul,
|
||||||
rand::rngs::OsRng,
|
rand::rngs::OsRng,
|
||||||
subtle::{Choice, ConditionallySelectable},
|
subtle::{Choice, ConditionallySelectable},
|
||||||
|
@ -220,7 +220,7 @@ impl RangeProof {
|
||||||
#[allow(clippy::many_single_char_names)]
|
#[allow(clippy::many_single_char_names)]
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
&self,
|
&self,
|
||||||
comms: Vec<&CompressedRistretto>,
|
comms: Vec<&PedersenCommitment>,
|
||||||
bit_lengths: Vec<usize>,
|
bit_lengths: Vec<usize>,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), RangeProofError> {
|
) -> Result<(), RangeProofError> {
|
||||||
|
@ -308,7 +308,7 @@ impl RangeProof {
|
||||||
.chain(self.ipp_proof.R_vec.iter().map(|R| R.decompress()))
|
.chain(self.ipp_proof.R_vec.iter().map(|R| R.decompress()))
|
||||||
.chain(bp_gens.G(nm).map(|&x| Some(x)))
|
.chain(bp_gens.G(nm).map(|&x| Some(x)))
|
||||||
.chain(bp_gens.H(nm).map(|&x| Some(x)))
|
.chain(bp_gens.H(nm).map(|&x| Some(x)))
|
||||||
.chain(comms.iter().map(|V| V.decompress())),
|
.chain(comms.iter().map(|V| Some(*V.get_point()))),
|
||||||
)
|
)
|
||||||
.ok_or(RangeProofError::MultiscalarMul)?;
|
.ok_or(RangeProofError::MultiscalarMul)?;
|
||||||
|
|
||||||
|
@ -403,11 +403,7 @@ mod tests {
|
||||||
let proof = RangeProof::new(vec![55], vec![32], vec![&open], &mut transcript_create);
|
let proof = RangeProof::new(vec![55], vec![32], vec![&open], &mut transcript_create);
|
||||||
|
|
||||||
assert!(proof
|
assert!(proof
|
||||||
.verify(
|
.verify(vec![&comm], vec![32], &mut transcript_verify)
|
||||||
vec![&comm.get_point().compress()],
|
|
||||||
vec![32],
|
|
||||||
&mut transcript_verify
|
|
||||||
)
|
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,13 +423,9 @@ mod tests {
|
||||||
&mut transcript_create,
|
&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();
|
|
||||||
|
|
||||||
assert!(proof
|
assert!(proof
|
||||||
.verify(
|
.verify(
|
||||||
vec![&comm_1_point, &comm_2_point, &comm_3_point],
|
vec![&comm_1, &comm_2, &comm_3],
|
||||||
vec![64, 32, 32],
|
vec![64, 32, 32],
|
||||||
&mut transcript_verify,
|
&mut transcript_verify,
|
||||||
)
|
)
|
||||||
|
|
|
@ -69,6 +69,8 @@ impl EqualityProof {
|
||||||
opening: &PedersenOpening,
|
opening: &PedersenOpening,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
transcript.equality_proof_domain_sep();
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
// extract the relevant scalar and Ristretto points from the inputs
|
||||||
let P_EG = elgamal_keypair.public.get_point();
|
let P_EG = elgamal_keypair.public.get_point();
|
||||||
let D_EG = ciphertext.handle.get_point();
|
let D_EG = ciphertext.handle.get_point();
|
||||||
|
@ -127,6 +129,8 @@ impl EqualityProof {
|
||||||
commitment: &PedersenCommitment,
|
commitment: &PedersenCommitment,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), EqualityProofError> {
|
) -> Result<(), EqualityProofError> {
|
||||||
|
transcript.equality_proof_domain_sep();
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
// extract the relevant scalar and Ristretto points from the inputs
|
||||||
let P_EG = elgamal_pubkey.get_point();
|
let P_EG = elgamal_pubkey.get_point();
|
||||||
let C_EG = ciphertext.commitment.get_point();
|
let C_EG = ciphertext.commitment.get_point();
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl From<TranscriptError> for ZeroBalanceProofError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum FeeProofError {
|
pub enum FeeSigmaProofError {
|
||||||
#[error("the required algebraic relation does not hold")]
|
#[error("the required algebraic relation does not hold")]
|
||||||
AlgebraicRelation,
|
AlgebraicRelation,
|
||||||
#[error("malformed proof")]
|
#[error("malformed proof")]
|
||||||
|
@ -68,7 +68,7 @@ pub enum FeeProofError {
|
||||||
Transcript,
|
Transcript,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TranscriptError> for FeeProofError {
|
impl From<TranscriptError> for FeeSigmaProofError {
|
||||||
fn from(_err: TranscriptError) -> Self {
|
fn from(_err: TranscriptError) -> Self {
|
||||||
Self::Transcript
|
Self::Transcript
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ use {
|
||||||
rand::rngs::OsRng,
|
rand::rngs::OsRng,
|
||||||
};
|
};
|
||||||
use {
|
use {
|
||||||
crate::{sigma_proofs::errors::FeeProofError, transcript::TranscriptProtocol},
|
crate::{sigma_proofs::errors::FeeSigmaProofError, transcript::TranscriptProtocol},
|
||||||
|
arrayref::{array_ref, array_refs},
|
||||||
curve25519_dalek::{
|
curve25519_dalek::{
|
||||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||||
scalar::Scalar,
|
scalar::Scalar,
|
||||||
|
@ -73,25 +74,25 @@ impl FeeSigmaProof {
|
||||||
&mut transcript_fee_below_max,
|
&mut transcript_fee_below_max,
|
||||||
);
|
);
|
||||||
|
|
||||||
let above_max = u64::ct_gt(&max_fee, &fee_amount);
|
let below_max = u64::ct_gt(&max_fee, &fee_amount);
|
||||||
|
|
||||||
// conditionally assign transcript; transcript is not conditionally selectable
|
// conditionally assign transcript; transcript is not conditionally selectable
|
||||||
if bool::from(above_max) {
|
if bool::from(below_max) {
|
||||||
*transcript = transcript_fee_above_max;
|
|
||||||
} else {
|
|
||||||
*transcript = transcript_fee_below_max;
|
*transcript = transcript_fee_below_max;
|
||||||
|
} else {
|
||||||
|
*transcript = transcript_fee_above_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
fee_max_proof: FeeMaxProof::conditional_select(
|
fee_max_proof: FeeMaxProof::conditional_select(
|
||||||
&proof_fee_above_max.fee_max_proof,
|
&proof_fee_above_max.fee_max_proof,
|
||||||
&proof_fee_below_max.fee_max_proof,
|
&proof_fee_below_max.fee_max_proof,
|
||||||
above_max,
|
below_max,
|
||||||
),
|
),
|
||||||
fee_equality_proof: FeeEqualityProof::conditional_select(
|
fee_equality_proof: FeeEqualityProof::conditional_select(
|
||||||
&proof_fee_above_max.fee_equality_proof,
|
&proof_fee_above_max.fee_equality_proof,
|
||||||
&proof_fee_below_max.fee_equality_proof,
|
&proof_fee_below_max.fee_equality_proof,
|
||||||
above_max,
|
below_max,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,7 +260,7 @@ impl FeeSigmaProof {
|
||||||
commitment_claimed: &PedersenCommitment,
|
commitment_claimed: &PedersenCommitment,
|
||||||
max_fee: u64,
|
max_fee: u64,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), FeeProofError> {
|
) -> Result<(), FeeSigmaProofError> {
|
||||||
// extract the relevant scalar and Ristretto points from the input
|
// extract the relevant scalar and Ristretto points from the input
|
||||||
let m = Scalar::from(max_fee);
|
let m = Scalar::from(max_fee);
|
||||||
|
|
||||||
|
@ -275,19 +276,19 @@ impl FeeSigmaProof {
|
||||||
.fee_max_proof
|
.fee_max_proof
|
||||||
.Y_max_proof
|
.Y_max_proof
|
||||||
.decompress()
|
.decompress()
|
||||||
.ok_or(FeeProofError::Format)?;
|
.ok_or(FeeSigmaProofError::Format)?;
|
||||||
let z_max = self.fee_max_proof.z_max_proof;
|
let z_max = self.fee_max_proof.z_max_proof;
|
||||||
|
|
||||||
let Y_delta_real = self
|
let Y_delta_real = self
|
||||||
.fee_equality_proof
|
.fee_equality_proof
|
||||||
.Y_delta
|
.Y_delta
|
||||||
.decompress()
|
.decompress()
|
||||||
.ok_or(FeeProofError::Format)?;
|
.ok_or(FeeSigmaProofError::Format)?;
|
||||||
let Y_claimed = self
|
let Y_claimed = self
|
||||||
.fee_equality_proof
|
.fee_equality_proof
|
||||||
.Y_claimed
|
.Y_claimed
|
||||||
.decompress()
|
.decompress()
|
||||||
.ok_or(FeeProofError::Format)?;
|
.ok_or(FeeSigmaProofError::Format)?;
|
||||||
let z_x = self.fee_equality_proof.z_x;
|
let z_x = self.fee_equality_proof.z_x;
|
||||||
let z_delta_real = self.fee_equality_proof.z_delta;
|
let z_delta_real = self.fee_equality_proof.z_delta;
|
||||||
let z_claimed = self.fee_equality_proof.z_claimed;
|
let z_claimed = self.fee_equality_proof.z_claimed;
|
||||||
|
@ -333,9 +334,56 @@ impl FeeSigmaProof {
|
||||||
if check.is_identity() {
|
if check.is_identity() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(FeeProofError::AlgebraicRelation)
|
Err(FeeSigmaProofError::AlgebraicRelation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> [u8; 256] {
|
||||||
|
let mut buf = [0_u8; 256];
|
||||||
|
buf[..32].copy_from_slice(self.fee_max_proof.Y_max_proof.as_bytes());
|
||||||
|
buf[32..64].copy_from_slice(self.fee_max_proof.z_max_proof.as_bytes());
|
||||||
|
buf[64..96].copy_from_slice(self.fee_max_proof.c_max_proof.as_bytes());
|
||||||
|
buf[96..128].copy_from_slice(self.fee_equality_proof.Y_delta.as_bytes());
|
||||||
|
buf[128..160].copy_from_slice(self.fee_equality_proof.Y_claimed.as_bytes());
|
||||||
|
buf[160..192].copy_from_slice(self.fee_equality_proof.z_x.as_bytes());
|
||||||
|
buf[192..224].copy_from_slice(self.fee_equality_proof.z_delta.as_bytes());
|
||||||
|
buf[224..256].copy_from_slice(self.fee_equality_proof.z_claimed.as_bytes());
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, FeeSigmaProofError> {
|
||||||
|
let bytes = array_ref![bytes, 0, 256];
|
||||||
|
let (Y_max_proof, z_max_proof, c_max_proof, Y_delta, Y_claimed, z_x, z_delta, z_claimed) =
|
||||||
|
array_refs![bytes, 32, 32, 32, 32, 32, 32, 32, 32];
|
||||||
|
|
||||||
|
let Y_max_proof = CompressedRistretto::from_slice(Y_max_proof);
|
||||||
|
let z_max_proof =
|
||||||
|
Scalar::from_canonical_bytes(*z_max_proof).ok_or(FeeSigmaProofError::Format)?;
|
||||||
|
let c_max_proof =
|
||||||
|
Scalar::from_canonical_bytes(*c_max_proof).ok_or(FeeSigmaProofError::Format)?;
|
||||||
|
|
||||||
|
let Y_delta = CompressedRistretto::from_slice(Y_delta);
|
||||||
|
let Y_claimed = CompressedRistretto::from_slice(Y_claimed);
|
||||||
|
let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(FeeSigmaProofError::Format)?;
|
||||||
|
let z_delta = Scalar::from_canonical_bytes(*z_delta).ok_or(FeeSigmaProofError::Format)?;
|
||||||
|
let z_claimed =
|
||||||
|
Scalar::from_canonical_bytes(*z_claimed).ok_or(FeeSigmaProofError::Format)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
fee_max_proof: FeeMaxProof {
|
||||||
|
Y_max_proof,
|
||||||
|
z_max_proof,
|
||||||
|
c_max_proof,
|
||||||
|
},
|
||||||
|
fee_equality_proof: FeeEqualityProof {
|
||||||
|
Y_delta,
|
||||||
|
Y_claimed,
|
||||||
|
z_x,
|
||||||
|
z_delta,
|
||||||
|
z_claimed,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fee max proof.
|
/// The fee max proof.
|
||||||
|
@ -491,4 +539,46 @@ mod test {
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fee_delta_is_zero() {
|
||||||
|
let transfer_amount: u64 = 100;
|
||||||
|
let max_fee: u64 = 3;
|
||||||
|
|
||||||
|
let rate_fee: u16 = 100; // 1.00%
|
||||||
|
let amount_fee: u64 = 1;
|
||||||
|
let delta: u64 = 0; // 1*10000 - 100*100
|
||||||
|
|
||||||
|
let (commitment_transfer, opening_transfer) = Pedersen::new(transfer_amount);
|
||||||
|
let (commitment_fee, opening_fee) = Pedersen::new(amount_fee);
|
||||||
|
|
||||||
|
let scalar_rate = Scalar::from(rate_fee);
|
||||||
|
let commitment_delta =
|
||||||
|
&(&commitment_fee * &Scalar::from(10000_u64)) - &(&commitment_transfer * &scalar_rate);
|
||||||
|
let opening_delta =
|
||||||
|
&(&opening_fee * &Scalar::from(10000_u64)) - &(&opening_transfer * &scalar_rate);
|
||||||
|
|
||||||
|
let (commitment_claimed, opening_claimed) = Pedersen::new(delta);
|
||||||
|
|
||||||
|
let mut transcript_prover = Transcript::new(b"test");
|
||||||
|
let mut transcript_verifier = Transcript::new(b"test");
|
||||||
|
|
||||||
|
let proof = FeeSigmaProof::new(
|
||||||
|
(amount_fee, &commitment_fee, &opening_fee),
|
||||||
|
(delta, &commitment_delta, &opening_delta),
|
||||||
|
(&commitment_claimed, &opening_claimed),
|
||||||
|
max_fee,
|
||||||
|
&mut transcript_prover,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(proof
|
||||||
|
.verify(
|
||||||
|
&commitment_fee,
|
||||||
|
&commitment_delta,
|
||||||
|
&commitment_claimed,
|
||||||
|
max_fee,
|
||||||
|
&mut transcript_verifier,
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,11 +62,13 @@ impl ValidityProof {
|
||||||
/// * `opening` - The opening associated with the Pedersen commitment
|
/// * `opening` - The opening associated with the Pedersen commitment
|
||||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||||
pub fn new<T: Into<Scalar>>(
|
pub fn new<T: Into<Scalar>>(
|
||||||
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
|
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey), // TODO: rename pubkey_auditor
|
||||||
amount: T,
|
amount: T,
|
||||||
opening: &PedersenOpening,
|
opening: &PedersenOpening,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
transcript.validity_proof_domain_sep();
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
// extract the relevant scalar and Ristretto points from the inputs
|
||||||
let P_dest = pubkey_dest.get_point();
|
let P_dest = pubkey_dest.get_point();
|
||||||
let P_auditor = pubkey_auditor.get_point();
|
let P_auditor = pubkey_auditor.get_point();
|
||||||
|
@ -120,6 +122,8 @@ impl ValidityProof {
|
||||||
(handle_dest, handle_auditor): (&DecryptHandle, &DecryptHandle),
|
(handle_dest, handle_auditor): (&DecryptHandle, &DecryptHandle),
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), ValidityProofError> {
|
) -> Result<(), ValidityProofError> {
|
||||||
|
transcript.validity_proof_domain_sep();
|
||||||
|
|
||||||
// include Y_0, Y_1, Y_2 to transcript and extract challenges
|
// include Y_0, Y_1, Y_2 to transcript and extract challenges
|
||||||
transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
|
transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
|
||||||
transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
|
transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
|
||||||
|
@ -235,6 +239,8 @@ impl AggregatedValidityProof {
|
||||||
(opening_lo, opening_hi): (&PedersenOpening, &PedersenOpening),
|
(opening_lo, opening_hi): (&PedersenOpening, &PedersenOpening),
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
transcript.aggregated_validity_proof_domain_sep();
|
||||||
|
|
||||||
let t = transcript.challenge_scalar(b"t");
|
let t = transcript.challenge_scalar(b"t");
|
||||||
|
|
||||||
let aggregated_message = amount_lo.into() + amount_hi.into() * t;
|
let aggregated_message = amount_lo.into() + amount_hi.into() * t;
|
||||||
|
@ -263,6 +269,8 @@ impl AggregatedValidityProof {
|
||||||
(handle_lo_auditor, handle_hi_auditor): (&DecryptHandle, &DecryptHandle),
|
(handle_lo_auditor, handle_hi_auditor): (&DecryptHandle, &DecryptHandle),
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), ValidityProofError> {
|
) -> Result<(), ValidityProofError> {
|
||||||
|
transcript.aggregated_validity_proof_domain_sep();
|
||||||
|
|
||||||
let t = transcript.challenge_scalar(b"t");
|
let t = transcript.challenge_scalar(b"t");
|
||||||
|
|
||||||
let aggregated_commitment = commitment_lo + commitment_hi * t;
|
let aggregated_commitment = commitment_lo + commitment_hi * t;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use {
|
use {
|
||||||
crate::errors::TranscriptError,
|
crate::{errors::TranscriptError, zk_token_elgamal::pod},
|
||||||
curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar, traits::IsIdentity},
|
curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar, traits::IsIdentity},
|
||||||
merlin::Transcript,
|
merlin::Transcript,
|
||||||
};
|
};
|
||||||
|
@ -19,9 +19,6 @@ pub trait TranscriptProtocol {
|
||||||
/// Append a domain separator for close account proof.
|
/// Append a domain separator for close account proof.
|
||||||
fn close_account_proof_domain_sep(&mut self);
|
fn close_account_proof_domain_sep(&mut self);
|
||||||
|
|
||||||
/// Append a domain separator for update account public key proof.
|
|
||||||
fn update_account_public_key_proof_domain_sep(&mut self);
|
|
||||||
|
|
||||||
/// Append a domain separator for withdraw proof.
|
/// Append a domain separator for withdraw proof.
|
||||||
fn withdraw_proof_domain_sep(&mut self);
|
fn withdraw_proof_domain_sep(&mut self);
|
||||||
|
|
||||||
|
@ -34,6 +31,33 @@ pub trait TranscriptProtocol {
|
||||||
/// Append a `point` with the given `label`.
|
/// Append a `point` with the given `label`.
|
||||||
fn append_point(&mut self, label: &'static [u8], point: &CompressedRistretto);
|
fn append_point(&mut self, label: &'static [u8], point: &CompressedRistretto);
|
||||||
|
|
||||||
|
/// Append an ElGamal pubkey with the given `label`.
|
||||||
|
fn append_pubkey(&mut self, label: &'static [u8], point: &pod::ElGamalPubkey);
|
||||||
|
|
||||||
|
/// Append an ElGamal ciphertext with the given `label`.
|
||||||
|
fn append_ciphertext(&mut self, label: &'static [u8], point: &pod::ElGamalCiphertext);
|
||||||
|
|
||||||
|
/// Append a Pedersen commitment with the given `label`.
|
||||||
|
fn append_commitment(&mut self, label: &'static [u8], point: &pod::PedersenCommitment);
|
||||||
|
|
||||||
|
/// Append an ElGamal decryption handle with the given `label`.
|
||||||
|
fn append_handle(&mut self, label: &'static [u8], point: &pod::DecryptHandle);
|
||||||
|
|
||||||
|
/// Append a domain separator for equality proof.
|
||||||
|
fn equality_proof_domain_sep(&mut self);
|
||||||
|
|
||||||
|
/// Append a domain separator for zero-balance proof.
|
||||||
|
fn zero_balance_proof_domain_sep(&mut self);
|
||||||
|
|
||||||
|
/// Append a domain separator for validity proof.
|
||||||
|
fn validity_proof_domain_sep(&mut self);
|
||||||
|
|
||||||
|
/// Append a domain separator for aggregated validity proof.
|
||||||
|
fn aggregated_validity_proof_domain_sep(&mut self);
|
||||||
|
|
||||||
|
/// Append a domain separator for fee sigma proof.
|
||||||
|
fn fee_sigma_proof_domain_sep(&mut self);
|
||||||
|
|
||||||
/// Check that a point is not the identity, then append it to the
|
/// Check that a point is not the identity, then append it to the
|
||||||
/// transcript. Otherwise, return an error.
|
/// transcript. Otherwise, return an error.
|
||||||
fn validate_and_append_point(
|
fn validate_and_append_point(
|
||||||
|
@ -66,10 +90,6 @@ impl TranscriptProtocol for Transcript {
|
||||||
self.append_message(b"dom-sep", b"CloseAccountProof");
|
self.append_message(b"dom-sep", b"CloseAccountProof");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_account_public_key_proof_domain_sep(&mut self) {
|
|
||||||
self.append_message(b"dom-sep", b"UpdateAccountPublicKeyProof");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn withdraw_proof_domain_sep(&mut self) {
|
fn withdraw_proof_domain_sep(&mut self) {
|
||||||
self.append_message(b"dom-sep", b"WithdrawProof");
|
self.append_message(b"dom-sep", b"WithdrawProof");
|
||||||
}
|
}
|
||||||
|
@ -105,4 +125,40 @@ impl TranscriptProtocol for Transcript {
|
||||||
|
|
||||||
Scalar::from_bytes_mod_order_wide(&buf)
|
Scalar::from_bytes_mod_order_wide(&buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn append_pubkey(&mut self, label: &'static [u8], pubkey: &pod::ElGamalPubkey) {
|
||||||
|
self.append_message(label, &pubkey.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_ciphertext(&mut self, label: &'static [u8], ciphertext: &pod::ElGamalCiphertext) {
|
||||||
|
self.append_message(label, &ciphertext.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_commitment(&mut self, label: &'static [u8], commitment: &pod::PedersenCommitment) {
|
||||||
|
self.append_message(label, &commitment.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_handle(&mut self, label: &'static [u8], handle: &pod::DecryptHandle) {
|
||||||
|
self.append_message(label, &handle.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn equality_proof_domain_sep(&mut self) {
|
||||||
|
self.append_message(b"dom-sep", b"equality-proof")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zero_balance_proof_domain_sep(&mut self) {
|
||||||
|
self.append_message(b"dom-sep", b"zero-balance-proof")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validity_proof_domain_sep(&mut self) {
|
||||||
|
self.append_message(b"dom-sep", b"validity-proof")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aggregated_validity_proof_domain_sep(&mut self) {
|
||||||
|
self.append_message(b"dom-sep", b"aggregated-validity-proof")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fee_sigma_proof_domain_sep(&mut self) {
|
||||||
|
self.append_message(b"dom-sep", b"fee-sigma-proof")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,15 @@ mod target_arch {
|
||||||
pedersen::PedersenCommitment,
|
pedersen::PedersenCommitment,
|
||||||
},
|
},
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
|
instruction::{
|
||||||
|
transfer::{TransferAmountEncryption, TransferPubkeys},
|
||||||
|
transfer_with_fee::{FeeEncryption, FeeParameters, TransferWithFeePubkeys},
|
||||||
|
},
|
||||||
range_proof::{errors::RangeProofError, RangeProof},
|
range_proof::{errors::RangeProofError, RangeProof},
|
||||||
sigma_proofs::{
|
sigma_proofs::{
|
||||||
equality_proof::EqualityProof,
|
equality_proof::EqualityProof,
|
||||||
errors::*,
|
errors::*,
|
||||||
|
fee_proof::FeeSigmaProof,
|
||||||
validity_proof::{AggregatedValidityProof, ValidityProof},
|
validity_proof::{AggregatedValidityProof, ValidityProof},
|
||||||
zero_balance_proof::ZeroBalanceProof,
|
zero_balance_proof::ZeroBalanceProof,
|
||||||
},
|
},
|
||||||
|
@ -202,6 +207,20 @@ mod target_arch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FeeSigmaProof> for pod::FeeSigmaProof {
|
||||||
|
fn from(proof: FeeSigmaProof) -> Self {
|
||||||
|
Self(proof.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<pod::FeeSigmaProof> for FeeSigmaProof {
|
||||||
|
type Error = FeeSigmaProofError;
|
||||||
|
|
||||||
|
fn try_from(pod: pod::FeeSigmaProof) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_bytes(&pod.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<RangeProof> for pod::RangeProof64 {
|
impl TryFrom<RangeProof> for pod::RangeProof64 {
|
||||||
type Error = RangeProofError;
|
type Error = RangeProofError;
|
||||||
|
|
||||||
|
@ -260,6 +279,104 @@ mod target_arch {
|
||||||
Self::from_bytes(&pod.0)
|
Self::from_bytes(&pod.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
impl TryFrom<RangeProof> for pod::RangeProof256 {
|
||||||
|
type Error = RangeProofError;
|
||||||
|
|
||||||
|
fn try_from(proof: RangeProof) -> Result<Self, Self::Error> {
|
||||||
|
if proof.ipp_proof.serialized_size() != 576 {
|
||||||
|
return Err(RangeProofError::Format);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = [0_u8; 800];
|
||||||
|
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..800].copy_from_slice(&proof.ipp_proof.to_bytes());
|
||||||
|
Ok(pod::RangeProof256(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<pod::RangeProof256> for RangeProof {
|
||||||
|
type Error = RangeProofError;
|
||||||
|
|
||||||
|
fn try_from(pod: pod::RangeProof256) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_bytes(&pod.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TransferPubkeys> for pod::TransferPubkeys {
|
||||||
|
fn from(keys: TransferPubkeys) -> Self {
|
||||||
|
Self(keys.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<pod::TransferPubkeys> for TransferPubkeys {
|
||||||
|
type Error = ProofError;
|
||||||
|
|
||||||
|
fn try_from(pod: pod::TransferPubkeys) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_bytes(&pod.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TransferWithFeePubkeys> for pod::TransferWithFeePubkeys {
|
||||||
|
fn from(keys: TransferWithFeePubkeys) -> Self {
|
||||||
|
Self(keys.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<pod::TransferWithFeePubkeys> for TransferWithFeePubkeys {
|
||||||
|
type Error = ProofError;
|
||||||
|
|
||||||
|
fn try_from(pod: pod::TransferWithFeePubkeys) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_bytes(&pod.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TransferAmountEncryption> for pod::TransferAmountEncryption {
|
||||||
|
fn from(ciphertext: TransferAmountEncryption) -> Self {
|
||||||
|
Self(ciphertext.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<pod::TransferAmountEncryption> for TransferAmountEncryption {
|
||||||
|
type Error = ProofError;
|
||||||
|
|
||||||
|
fn try_from(pod: pod::TransferAmountEncryption) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_bytes(&pod.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FeeEncryption> for pod::FeeEncryption {
|
||||||
|
fn from(ciphertext: FeeEncryption) -> Self {
|
||||||
|
Self(ciphertext.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<pod::FeeEncryption> for FeeEncryption {
|
||||||
|
type Error = ProofError;
|
||||||
|
|
||||||
|
fn try_from(pod: pod::FeeEncryption) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_bytes(&pod.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FeeParameters> for pod::FeeParameters {
|
||||||
|
fn from(parameters: FeeParameters) -> Self {
|
||||||
|
Self(parameters.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<pod::FeeParameters> for FeeParameters {
|
||||||
|
fn from(pod: pod::FeeParameters) -> Self {
|
||||||
|
Self::from_bytes(&pod.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "bpf")]
|
#[cfg(target_arch = "bpf")]
|
||||||
|
@ -288,11 +405,7 @@ mod tests {
|
||||||
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
|
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
|
||||||
|
|
||||||
assert!(proof_deserialized
|
assert!(proof_deserialized
|
||||||
.verify(
|
.verify(vec![&comm], vec![64], &mut transcript_verify)
|
||||||
vec![&comm.get_point().compress()],
|
|
||||||
vec![64],
|
|
||||||
&mut transcript_verify
|
|
||||||
)
|
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// should fail to serialize to pod::RangeProof128
|
// should fail to serialize to pod::RangeProof128
|
||||||
|
@ -317,16 +430,12 @@ mod tests {
|
||||||
&mut transcript_create,
|
&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_serialized: pod::RangeProof128 = proof.try_into().unwrap();
|
||||||
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
|
let proof_deserialized: RangeProof = proof_serialized.try_into().unwrap();
|
||||||
|
|
||||||
assert!(proof_deserialized
|
assert!(proof_deserialized
|
||||||
.verify(
|
.verify(
|
||||||
vec![&comm_1_point, &comm_2_point, &comm_3_point],
|
vec![&comm_1, &comm_2, &comm_3],
|
||||||
vec![64, 32, 32],
|
vec![64, 32, 32],
|
||||||
&mut transcript_verify,
|
&mut transcript_verify,
|
||||||
)
|
)
|
||||||
|
|
|
@ -95,6 +95,11 @@ pub struct ZeroBalanceProof(pub [u8; 96]);
|
||||||
unsafe impl Zeroable for ZeroBalanceProof {}
|
unsafe impl Zeroable for ZeroBalanceProof {}
|
||||||
unsafe impl Pod for ZeroBalanceProof {}
|
unsafe impl Pod for ZeroBalanceProof {}
|
||||||
|
|
||||||
|
/// Serialization of fee sigma proof
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct FeeSigmaProof(pub [u8; 256]);
|
||||||
|
|
||||||
/// Serialization of range proofs for 64-bit numbers (for `Withdraw` instruction)
|
/// Serialization of range proofs for 64-bit numbers (for `Withdraw` instruction)
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
@ -115,6 +120,16 @@ pub struct RangeProof128(pub [u8; 736]);
|
||||||
unsafe impl Zeroable for RangeProof128 {}
|
unsafe impl Zeroable for RangeProof128 {}
|
||||||
unsafe impl Pod for RangeProof128 {}
|
unsafe impl Pod for RangeProof128 {}
|
||||||
|
|
||||||
|
/// Serialization of range proofs for 128-bit numbers (for `TransferRangeProof` instruction)
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct RangeProof256(pub [u8; 800]);
|
||||||
|
|
||||||
|
// `PodRangeProof256` is a Pod and Zeroable.
|
||||||
|
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
|
||||||
|
unsafe impl Zeroable for RangeProof256 {}
|
||||||
|
unsafe impl Pod for RangeProof256 {}
|
||||||
|
|
||||||
/// Serialization for AeCiphertext
|
/// Serialization for AeCiphertext
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
@ -136,3 +151,33 @@ impl Default for AeCiphertext {
|
||||||
Self::zeroed()
|
Self::zeroed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: refactor this code into the instruction module
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct TransferPubkeys(pub [u8; 96]);
|
||||||
|
|
||||||
|
unsafe impl Zeroable for TransferPubkeys {}
|
||||||
|
unsafe impl Pod for TransferPubkeys {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct TransferWithFeePubkeys(pub [u8; 128]);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct TransferAmountEncryption(pub [u8; 128]);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct FeeEncryption(pub [u8; 96]);
|
||||||
|
|
||||||
|
unsafe impl Zeroable for FeeEncryption {}
|
||||||
|
unsafe impl Pod for FeeEncryption {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct FeeParameters(pub [u8; 10]);
|
||||||
|
|
||||||
|
unsafe impl Zeroable for FeeParameters {}
|
||||||
|
unsafe impl Pod for FeeParameters {}
|
||||||
|
|
Loading…
Reference in New Issue