use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_arch = "bpf"))]
use {
crate::{
encryption::{
discrete_log::*,
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey},
pedersen::{Pedersen, PedersenCommitment, PedersenDecryptHandle, PedersenOpening},
},
errors::ProofError,
instruction::{Role, Verifiable},
range_proof::RangeProof,
sigma_proofs::{equality_proof::EqualityProof, validity_proof::ValidityProof},
transcript::TranscriptProtocol,
},
curve25519_dalek::scalar::Scalar,
merlin::Transcript,
std::convert::TryInto,
};
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferData {
pub amount_comms: TransferCommitments,
pub decrypt_handles_lo: TransferDecryptHandles,
pub decrypt_handles_hi: TransferDecryptHandles,
pub transfer_public_keys: TransferPubkeys,
pub new_spendable_ct: pod::ElGamalCiphertext,
pub proof: TransferProof,
}
#[cfg(not(target_arch = "bpf"))]
impl TransferData {
#[allow(clippy::too_many_arguments)]
pub fn new(
transfer_amount: u64,
spendable_balance: u64,
spendable_ct: ElGamalCiphertext,
source_keypair: &ElGamalKeypair,
dest_pk: ElGamalPubkey,
auditor_pk: ElGamalPubkey,
) -> Self {
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
let (comm_lo, open_lo) = Pedersen::new(amount_lo);
let (comm_hi, open_hi) = Pedersen::new(amount_hi);
let handle_source_lo = source_keypair.public.decrypt_handle(&open_lo);
let handle_dest_lo = dest_pk.decrypt_handle(&open_lo);
let handle_auditor_lo = auditor_pk.decrypt_handle(&open_lo);
let handle_source_hi = source_keypair.public.decrypt_handle(&open_hi);
let handle_dest_hi = dest_pk.decrypt_handle(&open_hi);
let handle_auditor_hi = auditor_pk.decrypt_handle(&open_hi);
let amount_comms = TransferCommitments {
lo: comm_lo.into(),
hi: comm_hi.into(),
};
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 transfer_public_keys = TransferPubkeys {
source_pk: source_keypair.public.into(),
dest_pk: dest_pk.into(),
auditor_pk: auditor_pk.into(),
};
let spendable_comm = spendable_ct.message_comm;
let spendable_handle = spendable_ct.decrypt_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 {
message_comm: new_spendable_comm,
decrypt_handle: new_spendable_handle,
};
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 {
amount_comms,
decrypt_handles_lo,
decrypt_handles_hi,
new_spendable_ct: new_spendable_ct.into(),
transfer_public_keys,
proof,
}
}
fn ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let transfer_comm_lo: PedersenCommitment = self.amount_comms.lo.try_into()?;
let decryption_handle_lo = match role {
Role::Source => self.decrypt_handles_lo.source,
Role::Dest => self.decrypt_handles_lo.dest,
Role::Auditor => self.decrypt_handles_lo.auditor,
}
.try_into()?;
Ok((transfer_comm_lo, decryption_handle_lo).into())
}
fn ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let transfer_comm_hi: PedersenCommitment = self.amount_comms.hi.try_into()?;
let decryption_handle_hi = match role {
Role::Source => self.decrypt_handles_hi.source,
Role::Dest => self.decrypt_handles_hi.dest,
Role::Auditor => self.decrypt_handles_hi.auditor,
}
.try_into()?;
Ok((transfer_comm_hi, decryption_handle_hi).into())
}
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::VerificationError)
}
}
}
#[cfg(not(target_arch = "bpf"))]
impl Verifiable for TransferData {
fn verify(&self) -> Result<(), ProofError> {
self.proof.verify(
&self.amount_comms,
&self.decrypt_handles_lo,
&self.decrypt_handles_hi,
&self.new_spendable_ct,
&self.transfer_public_keys,
)
}
}
#[allow(non_snake_case)]
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferProof {
pub source_commitment: pod::PedersenCommitment,
pub equality_proof: pod::EqualityProof,
pub validity_proof: pod::ValidityProof,
pub range_proof: pod::RangeProof128,
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl TransferProof {
fn transcript_new() -> Transcript {
Transcript::new(b"TransferProof")
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::many_single_char_names)]
pub fn new(
source_keypair: &ElGamalKeypair,
dest_pk: &ElGamalPubkey,
auditor_pk: &ElGamalPubkey,
transfer_amt: (u64, u64),
openings: (&PedersenOpening, &PedersenOpening),
source_new_balance: u64,
source_new_balance_ct: &ElGamalCiphertext,
) -> Self {
let mut transcript = Self::transcript_new();
transcript.transfer_proof_domain_sep();
let (source_commitment, source_open) = Pedersen::new(source_new_balance);
let P_EG = source_keypair.public.get_point();
let C_EG = source_new_balance_ct.message_comm.get_point();
let D_EG = source_new_balance_ct.decrypt_handle.get_point();
let C_Ped = source_commitment.get_point();
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());
let equality_proof = EqualityProof::new(
source_keypair,
source_new_balance_ct,
source_new_balance,
&source_open,
&mut transcript,
);
let validity_proof =
ValidityProof::new(dest_pk, auditor_pk, transfer_amt, openings, &mut transcript);
let range_proof = RangeProof::create(
vec![source_new_balance, transfer_amt.0, transfer_amt.1],
vec![64, 32, 32],
vec![&source_open, openings.0, openings.1],
&mut transcript,
);
Self {
source_commitment: source_commitment.into(),
equality_proof: equality_proof.try_into().expect("equality proof"),
validity_proof: validity_proof.try_into().expect("validity proof"),
range_proof: range_proof.try_into().expect("range proof"),
}
}
pub fn verify(
self,
amount_comms: &TransferCommitments,
decryption_handles_lo: &TransferDecryptHandles,
decryption_handles_hi: &TransferDecryptHandles,
new_spendable_ct: &pod::ElGamalCiphertext,
transfer_public_keys: &TransferPubkeys,
) -> Result<(), ProofError> {
let mut transcript = Self::transcript_new();
let commitment: PedersenCommitment = self.source_commitment.try_into()?;
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
let validity_proof: ValidityProof = self.validity_proof.try_into()?;
let range_proof: RangeProof = self.range_proof.try_into()?;
transcript.transfer_proof_domain_sep();
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.message_comm.get_point();
let D_EG = new_spendable_ct.decrypt_handle.get_point();
let C_Ped = commitment.get_point();
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());
equality_proof.verify(&source_pk, &new_spendable_ct, &commitment, &mut transcript)?;
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: PedersenDecryptHandle = decryption_handles_lo.dest.try_into()?;
let handle_hi_dest: PedersenDecryptHandle = decryption_handles_hi.dest.try_into()?;
let handle_lo_auditor: PedersenDecryptHandle = decryption_handles_lo.auditor.try_into()?;
let handle_hi_auditor: PedersenDecryptHandle = decryption_handles_hi.auditor.try_into()?;
validity_proof.verify(
&dest_elgamal_pubkey,
&auditor_elgamal_pubkey,
(&amount_comm_lo, &amount_comm_hi),
(&handle_lo_dest, &handle_hi_dest),
(&handle_lo_auditor, &handle_hi_auditor),
&mut transcript,
)?;
range_proof.verify(
vec![
&self.source_commitment.into(),
&amount_comms.lo.into(),
&amount_comms.hi.into(),
],
vec![64_usize, 32_usize, 32_usize],
&mut transcript,
)?;
Ok(())
}
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferPubkeys {
pub source_pk: pod::ElGamalPubkey, pub dest_pk: pod::ElGamalPubkey, pub auditor_pk: pod::ElGamalPubkey, }
#[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 TransferDecryptHandles {
pub source: pod::PedersenDecryptHandle, pub dest: pod::PedersenDecryptHandle, pub auditor: pod::PedersenDecryptHandle, }
#[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)
}
#[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"))]
pub fn combine_u32_handles(
handle_lo: PedersenDecryptHandle,
handle_hi: PedersenDecryptHandle,
) -> PedersenDecryptHandle {
handle_lo + handle_hi * Scalar::from(TWO_32)
}
#[cfg(test)]
mod test {
use {super::*, crate::encryption::elgamal::ElGamalKeypair};
#[test]
fn test_transfer_correctness() {
let source_keypair = ElGamalKeypair::default();
let dest_pk = ElGamalKeypair::default().public;
let auditor_pk = ElGamalKeypair::default().public;
let spendable_balance: u64 = 77;
let spendable_ct = source_keypair.public.encrypt(spendable_balance);
let transfer_amount: u64 = 55;
let transfer_data = TransferData::new(
transfer_amount,
spendable_balance,
spendable_ct,
&source_keypair,
dest_pk,
auditor_pk,
);
assert!(transfer_data.verify().is_ok());
}
#[test]
fn test_source_dest_ciphertext() {
let source_keypair = ElGamalKeypair::default();
let ElGamalKeypair {
public: dest_pk,
secret: dest_sk,
} = ElGamalKeypair::default();
let ElGamalKeypair {
public: auditor_pk,
secret: auditor_sk,
} = ElGamalKeypair::default();
let spendable_balance: u64 = 77;
let spendable_ct = source_keypair.public.encrypt(spendable_balance);
let transfer_amount: u64 = 55;
let transfer_data = TransferData::new(
transfer_amount,
spendable_balance,
spendable_ct,
&source_keypair,
dest_pk,
auditor_pk,
);
assert_eq!(
transfer_data
.decrypt_amount(Role::Source, &source_keypair.secret)
.unwrap(),
55_u64,
);
assert_eq!(
transfer_data.decrypt_amount(Role::Dest, &dest_sk).unwrap(),
55_u64,
);
assert_eq!(
transfer_data
.decrypt_amount(Role::Auditor, &auditor_sk)
.unwrap(),
55_u64,
);
}
}