solana_zk_token_sdk/instruction/
withdraw.rs#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::{ElGamal, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::{Pedersen, PedersenCommitment},
},
errors::{ProofGenerationError, ProofVerificationError},
range_proof::RangeProof,
sigma_proofs::ciphertext_commitment_equality_proof::CiphertextCommitmentEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_os = "solana"))]
const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64;
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawData {
pub context: WithdrawProofContext, pub proof: WithdrawProof, }
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawProofContext {
pub pubkey: pod::ElGamalPubkey, pub final_ciphertext: pod::ElGamalCiphertext, }
#[cfg(not(target_os = "solana"))]
impl WithdrawData {
pub fn new(
amount: u64,
keypair: &ElGamalKeypair,
current_balance: u64,
current_ciphertext: &ElGamalCiphertext,
) -> Result<Self, ProofGenerationError> {
let final_balance = current_balance
.checked_sub(amount)
.ok_or(ProofGenerationError::NotEnoughFunds)?;
let final_ciphertext = current_ciphertext - &ElGamal::encode(amount);
let pod_pubkey = pod::ElGamalPubkey(keypair.pubkey().to_bytes());
let pod_final_ciphertext: pod::ElGamalCiphertext = final_ciphertext.into();
let context = WithdrawProofContext {
pubkey: pod_pubkey,
final_ciphertext: pod_final_ciphertext,
};
let mut transcript = context.new_transcript();
let proof = WithdrawProof::new(keypair, final_balance, &final_ciphertext, &mut transcript)?;
Ok(Self { context, proof })
}
}
impl ZkProofData<WithdrawProofContext> for WithdrawData {
const PROOF_TYPE: ProofType = ProofType::Withdraw;
fn context_data(&self) -> &WithdrawProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofVerificationError> {
let mut transcript = self.context.new_transcript();
let elgamal_pubkey = self.context.pubkey.try_into()?;
let final_balance_ciphertext = self.context.final_ciphertext.try_into()?;
self.proof
.verify(&elgamal_pubkey, &final_balance_ciphertext, &mut transcript)
}
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl WithdrawProofContext {
fn new_transcript(&self) -> Transcript {
let mut transcript = Transcript::new(b"WithdrawProof");
transcript.append_pubkey(b"pubkey", &self.pubkey);
transcript.append_ciphertext(b"ciphertext", &self.final_ciphertext);
transcript
}
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
#[allow(non_snake_case)]
pub struct WithdrawProof {
pub commitment: pod::PedersenCommitment,
pub equality_proof: pod::CiphertextCommitmentEqualityProof,
pub range_proof: pod::RangeProofU64, }
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl WithdrawProof {
pub fn new(
keypair: &ElGamalKeypair,
final_balance: u64,
final_ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<Self, ProofGenerationError> {
let (commitment, opening) = Pedersen::new(final_balance);
let pod_commitment: pod::PedersenCommitment = commitment.into();
transcript.append_commitment(b"commitment", &pod_commitment);
let equality_proof = CiphertextCommitmentEqualityProof::new(
keypair,
final_ciphertext,
&opening,
final_balance,
transcript,
);
let range_proof =
RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript)?;
Ok(Self {
commitment: pod_commitment,
equality_proof: equality_proof.into(),
range_proof: range_proof
.try_into()
.map_err(|_| ProofGenerationError::ProofLength)?,
})
}
pub fn verify(
&self,
pubkey: &ElGamalPubkey,
final_ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), ProofVerificationError> {
transcript.append_commitment(b"commitment", &self.commitment);
let commitment: PedersenCommitment = self.commitment.try_into()?;
let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?;
let range_proof: RangeProof = self.range_proof.try_into()?;
equality_proof.verify(pubkey, final_ciphertext, &commitment, transcript)?;
range_proof.verify(
vec![&commitment],
vec![WITHDRAW_AMOUNT_BIT_LENGTH],
transcript,
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use {super::*, crate::encryption::elgamal::ElGamalKeypair};
#[test]
fn test_withdraw_correctness() {
let keypair = ElGamalKeypair::new_rand();
let current_balance: u64 = 77;
let current_ciphertext = keypair.pubkey().encrypt(current_balance);
let withdraw_amount: u64 = 55;
let data = WithdrawData::new(
withdraw_amount,
&keypair,
current_balance,
¤t_ciphertext,
)
.unwrap();
assert!(data.verify_proof().is_ok());
let wrong_balance: u64 = 99;
let data = WithdrawData::new(
withdraw_amount,
&keypair,
wrong_balance,
¤t_ciphertext,
)
.unwrap();
assert!(data.verify_proof().is_err());
}
}