Files
solana/zk-token-sdk/src/instruction/transfer.rs

484 lines
16 KiB
Rust
Raw Normal View History

2021-09-29 21:45:35 -07:00
use {
2021-09-30 10:25:36 -07:00
crate::zk_token_elgamal::pod,
2021-09-29 21:45:35 -07:00
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_arch = "bpf"))]
use {
crate::{
encryption::{
2021-10-28 07:49:59 -04:00
discrete_log::*,
2021-12-09 10:56:33 -05:00
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey},
pedersen::{
Pedersen, PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening,
},
2021-09-29 21:45:35 -07:00
},
2021-12-09 10:56:33 -05:00
equality_proof::EqualityProof,
2021-09-29 21:45:35 -07:00
errors::ProofError,
2021-10-28 07:49:59 -04:00
instruction::{Role, Verifiable},
2021-09-29 21:45:35 -07:00
range_proof::RangeProof,
transcript::TranscriptProtocol,
},
curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::{IsIdentity, MultiscalarMul, VartimeMultiscalarMul},
},
merlin::Transcript,
rand::rngs::OsRng,
std::convert::TryInto,
};
2021-10-04 13:14:48 -07:00
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
2021-09-29 21:45:35 -07:00
pub struct TransferData {
2021-10-06 10:54:03 -04:00
/// The transfer amount encoded as Pedersen commitments
pub amount_comms: TransferCommitments,
/// 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 public encryption keys associated with the transfer: source, dest, and auditor
2021-12-09 10:56:33 -05:00
pub transfer_public_keys: TransferPubkeys, // 96 bytes
2021-10-06 10:54:03 -04:00
/// The final spendable ciphertext after the transfer
pub new_spendable_ct: pod::ElGamalCiphertext, // 64 bytes
/// Zero-knowledge proofs for Transfer
pub proof: TransferProof,
2021-09-29 21:45:35 -07:00
}
#[cfg(not(target_arch = "bpf"))]
impl TransferData {
#[allow(clippy::too_many_arguments)]
2021-09-29 21:45:35 -07:00
pub fn new(
transfer_amount: u64,
spendable_balance: u64,
spendable_ct: ElGamalCiphertext,
2021-12-09 10:56:33 -05:00
source_keypair: &ElGamalKeypair,
dest_pk: ElGamalPubkey,
auditor_pk: ElGamalPubkey,
2021-09-29 21:45:35 -07:00
) -> Self {
// split and encrypt transfer amount
//
2021-10-11 11:25:16 -07:00
// encryption is a bit more involved since we are generating each components of an ElGamalKeypair
2021-09-29 21:45:35 -07:00
// ciphertext separately.
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);
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
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);
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
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);
2021-09-29 21:45:35 -07:00
// message encoding as Pedersen commitments, which will be included in range proof data
2021-10-06 10:54:03 -04:00
let amount_comms = TransferCommitments {
2021-09-29 21:45:35 -07:00
lo: comm_lo.into(),
hi: comm_hi.into(),
};
// decryption handles, which will be included in the validity proof data
2021-10-06 10:54:03 -04:00
let decrypt_handles_lo = TransferDecryptHandles {
2021-09-29 21:45:35 -07:00
source: handle_source_lo.into(),
dest: handle_dest_lo.into(),
auditor: handle_auditor_lo.into(),
};
2021-10-06 10:54:03 -04:00
let decrypt_handles_hi = TransferDecryptHandles {
2021-09-29 21:45:35 -07:00
source: handle_source_hi.into(),
dest: handle_dest_hi.into(),
auditor: handle_auditor_hi.into(),
};
// grouping of the public keys for the transfer
2021-12-09 10:56:33 -05:00
let transfer_public_keys = TransferPubkeys {
source_pk: source_keypair.public.into(),
2021-09-29 21:45:35 -07:00
dest_pk: dest_pk.into(),
auditor_pk: auditor_pk.into(),
};
// subtract transfer amount from the spendable ciphertext
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 {
2021-09-29 21:45:35 -07:00
message_comm: new_spendable_comm,
decrypt_handle: new_spendable_handle,
};
// range_proof and validity_proof should be generated together
2021-10-06 10:54:03 -04:00
let proof = TransferProof::new(
2021-12-09 10:56:33 -05:00
&source_keypair,
2021-09-29 21:45:35 -07:00
&dest_pk,
&auditor_pk,
(amount_lo as u64, amount_hi as u64),
&open_lo,
&open_hi,
new_spendable_balance,
&new_spendable_ct,
);
2021-10-06 10:54:03 -04:00
Self {
2021-09-29 21:45:35 -07:00
amount_comms,
2021-10-06 10:54:03 -04:00
decrypt_handles_lo,
decrypt_handles_hi,
2021-09-29 21:45:35 -07:00
new_spendable_ct: new_spendable_ct.into(),
2021-10-06 10:54:03 -04:00
transfer_public_keys,
proof,
2021-09-29 21:45:35 -07:00
}
}
/// Extracts the lo ciphertexts associated with a transfer data
2021-10-28 11:45:15 -07:00
fn ciphertext_lo(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let transfer_comm_lo: PedersenCommitment = self.amount_comms.lo.try_into()?;
2021-10-04 09:48:05 -04:00
let decryption_handle_lo = match role {
2021-10-28 07:49:59 -04:00
Role::Source => self.decrypt_handles_lo.source,
Role::Dest => self.decrypt_handles_lo.dest,
Role::Auditor => self.decrypt_handles_lo.auditor,
}
.try_into()?;
2021-10-04 09:48:05 -04:00
Ok((transfer_comm_lo, decryption_handle_lo).into())
}
/// Extracts the lo ciphertexts associated with a transfer data
2021-10-28 11:45:15 -07:00
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 {
2021-10-28 07:49:59 -04:00
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())
}
/// Decrypts transfer amount from transfer 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
2021-10-28 07:49:59 -04:00
///
/// TODO: Define specific error type for decryption error
2021-10-28 11:45:15 -07:00
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);
2021-10-28 07:49:59 -04:00
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)
}
}
2021-09-29 21:45:35 -07:00
}
2021-10-04 13:14:48 -07:00
#[cfg(not(target_arch = "bpf"))]
impl Verifiable for TransferData {
fn verify(&self) -> Result<(), ProofError> {
2021-09-29 21:45:35 -07:00
self.proof.verify(
2021-10-06 10:54:03 -04:00
&self.amount_comms,
&self.decrypt_handles_lo,
&self.decrypt_handles_hi,
&self.new_spendable_ct,
2021-09-29 21:45:35 -07:00
&self.transfer_public_keys,
)
}
}
2021-10-06 10:54:03 -04:00
#[allow(non_snake_case)]
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferProof {
2021-12-09 10:56:33 -05:00
/// New Pedersen commitment for the remaining balance in source
pub source_commitment: pod::PedersenCommitment,
/// Associated equality proof
pub equality_proof: pod::EqualityProof,
// Associated range proof
2021-09-30 10:25:36 -07:00
pub range_proof: pod::RangeProof128,
2021-09-29 21:45:35 -07:00
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
2021-10-06 10:54:03 -04:00
impl TransferProof {
2021-12-09 10:56:33 -05:00
fn transcript_new() -> Transcript {
Transcript::new(b"TransferProof")
}
2021-09-29 21:45:35 -07:00
#[allow(clippy::too_many_arguments)]
#[allow(clippy::many_single_char_names)]
pub fn new(
2021-12-09 10:56:33 -05:00
source_keypair: &ElGamalKeypair,
dest_pk: &ElGamalPubkey,
auditor_pk: &ElGamalPubkey,
2021-09-29 21:45:35 -07:00
transfer_amt: (u64, u64),
lo_open: &PedersenOpening,
hi_open: &PedersenOpening,
2021-12-09 10:56:33 -05:00
source_new_balance: u64,
source_new_balance_ct: &ElGamalCiphertext,
2021-10-06 10:54:03 -04:00
) -> Self {
2021-12-09 10:56:33 -05:00
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
let (source_commitment, source_open) = Pedersen::new(source_new_balance);
// extract the relevant scalar and Ristretto points from the inputs
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();
// 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());
// let c = transcript.challenge_scalar(b"c");
// println!("{:?}", c);
// generate equality_proof
let equality_proof = EqualityProof::new(
source_keypair,
source_new_balance_ct,
source_new_balance,
&source_open,
&mut transcript,
);
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
// TODO: Add ct validity proof
2021-09-29 21:45:35 -07:00
2021-10-06 10:54:03 -04:00
// generate the range proof
2021-12-09 10:56:33 -05:00
let range_proof = RangeProof::create(
vec![source_new_balance, transfer_amt.0, transfer_amt.1],
2021-10-06 10:54:03 -04:00
vec![64, 32, 32],
2021-12-09 10:56:33 -05:00
vec![&source_open, lo_open, hi_open],
2021-10-06 10:54:03 -04:00
&mut transcript,
);
Self {
2021-12-09 10:56:33 -05:00
source_commitment: source_commitment.into(),
equality_proof: equality_proof.try_into().expect("equality proof"),
range_proof: range_proof.try_into().expect("range proof"),
2021-10-06 10:54:03 -04:00
}
2021-09-29 21:45:35 -07:00
}
pub fn verify(
self,
2021-10-06 10:54:03 -04:00
amount_comms: &TransferCommitments,
decryption_handles_lo: &TransferDecryptHandles,
decryption_handles_hi: &TransferDecryptHandles,
new_spendable_ct: &pod::ElGamalCiphertext,
2021-12-09 10:56:33 -05:00
transfer_public_keys: &TransferPubkeys,
2021-09-29 21:45:35 -07:00
) -> Result<(), ProofError> {
2021-12-09 10:56:33 -05:00
let mut transcript = Self::transcript_new();
2021-10-06 10:54:03 -04:00
2021-12-09 10:56:33 -05:00
let commitment: PedersenCommitment = self.source_commitment.try_into()?;
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
2021-10-06 10:54:03 -04:00
let range_proof: RangeProof = self.range_proof.try_into()?;
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
// add a domain separator to record the start of the protocol
transcript.transfer_proof_domain_sep();
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
// extract the relevant scalar and Ristretto points from the inputs
let source_pk: ElGamalPubkey = transfer_public_keys.source_pk.try_into()?;
2021-10-06 10:54:03 -04:00
let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?;
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
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();
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
// 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());
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
// verify equality proof
//
// TODO: we can also consider verifying equality and range proof in a batch
equality_proof.verify(&source_pk, &new_spendable_ct, &commitment, &mut transcript)?;
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
// TODO: validity proof
2021-09-29 21:45:35 -07:00
2021-10-06 10:54:03 -04:00
// verify range proof
2021-12-09 10:56:33 -05:00
range_proof.verify(
2021-10-06 10:54:03 -04:00
vec![
2021-12-09 10:56:33 -05:00
&self.source_commitment.into(),
2021-10-06 10:54:03 -04:00
&amount_comms.lo.into(),
&amount_comms.hi.into(),
],
vec![64_usize, 32_usize, 32_usize],
&mut transcript,
2021-12-09 10:56:33 -05:00
)?;
2021-09-29 21:45:35 -07:00
2021-12-09 10:56:33 -05:00
Ok(())
2021-09-29 21:45:35 -07:00
}
}
/// The ElGamal public keys needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
2021-12-09 10:56:33 -05:00
pub struct TransferPubkeys {
pub source_pk: pod::ElGamalPubkey, // 32 bytes
pub dest_pk: pod::ElGamalPubkey, // 32 bytes
pub auditor_pk: pod::ElGamalPubkey, // 32 bytes
2021-09-29 21:45:35 -07:00
}
/// The transfer amount commitments needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
2021-10-06 10:54:03 -04:00
pub struct TransferCommitments {
pub lo: pod::PedersenCommitment, // 32 bytes
pub hi: pod::PedersenCommitment, // 32 bytes
2021-09-29 21:45:35 -07:00
}
/// The decryption handles needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
2021-10-06 10:54:03 -04:00
pub struct TransferDecryptHandles {
pub source: pod::PedersenDecryptHandle, // 32 bytes
pub dest: pod::PedersenDecryptHandle, // 32 bytes
pub auditor: pod::PedersenDecryptHandle, // 32 bytes
2021-09-29 21:45:35 -07:00
}
/// 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 {
2021-09-29 21:45:35 -07:00
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 {
2021-09-29 21:45:35 -07:00
handle_lo + handle_hi * Scalar::from(TWO_32)
}
2021-09-30 10:25:36 -07:00
/*
pub fn combine_u32_ciphertexts(ct_lo: ElGamalCiphertext, ct_hi: ElGamalCiphertext) -> ElGamalCiphertext {
2021-09-29 21:45:35 -07:00
ct_lo + ct_hi * Scalar::from(TWO_32)
2021-10-04 09:48:05 -04:00
}*/
2021-09-29 21:45:35 -07:00
#[cfg(test)]
mod test {
2021-12-03 09:06:31 -08:00
use {super::*, crate::encryption::elgamal::ElGamalKeypair};
2021-09-29 21:45:35 -07:00
#[test]
fn test_transfer_correctness() {
2021-10-11 11:25:16 -07:00
// ElGamalKeypair keys for source, destination, and auditor accounts
2021-12-09 10:56:33 -05:00
let source_keypair = ElGamalKeypair::default();
2021-10-11 11:32:39 -07:00
let dest_pk = ElGamalKeypair::default().public;
let auditor_pk = ElGamalKeypair::default().public;
2021-09-29 21:45:35 -07:00
// create source account spendable ciphertext
let spendable_balance: u64 = 77;
2021-12-09 10:56:33 -05:00
let spendable_ct = source_keypair.public.encrypt(spendable_balance);
2021-09-29 21:45:35 -07:00
// transfer amount
let transfer_amount: u64 = 55;
// create transfer data
let transfer_data = TransferData::new(
transfer_amount,
spendable_balance,
spendable_ct,
2021-12-09 10:56:33 -05:00
&source_keypair,
2021-09-29 21:45:35 -07:00
dest_pk,
auditor_pk,
);
2021-10-04 13:14:48 -07:00
assert!(transfer_data.verify().is_ok());
2021-09-29 21:45:35 -07:00
}
#[test]
2021-10-04 10:07:47 -04:00
fn test_source_dest_ciphertext() {
2021-10-11 11:25:16 -07:00
// ElGamalKeypair keys for source, destination, and auditor accounts
2021-12-09 10:56:33 -05:00
let source_keypair = ElGamalKeypair::default();
2021-10-11 11:25:16 -07:00
let ElGamalKeypair {
2021-10-11 11:32:39 -07:00
public: dest_pk,
secret: dest_sk,
2021-10-11 11:25:16 -07:00
} = ElGamalKeypair::default();
let ElGamalKeypair {
public: auditor_pk,
secret: auditor_sk,
} = ElGamalKeypair::default();
// create source account spendable ciphertext
let spendable_balance: u64 = 77;
2021-12-09 10:56:33 -05:00
let spendable_ct = source_keypair.public.encrypt(spendable_balance);
// transfer amount
let transfer_amount: u64 = 55;
// create transfer data
let transfer_data = TransferData::new(
transfer_amount,
spendable_balance,
spendable_ct,
2021-12-09 10:56:33 -05:00
&source_keypair,
dest_pk,
auditor_pk,
);
2021-10-04 09:48:05 -04:00
assert_eq!(
2021-10-28 07:49:59 -04:00
transfer_data
2021-12-09 10:56:33 -05:00
.decrypt_amount(Role::Source, &source_keypair.secret)
2021-10-28 07:49:59 -04:00
.unwrap(),
55_u64,
);
assert_eq!(
2021-10-28 11:45:15 -07:00
transfer_data.decrypt_amount(Role::Dest, &dest_sk).unwrap(),
55_u64,
2021-10-04 09:48:05 -04:00
);
2021-10-04 09:48:05 -04:00
assert_eq!(
2021-10-28 07:49:59 -04:00
transfer_data
2021-10-28 11:45:15 -07:00
.decrypt_amount(Role::Auditor, &auditor_sk)
2021-10-28 07:49:59 -04:00
.unwrap(),
55_u64,
2021-10-04 09:48:05 -04:00
);
}
2021-09-29 21:45:35 -07:00
}