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

551 lines
19 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-01 09:48:45 -07:00
elgamal::{ElGamalCiphertext, ElGamalPubkey, ElGamalSecretKey},
pedersen::{
Pedersen, PedersenBase, PedersenCommitment, PedersenDecryptHandle, PedersenOpening,
},
2021-09-29 21:45:35 -07:00
},
errors::ProofError,
instruction::Verifiable,
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
pub transfer_public_keys: TransferPubKeys, // 96 bytes
/// 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 {
pub fn new(
transfer_amount: u64,
spendable_balance: u64,
spendable_ct: ElGamalCiphertext,
source_pk: ElGamalPubkey,
2021-10-01 09:48:45 -07:00
source_sk: &ElGamalSecretKey,
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
let handle_source_lo = source_pk.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
let handle_source_hi = source_pk.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
let transfer_public_keys = TransferPubKeys {
source_pk: source_pk.into(),
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-09-29 21:45:35 -07:00
source_sk,
&source_pk,
&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
}
}
2021-10-04 10:07:47 -04:00
/// Extracts the lo and hi source ciphertexts associated with a transfer data and returns the
/// *combined* ciphertext
pub fn source_ciphertext(&self) -> Result<ElGamalCiphertext, ProofError> {
2021-10-06 10:54:03 -04:00
let transfer_comms_lo: PedersenCommitment = self.amount_comms.lo.try_into()?;
let transfer_comms_hi: PedersenCommitment = self.amount_comms.hi.try_into()?;
2021-10-04 09:48:05 -04:00
let transfer_comm = combine_u32_comms(transfer_comms_lo, transfer_comms_hi);
2021-10-06 10:54:03 -04:00
let decryption_handle_lo: PedersenDecryptHandle =
self.decrypt_handles_lo.source.try_into()?;
let decryption_handle_hi: PedersenDecryptHandle =
self.decrypt_handles_hi.source.try_into()?;
2021-10-04 09:48:05 -04:00
let decryption_handle = combine_u32_handles(decryption_handle_lo, decryption_handle_hi);
Ok((transfer_comm, decryption_handle).into())
}
2021-10-04 10:07:47 -04:00
/// Extracts the lo and hi destination ciphertexts associated with a transfer data and returns
/// the *combined* ciphertext
pub fn dest_ciphertext(&self) -> Result<ElGamalCiphertext, ProofError> {
2021-10-06 10:54:03 -04:00
let transfer_comms_lo: PedersenCommitment = self.amount_comms.lo.try_into()?;
let transfer_comms_hi: PedersenCommitment = self.amount_comms.hi.try_into()?;
2021-10-04 09:48:05 -04:00
let transfer_comm = combine_u32_comms(transfer_comms_lo, transfer_comms_hi);
let decryption_handle_lo: PedersenDecryptHandle =
2021-10-06 10:54:03 -04:00
self.decrypt_handles_lo.dest.try_into()?;
let decryption_handle_hi: PedersenDecryptHandle =
2021-10-06 10:54:03 -04:00
self.decrypt_handles_hi.dest.try_into()?;
2021-10-04 09:48:05 -04:00
let decryption_handle = combine_u32_handles(decryption_handle_lo, decryption_handle_hi);
Ok((transfer_comm, decryption_handle).into())
}
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 {
// Proof component for the spendable ciphertext components: R
pub R: pod::CompressedRistretto, // 32 bytes
// Proof component for the spendable ciphertext components: z
pub z: pod::Scalar, // 32 bytes
// Proof component for the transaction amount components: T_src
pub T_joint: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_1
pub T_1: pod::CompressedRistretto, // 32 bytes
// Proof component for the transaction amount components: T_2
pub T_2: pod::CompressedRistretto, // 32 bytes
// Range proof component
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-09-29 21:45:35 -07:00
#[allow(clippy::too_many_arguments)]
#[allow(clippy::many_single_char_names)]
pub fn new(
2021-10-01 09:48:45 -07:00
source_sk: &ElGamalSecretKey,
source_pk: &ElGamalPubkey,
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-09-29 21:45:35 -07:00
new_spendable_balance: u64,
new_spendable_ct: &ElGamalCiphertext,
2021-10-06 10:54:03 -04:00
) -> Self {
2021-09-29 21:45:35 -07:00
// TODO: should also commit to pubkeys and commitments later
2021-10-06 10:54:03 -04:00
let mut transcript = merlin::Transcript::new(b"TransferProof");
2021-09-29 21:45:35 -07:00
let H = PedersenBase::default().H;
let D = new_spendable_ct.decrypt_handle.get_point();
let s = source_sk.get_scalar();
// Generate proof for the new spendable ciphertext
let r_new = Scalar::random(&mut OsRng);
let y = Scalar::random(&mut OsRng);
let R = RistrettoPoint::multiscalar_mul(vec![y, r_new], vec![D, H]).compress();
2021-10-06 10:54:03 -04:00
transcript.append_point(b"R", &R);
let c = transcript.challenge_scalar(b"c");
2021-09-29 21:45:35 -07:00
let z = s + c * y;
let new_spendable_open = PedersenOpening(c * r_new);
2021-09-29 21:45:35 -07:00
// Generate proof for the transfer amounts
let t_1_blinding = PedersenOpening::random(&mut OsRng);
let t_2_blinding = PedersenOpening::random(&mut OsRng);
2021-09-29 21:45:35 -07:00
2021-10-06 10:54:03 -04:00
// generate the range proof
let range_proof = RangeProof::create_with(
vec![new_spendable_balance, transfer_amt.0, transfer_amt.1],
vec![64, 32, 32],
vec![&new_spendable_open, lo_open, hi_open],
&t_1_blinding,
&t_2_blinding,
&mut transcript,
);
let u = transcript.challenge_scalar(b"u");
2021-09-29 21:45:35 -07:00
let P_joint = RistrettoPoint::multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
source_pk.get_point(),
dest_pk.get_point(),
auditor_pk.get_point(),
],
);
let T_joint = (new_spendable_open.get_scalar() * P_joint).compress();
let T_1 = (t_1_blinding.get_scalar() * P_joint).compress();
let T_2 = (t_2_blinding.get_scalar() * P_joint).compress();
2021-10-06 10:54:03 -04:00
transcript.append_point(b"T_1", &T_1);
transcript.append_point(b"T_2", &T_2);
2021-09-29 21:45:35 -07:00
2021-10-06 10:54:03 -04:00
Self {
2021-09-29 21:45:35 -07:00
R: R.into(),
z: z.into(),
T_joint: T_joint.into(),
T_1: T_1.into(),
T_2: T_2.into(),
2021-10-06 10:54:03 -04:00
range_proof: range_proof.try_into().expect("invalid range proof"),
}
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-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-09-29 21:45:35 -07:00
transfer_public_keys: &TransferPubKeys,
) -> Result<(), ProofError> {
2021-10-06 10:54:03 -04:00
let mut transcript = Transcript::new(b"TransferProof");
let range_proof: RangeProof = self.range_proof.try_into()?;
2021-09-29 21:45:35 -07:00
let source_pk: ElGamalPubkey = transfer_public_keys.source_pk.try_into()?;
let dest_pk: ElGamalPubkey = transfer_public_keys.dest_pk.try_into()?;
let auditor_pk: ElGamalPubkey = transfer_public_keys.auditor_pk.try_into()?;
2021-09-29 21:45:35 -07:00
2021-10-06 10:54:03 -04:00
// derive Pedersen commitment for range proof verification
let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?;
2021-09-29 21:45:35 -07:00
let C = new_spendable_ct.message_comm.get_point();
let D = new_spendable_ct.decrypt_handle.get_point();
let R = self.R.into();
let z: Scalar = self.z.into();
transcript.validate_and_append_point(b"R", &R)?;
let c = transcript.challenge_scalar(b"c");
let R = R.decompress().ok_or(ProofError::VerificationError)?;
let spendable_comm_verification =
RistrettoPoint::multiscalar_mul(vec![Scalar::one(), -z, c], vec![C, D, R]).compress();
2021-10-06 10:54:03 -04:00
// verify range proof
let range_proof_verification = range_proof.verify_challenges(
vec![
&spendable_comm_verification,
&amount_comms.lo.into(),
&amount_comms.hi.into(),
],
vec![64_usize, 32_usize, 32_usize],
&mut transcript,
);
if range_proof_verification.is_err() {
2021-09-29 21:45:35 -07:00
return Err(ProofError::VerificationError);
}
2021-10-06 10:54:03 -04:00
let (z, x) = range_proof_verification.unwrap();
// check well-formedness of decryption handles
2021-09-29 21:45:35 -07:00
// derive joint public key
let u = transcript.challenge_scalar(b"u");
let P_joint = RistrettoPoint::vartime_multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
source_pk.get_point(),
dest_pk.get_point(),
auditor_pk.get_point(),
],
);
2021-10-06 10:54:03 -04:00
let t_x_blinding: Scalar = range_proof.t_x_blinding;
2021-09-29 21:45:35 -07:00
let T_1: CompressedRistretto = self.T_1.into();
let T_2: CompressedRistretto = self.T_2.into();
let handle_source_lo: PedersenDecryptHandle = decryption_handles_lo.source.try_into()?;
let handle_dest_lo: PedersenDecryptHandle = decryption_handles_lo.dest.try_into()?;
let handle_auditor_lo: PedersenDecryptHandle = decryption_handles_lo.auditor.try_into()?;
2021-09-29 21:45:35 -07:00
let D_joint: CompressedRistretto = self.T_joint.into();
let D_joint = D_joint.decompress().ok_or(ProofError::VerificationError)?;
let D_joint_lo = RistrettoPoint::vartime_multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
handle_source_lo.get_point(),
handle_dest_lo.get_point(),
handle_auditor_lo.get_point(),
],
);
let handle_source_hi: PedersenDecryptHandle = decryption_handles_hi.source.try_into()?;
let handle_dest_hi: PedersenDecryptHandle = decryption_handles_hi.dest.try_into()?;
let handle_auditor_hi: PedersenDecryptHandle = decryption_handles_hi.auditor.try_into()?;
2021-09-29 21:45:35 -07:00
let D_joint_hi = RistrettoPoint::vartime_multiscalar_mul(
vec![Scalar::one(), u, u * u],
vec![
handle_source_hi.get_point(),
handle_dest_hi.get_point(),
handle_auditor_hi.get_point(),
],
);
// TODO: combine Pedersen commitment verification above to here for efficiency
// TODO: might need to add an additional proof-of-knowledge here (additional 64 byte)
let mega_check = RistrettoPoint::optional_multiscalar_mul(
vec![-t_x_blinding, x, x * x, z * z, z * z * z, z * z * z * z],
vec![
Some(P_joint),
T_1.decompress(),
T_2.decompress(),
Some(D_joint),
Some(D_joint_lo),
Some(D_joint_hi),
],
)
.ok_or(ProofError::VerificationError)?;
if mega_check.is_identity() {
Ok(())
} else {
Err(ProofError::VerificationError)
}
}
}
/// The ElGamal public keys needed for a transfer
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
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 {
use super::*;
2021-10-11 11:25:16 -07:00
use crate::encryption::{
discrete_log::decode_u32_precomputation_for_G, 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
let ElGamalKeypair {
2021-10-07 12:44:17 -07:00
pk: source_pk,
sk: source_sk,
2021-10-11 11:25:16 -07:00
} = ElGamalKeypair::default();
let dest_pk = ElGamalKeypair::default().pk;
let auditor_pk = ElGamalKeypair::default().pk;
2021-09-29 21:45:35 -07:00
// create source account spendable ciphertext
let spendable_balance: u64 = 77;
let spendable_ct = source_pk.encrypt(spendable_balance);
// transfer amount
let transfer_amount: u64 = 55;
// create transfer data
let transfer_data = TransferData::new(
transfer_amount,
spendable_balance,
spendable_ct,
source_pk,
&source_sk,
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
let ElGamalKeypair {
2021-10-07 12:44:17 -07:00
pk: source_pk,
sk: source_sk,
2021-10-11 11:25:16 -07:00
} = ElGamalKeypair::default();
let ElGamalKeypair {
2021-10-07 12:44:17 -07:00
pk: dest_pk,
sk: dest_sk,
2021-10-11 11:25:16 -07:00
} = ElGamalKeypair::default();
let auditor_pk = ElGamalKeypair::default().pk;
// create source account spendable ciphertext
let spendable_balance: u64 = 77;
let spendable_ct = source_pk.encrypt(spendable_balance);
// transfer amount
let transfer_amount: u64 = 55;
// create transfer data
let transfer_data = TransferData::new(
transfer_amount,
spendable_balance,
spendable_ct,
source_pk,
&source_sk,
dest_pk,
auditor_pk,
);
let decryption_data = decode_u32_precomputation_for_G();
2021-10-04 10:07:47 -04:00
let source_ciphertext = transfer_data.source_ciphertext().unwrap();
2021-10-04 09:48:05 -04:00
assert_eq!(
source_ciphertext
.decrypt_u32_online(&source_sk, &decryption_data)
.unwrap(),
55_u32
);
2021-10-04 10:07:47 -04:00
let dest_ciphertext = transfer_data.dest_ciphertext().unwrap();
2021-10-04 09:48:05 -04:00
assert_eq!(
dest_ciphertext
.decrypt_u32_online(&dest_sk, &decryption_data)
.unwrap(),
55_u32
);
}
2021-09-29 21:45:35 -07:00
}