diff --git a/zk-token-sdk/src/instruction/transfer.rs b/zk-token-sdk/src/instruction/transfer.rs index 88c5b6c10..69b9fb0fb 100644 --- a/zk-token-sdk/src/instruction/transfer.rs +++ b/zk-token-sdk/src/instruction/transfer.rs @@ -26,14 +26,26 @@ use { std::convert::TryInto, }; -/// Just a grouping struct for the data required for the two transfer instructions. It is -/// convenient to generate the two components jointly as they share common components. #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct TransferData { - pub range_proof: TransferRangeProofData, - pub validity_proof: TransferValidityProofData, - ephemeral_state: TransferEphemeralState, // 128 bytes + /// 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, } #[cfg(not(target_arch = "bpf"))] @@ -65,19 +77,19 @@ impl TransferData { let handle_auditor_hi = auditor_pk.decrypt_handle(&open_hi); // message encoding as Pedersen commitments, which will be included in range proof data - let amount_comms = TransferComms { + let amount_comms = TransferCommitments { lo: comm_lo.into(), hi: comm_hi.into(), }; // decryption handles, which will be included in the validity proof data - let decryption_handles_lo = TransferHandles { + let decrypt_handles_lo = TransferDecryptHandles { source: handle_source_lo.into(), dest: handle_dest_lo.into(), auditor: handle_auditor_lo.into(), }; - let decryption_handles_hi = TransferHandles { + let decrypt_handles_hi = TransferDecryptHandles { source: handle_source_hi.into(), dest: handle_dest_hi.into(), auditor: handle_auditor_hi.into(), @@ -105,7 +117,7 @@ impl TransferData { }; // range_proof and validity_proof should be generated together - let (transfer_proofs, ephemeral_state) = TransferProofs::new( + let proof = TransferProof::new( source_sk, &source_pk, &dest_pk, @@ -117,44 +129,27 @@ impl TransferData { &new_spendable_ct, ); - // generate data components - let range_proof = TransferRangeProofData { + Self { amount_comms, - proof: transfer_proofs.range_proof, - }; - - let validity_proof = TransferValidityProofData { - decryption_handles_lo, - decryption_handles_hi, - transfer_public_keys, + decrypt_handles_lo, + decrypt_handles_hi, new_spendable_ct: new_spendable_ct.into(), - proof: transfer_proofs.validity_proof, - }; - - TransferData { - range_proof, - validity_proof, - ephemeral_state, + transfer_public_keys, + proof, } } /// Extracts the lo and hi source ciphertexts associated with a transfer data and returns the /// *combined* ciphertext pub fn source_ciphertext(&self) -> Result { - let transfer_comms_lo: PedersenCommitment = self.range_proof.amount_comms.lo.try_into()?; - let transfer_comms_hi: PedersenCommitment = self.range_proof.amount_comms.hi.try_into()?; + let transfer_comms_lo: PedersenCommitment = self.amount_comms.lo.try_into()?; + let transfer_comms_hi: PedersenCommitment = self.amount_comms.hi.try_into()?; let transfer_comm = combine_u32_comms(transfer_comms_lo, transfer_comms_hi); - let decryption_handle_lo: PedersenDecryptHandle = self - .validity_proof - .decryption_handles_lo - .source - .try_into()?; - let decryption_handle_hi: PedersenDecryptHandle = self - .validity_proof - .decryption_handles_hi - .source - .try_into()?; + let decryption_handle_lo: PedersenDecryptHandle = + self.decrypt_handles_lo.source.try_into()?; + let decryption_handle_hi: PedersenDecryptHandle = + self.decrypt_handles_hi.source.try_into()?; let decryption_handle = combine_u32_handles(decryption_handle_lo, decryption_handle_hi); Ok((transfer_comm, decryption_handle).into()) @@ -163,14 +158,14 @@ impl TransferData { /// Extracts the lo and hi destination ciphertexts associated with a transfer data and returns /// the *combined* ciphertext pub fn dest_ciphertext(&self) -> Result { - let transfer_comms_lo: PedersenCommitment = self.range_proof.amount_comms.lo.try_into()?; - let transfer_comms_hi: PedersenCommitment = self.range_proof.amount_comms.hi.try_into()?; + let transfer_comms_lo: PedersenCommitment = self.amount_comms.lo.try_into()?; + let transfer_comms_hi: PedersenCommitment = self.amount_comms.hi.try_into()?; let transfer_comm = combine_u32_comms(transfer_comms_lo, transfer_comms_hi); let decryption_handle_lo: PedersenDecryptHandle = - self.validity_proof.decryption_handles_lo.dest.try_into()?; + self.decrypt_handles_lo.dest.try_into()?; let decryption_handle_hi: PedersenDecryptHandle = - self.validity_proof.decryption_handles_hi.dest.try_into()?; + self.decrypt_handles_hi.dest.try_into()?; let decryption_handle = combine_u32_handles(decryption_handle_lo, decryption_handle_hi); Ok((transfer_comm, decryption_handle).into()) @@ -180,101 +175,37 @@ impl TransferData { #[cfg(not(target_arch = "bpf"))] impl Verifiable for TransferData { fn verify(&self) -> Result<(), ProofError> { - self.range_proof.verify(&self.ephemeral_state)?; - self.validity_proof.verify(&self.ephemeral_state) - } -} - -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct TransferRangeProofData { - /// The transfer amount encoded as Pedersen commitments - pub amount_comms: TransferComms, // 64 bytes - - /// Proof that certifies: - /// 1. the source account has enough funds for the transfer (i.e. the final balance is a - /// 64-bit positive number) - /// 2. the transfer amount is a 64-bit positive number - pub proof: pod::RangeProof128, // 736 bytes -} - -#[cfg(not(target_arch = "bpf"))] -impl TransferRangeProofData { - fn verify(&self, ephemeral_state: &TransferEphemeralState) -> Result<(), ProofError> { - let mut transcript = Transcript::new(b"TransferRangeProof"); - - // standard range proof verification - let proof: RangeProof = self.proof.try_into()?; - proof.verify_with( - vec![ - &ephemeral_state.spendable_comm_verification.into(), - &self.amount_comms.lo.into(), - &self.amount_comms.hi.into(), - ], - vec![64_usize, 32_usize, 32_usize], - Some(ephemeral_state.x.into()), - Some(ephemeral_state.z.into()), - &mut transcript, - ) - } -} - -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct TransferValidityProofData { - /// The decryption handles that allow decryption of the lo-bits - pub decryption_handles_lo: TransferHandles, // 96 bytes - - /// The decryption handles that allow decryption of the hi-bits - pub decryption_handles_hi: TransferHandles, // 96 bytes - - /// 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 - - /// Proof that certifies that the decryption handles are generated correctly - pub proof: ValidityProof, // 160 bytes -} - -/// The joint data that is shared between the two transfer instructions. -/// -/// Identical ephemeral data should be included in the two transfer instructions and this should be -/// checked by the ZK Token program. -#[derive(Clone, Copy, Pod, Zeroable, PartialEq)] -#[repr(C)] -pub struct TransferEphemeralState { - pub spendable_comm_verification: pod::PedersenCommitment, // 32 bytes - pub x: pod::Scalar, // 32 bytes - pub z: pod::Scalar, // 32 bytes - pub t_x_blinding: pod::Scalar, // 32 bytes -} - -#[cfg(not(target_arch = "bpf"))] -impl TransferValidityProofData { - fn verify(&self, ephemeral_state: &TransferEphemeralState) -> Result<(), ProofError> { self.proof.verify( - &self.new_spendable_ct.try_into()?, - &self.decryption_handles_lo, - &self.decryption_handles_hi, + &self.amount_comms, + &self.decrypt_handles_lo, + &self.decrypt_handles_hi, + &self.new_spendable_ct, &self.transfer_public_keys, - ephemeral_state, ) } } -/// Just a grouping struct for the two proofs that are needed for a transfer instruction. The two -/// proofs have to be generated together as they share joint data. -#[cfg(not(target_arch = "bpf"))] -pub struct TransferProofs { +#[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 pub range_proof: pod::RangeProof128, - pub validity_proof: ValidityProof, } #[allow(non_snake_case)] #[cfg(not(target_arch = "bpf"))] -impl TransferProofs { +impl TransferProof { #[allow(clippy::too_many_arguments)] #[allow(clippy::many_single_char_names)] pub fn new( @@ -287,9 +218,9 @@ impl TransferProofs { hi_open: &PedersenOpening, new_spendable_balance: u64, new_spendable_ct: &ElGamalCiphertext, - ) -> (Self, TransferEphemeralState) { + ) -> Self { // TODO: should also commit to pubkeys and commitments later - let mut transcript_validity_proof = merlin::Transcript::new(b"TransferValidityProof"); + let mut transcript = merlin::Transcript::new(b"TransferProof"); let H = PedersenBase::default().H; let D = new_spendable_ct.decrypt_handle.get_point(); @@ -300,20 +231,28 @@ impl TransferProofs { let y = Scalar::random(&mut OsRng); let R = RistrettoPoint::multiscalar_mul(vec![y, r_new], vec![D, H]).compress(); - transcript_validity_proof.append_point(b"R", &R); - let c = transcript_validity_proof.challenge_scalar(b"c"); + transcript.append_point(b"R", &R); + let c = transcript.challenge_scalar(b"c"); let z = s + c * y; let new_spendable_open = PedersenOpening(c * r_new); - let spendable_comm_verification = - Pedersen::with(new_spendable_balance, &new_spendable_open); - // Generate proof for the transfer amounts let t_1_blinding = PedersenOpening::random(&mut OsRng); let t_2_blinding = PedersenOpening::random(&mut OsRng); - let u = transcript_validity_proof.challenge_scalar(b"u"); + // 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"); + let P_joint = RistrettoPoint::multiscalar_mul( vec![Scalar::one(), u, u * u], vec![ @@ -326,85 +265,41 @@ impl TransferProofs { let T_1 = (t_1_blinding.get_scalar() * P_joint).compress(); let T_2 = (t_2_blinding.get_scalar() * P_joint).compress(); - transcript_validity_proof.append_point(b"T_1", &T_1); - transcript_validity_proof.append_point(b"T_2", &T_2); + transcript.append_point(b"T_1", &T_1); + transcript.append_point(b"T_2", &T_2); - // define the validity proof - let validity_proof = ValidityProof { + Self { R: R.into(), z: z.into(), T_joint: T_joint.into(), T_1: T_1.into(), T_2: T_2.into(), - }; - - // generate the range proof - let mut transcript_range_proof = Transcript::new(b"TransferRangeProof"); - let (range_proof, x, z) = 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_range_proof, - ); - - // define ephemeral state - let ephemeral_state = TransferEphemeralState { - spendable_comm_verification: spendable_comm_verification.into(), - x: x.into(), - z: z.into(), - t_x_blinding: range_proof.t_x_blinding.into(), - }; - - ( - Self { - range_proof: range_proof.try_into().expect("valid range_proof"), - validity_proof, - }, - ephemeral_state, - ) + range_proof: range_proof.try_into().expect("invalid range proof"), + } } } -/// Proof components for transfer instructions. -/// -/// These two components should be output by a RangeProof creation function. -#[allow(non_snake_case)] -#[derive(Clone, Copy, Pod, Zeroable)] -#[repr(C)] -pub struct ValidityProof { - // 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 -} - #[allow(non_snake_case)] #[cfg(not(target_arch = "bpf"))] -impl ValidityProof { +impl TransferProof { pub fn verify( self, - new_spendable_ct: &ElGamalCiphertext, - decryption_handles_lo: &TransferHandles, - decryption_handles_hi: &TransferHandles, + amount_comms: &TransferCommitments, + decryption_handles_lo: &TransferDecryptHandles, + decryption_handles_hi: &TransferDecryptHandles, + new_spendable_ct: &pod::ElGamalCiphertext, transfer_public_keys: &TransferPubKeys, - ephemeral_state: &TransferEphemeralState, ) -> Result<(), ProofError> { - let mut transcript = Transcript::new(b"TransferValidityProof"); + let mut transcript = Transcript::new(b"TransferProof"); + + let range_proof: RangeProof = self.range_proof.try_into()?; 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()?; - // verify Pedersen commitment in the ephemeral state - let C_ephemeral: CompressedRistretto = ephemeral_state.spendable_comm_verification.into(); + // derive Pedersen commitment for range proof verification + let new_spendable_ct: ElGamalCiphertext = (*new_spendable_ct).try_into()?; let C = new_spendable_ct.message_comm.get_point(); let D = new_spendable_ct.decrypt_handle.get_point(); @@ -420,9 +315,23 @@ impl ValidityProof { let spendable_comm_verification = RistrettoPoint::multiscalar_mul(vec![Scalar::one(), -z, c], vec![C, D, R]).compress(); - if C_ephemeral != spendable_comm_verification { + // 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() { return Err(ProofError::VerificationError); } + let (z, x) = range_proof_verification.unwrap(); + + // check well-formedness of decryption handles // derive joint public key let u = transcript.challenge_scalar(b"u"); @@ -435,14 +344,10 @@ impl ValidityProof { ], ); - // check well-formedness of decryption handles - let t_x_blinding: Scalar = ephemeral_state.t_x_blinding.into(); + let t_x_blinding: Scalar = range_proof.t_x_blinding; let T_1: CompressedRistretto = self.T_1.into(); let T_2: CompressedRistretto = self.T_2.into(); - let x = ephemeral_state.x.into(); - let z: Scalar = ephemeral_state.z.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()?; @@ -507,7 +412,7 @@ pub struct TransferPubKeys { /// The transfer amount commitments needed for a transfer #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] -pub struct TransferComms { +pub struct TransferCommitments { pub lo: pod::PedersenCommitment, // 32 bytes pub hi: pod::PedersenCommitment, // 32 bytes } @@ -515,7 +420,7 @@ pub struct TransferComms { /// The decryption handles needed for a transfer #[derive(Clone, Copy, Pod, Zeroable)] #[repr(C)] -pub struct TransferHandles { +pub struct TransferDecryptHandles { pub source: pod::PedersenDecryptHandle, // 32 bytes pub dest: pod::PedersenDecryptHandle, // 32 bytes pub auditor: pod::PedersenDecryptHandle, // 32 bytes