2022-10-19 01:36:57 -07:00
|
|
|
use crate::{
|
|
|
|
curve25519::{
|
|
|
|
ristretto::{add_ristretto, multiply_ristretto, subtract_ristretto, PodRistrettoPoint},
|
|
|
|
scalar::PodScalar,
|
|
|
|
},
|
|
|
|
zk_token_elgamal::pod,
|
|
|
|
};
|
|
|
|
|
|
|
|
const SHIFT_BITS: usize = 16;
|
|
|
|
|
|
|
|
const G: PodRistrettoPoint = PodRistrettoPoint([
|
|
|
|
226, 242, 174, 10, 106, 188, 78, 113, 168, 132, 169, 97, 197, 0, 81, 95, 88, 227, 11, 106, 165,
|
|
|
|
130, 221, 141, 182, 166, 89, 69, 224, 141, 45, 118,
|
|
|
|
]);
|
|
|
|
|
|
|
|
/// Add two ElGamal ciphertexts
|
|
|
|
pub fn add(
|
|
|
|
left_ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
right_ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
) -> Option<pod::ElGamalCiphertext> {
|
|
|
|
let (left_commitment, left_handle): (pod::PedersenCommitment, pod::DecryptHandle) =
|
|
|
|
(*left_ciphertext).into();
|
|
|
|
let (right_commitment, right_handle): (pod::PedersenCommitment, pod::DecryptHandle) =
|
|
|
|
(*right_ciphertext).into();
|
|
|
|
|
|
|
|
let result_commitment: pod::PedersenCommitment =
|
|
|
|
add_ristretto(&left_commitment.into(), &right_commitment.into())?.into();
|
|
|
|
let result_handle: pod::DecryptHandle =
|
|
|
|
add_ristretto(&left_handle.into(), &right_handle.into())?.into();
|
|
|
|
|
|
|
|
Some((result_commitment, result_handle).into())
|
2021-09-30 10:25:36 -07:00
|
|
|
}
|
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
/// Multiply an ElGamal ciphertext by a scalar
|
|
|
|
pub fn multiply(
|
|
|
|
scalar: &PodScalar,
|
|
|
|
ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
) -> Option<pod::ElGamalCiphertext> {
|
|
|
|
let (commitment, handle): (pod::PedersenCommitment, pod::DecryptHandle) = (*ciphertext).into();
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let commitment_point: PodRistrettoPoint = commitment.into();
|
|
|
|
let handle_point: PodRistrettoPoint = handle.into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let result_commitment: pod::PedersenCommitment =
|
|
|
|
multiply_ristretto(scalar, &commitment_point)?.into();
|
|
|
|
let result_handle: pod::DecryptHandle = multiply_ristretto(scalar, &handle_point)?.into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
Some((result_commitment, result_handle).into())
|
|
|
|
}
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
/// Compute `left_ciphertext + (right_ciphertext_lo + 2^16 * right_ciphertext_hi)`
|
|
|
|
pub fn add_with_lo_hi(
|
|
|
|
left_ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
right_ciphertext_lo: &pod::ElGamalCiphertext,
|
|
|
|
right_ciphertext_hi: &pod::ElGamalCiphertext,
|
|
|
|
) -> Option<pod::ElGamalCiphertext> {
|
|
|
|
let shift_scalar = to_scalar(1_u64 << SHIFT_BITS);
|
|
|
|
let shifted_right_ciphertext_hi = multiply(&shift_scalar, right_ciphertext_hi)?;
|
|
|
|
let combined_right_ciphertext = add(right_ciphertext_lo, &shifted_right_ciphertext_hi)?;
|
|
|
|
add(left_ciphertext, &combined_right_ciphertext)
|
|
|
|
}
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
/// Subtract two ElGamal ciphertexts
|
|
|
|
pub fn subtract(
|
|
|
|
left_ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
right_ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
) -> Option<pod::ElGamalCiphertext> {
|
|
|
|
let (left_commitment, left_handle): (pod::PedersenCommitment, pod::DecryptHandle) =
|
|
|
|
(*left_ciphertext).into();
|
|
|
|
let (right_commitment, right_handle): (pod::PedersenCommitment, pod::DecryptHandle) =
|
|
|
|
(*right_ciphertext).into();
|
|
|
|
|
|
|
|
let result_commitment: pod::PedersenCommitment =
|
|
|
|
subtract_ristretto(&left_commitment.into(), &right_commitment.into())?.into();
|
|
|
|
let result_handle: pod::DecryptHandle =
|
|
|
|
subtract_ristretto(&left_handle.into(), &right_handle.into())?.into();
|
|
|
|
|
|
|
|
Some((result_commitment, result_handle).into())
|
|
|
|
}
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
/// Compute `left_ciphertext - (right_ciphertext_lo + 2^16 * right_ciphertext_hi)`
|
|
|
|
pub fn subtract_with_lo_hi(
|
|
|
|
left_ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
right_ciphertext_lo: &pod::ElGamalCiphertext,
|
|
|
|
right_ciphertext_hi: &pod::ElGamalCiphertext,
|
|
|
|
) -> Option<pod::ElGamalCiphertext> {
|
|
|
|
let shift_scalar = to_scalar(1_u64 << SHIFT_BITS);
|
|
|
|
let shifted_right_ciphertext_hi = multiply(&shift_scalar, right_ciphertext_hi)?;
|
|
|
|
let combined_right_ciphertext = add(right_ciphertext_lo, &shifted_right_ciphertext_hi)?;
|
|
|
|
subtract(left_ciphertext, &combined_right_ciphertext)
|
|
|
|
}
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
/// Add a constant amount to a ciphertext
|
|
|
|
pub fn add_to(ciphertext: &pod::ElGamalCiphertext, amount: u64) -> Option<pod::ElGamalCiphertext> {
|
|
|
|
let amount_scalar = to_scalar(amount);
|
|
|
|
let amount_point = multiply_ristretto(&amount_scalar, &G)?;
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let (commitment, handle): (pod::PedersenCommitment, pod::DecryptHandle) = (*ciphertext).into();
|
|
|
|
let commitment_point: PodRistrettoPoint = commitment.into();
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let result_commitment: pod::PedersenCommitment =
|
|
|
|
add_ristretto(&commitment_point, &amount_point)?.into();
|
|
|
|
Some((result_commitment, handle).into())
|
|
|
|
}
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
/// Subtract a constant amount to a ciphertext
|
|
|
|
pub fn subtract_from(
|
|
|
|
ciphertext: &pod::ElGamalCiphertext,
|
|
|
|
amount: u64,
|
|
|
|
) -> Option<pod::ElGamalCiphertext> {
|
|
|
|
let amount_scalar = to_scalar(amount);
|
|
|
|
let amount_point = multiply_ristretto(&amount_scalar, &G)?;
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let (commitment, handle): (pod::PedersenCommitment, pod::DecryptHandle) = (*ciphertext).into();
|
|
|
|
let commitment_point: PodRistrettoPoint = commitment.into();
|
2022-06-13 06:39:07 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let result_commitment: pod::PedersenCommitment =
|
|
|
|
subtract_ristretto(&commitment_point, &amount_point)?.into();
|
|
|
|
Some((result_commitment, handle).into())
|
2021-09-30 10:25:36 -07:00
|
|
|
}
|
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
/// Convert a `u64` amount into a curve25519 scalar
|
|
|
|
fn to_scalar(amount: u64) -> PodScalar {
|
|
|
|
let mut bytes = [0u8; 32];
|
|
|
|
bytes[..8].copy_from_slice(&amount.to_le_bytes());
|
|
|
|
PodScalar(bytes)
|
|
|
|
}
|
2021-09-30 12:13:02 -07:00
|
|
|
|
2021-09-30 10:25:36 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use {
|
|
|
|
crate::{
|
|
|
|
encryption::{
|
2021-10-11 11:25:16 -07:00
|
|
|
elgamal::{ElGamalCiphertext, ElGamalKeypair},
|
2021-10-05 06:02:52 -07:00
|
|
|
pedersen::{Pedersen, PedersenOpening},
|
2021-09-30 10:25:36 -07:00
|
|
|
},
|
2022-10-19 01:36:57 -07:00
|
|
|
instruction::split_u64,
|
2021-09-30 10:25:36 -07:00
|
|
|
zk_token_elgamal::{ops, pod},
|
|
|
|
},
|
|
|
|
bytemuck::Zeroable,
|
|
|
|
curve25519_dalek::scalar::Scalar,
|
|
|
|
std::convert::TryInto,
|
|
|
|
};
|
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
const TWO_16: u64 = 65536;
|
|
|
|
|
2021-09-30 10:25:36 -07:00
|
|
|
#[test]
|
|
|
|
fn test_zero_ct() {
|
2021-09-30 11:11:53 -07:00
|
|
|
let spendable_balance = pod::ElGamalCiphertext::zeroed();
|
|
|
|
let spendable_ct: ElGamalCiphertext = spendable_balance.try_into().unwrap();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
// spendable_ct should be an encryption of 0 for any public key when
|
|
|
|
// `PedersenOpen::default()` is used
|
2022-01-21 17:56:27 -08:00
|
|
|
let public = ElGamalKeypair::new_rand().public;
|
2021-09-30 10:25:36 -07:00
|
|
|
let balance: u64 = 0;
|
|
|
|
assert_eq!(
|
|
|
|
spendable_ct,
|
2021-10-11 11:32:39 -07:00
|
|
|
public.encrypt_with(balance, &PedersenOpening::default())
|
2021-09-30 10:25:36 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
// homomorphism should work like any other ciphertext
|
2022-01-21 17:56:27 -08:00
|
|
|
let open = PedersenOpening::new_rand();
|
2021-10-11 11:32:39 -07:00
|
|
|
let transfer_amount_ct = public.encrypt_with(55_u64, &open);
|
2021-09-30 11:11:53 -07:00
|
|
|
let transfer_amount_pod: pod::ElGamalCiphertext = transfer_amount_ct.into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2021-09-30 21:33:57 -07:00
|
|
|
let sum = ops::add(&spendable_balance, &transfer_amount_pod).unwrap();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2021-10-11 11:32:39 -07:00
|
|
|
let expected: pod::ElGamalCiphertext = public.encrypt_with(55_u64, &open).into();
|
2021-09-30 10:25:36 -07:00
|
|
|
assert_eq!(expected, sum);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_add_to() {
|
2021-09-30 11:11:53 -07:00
|
|
|
let spendable_balance = pod::ElGamalCiphertext::zeroed();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2021-09-30 21:33:57 -07:00
|
|
|
let added_ct = ops::add_to(&spendable_balance, 55).unwrap();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2022-01-21 17:56:27 -08:00
|
|
|
let public = ElGamalKeypair::new_rand().public;
|
2021-10-11 11:32:39 -07:00
|
|
|
let expected: pod::ElGamalCiphertext = public
|
|
|
|
.encrypt_with(55_u64, &PedersenOpening::default())
|
|
|
|
.into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
assert_eq!(expected, added_ct);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_subtract_from() {
|
|
|
|
let amount = 77_u64;
|
2022-01-21 17:56:27 -08:00
|
|
|
let public = ElGamalKeypair::new_rand().public;
|
|
|
|
let open = PedersenOpening::new_rand();
|
2021-10-11 11:32:39 -07:00
|
|
|
let encrypted_amount: pod::ElGamalCiphertext = public.encrypt_with(amount, &open).into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2021-09-30 21:33:57 -07:00
|
|
|
let subtracted_ct = ops::subtract_from(&encrypted_amount, 55).unwrap();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2021-10-11 11:32:39 -07:00
|
|
|
let expected: pod::ElGamalCiphertext = public.encrypt_with(22_u64, &open).into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
assert_eq!(expected, subtracted_ct);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_transfer_arithmetic() {
|
|
|
|
// transfer amount
|
|
|
|
let transfer_amount: u64 = 55;
|
2022-11-29 22:11:38 -08:00
|
|
|
let (amount_lo, amount_hi) = split_u64(transfer_amount, 16);
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
// generate public keys
|
2022-01-21 17:56:27 -08:00
|
|
|
let source_pk = ElGamalKeypair::new_rand().public;
|
|
|
|
let dest_pk = ElGamalKeypair::new_rand().public;
|
|
|
|
let auditor_pk = ElGamalKeypair::new_rand().public;
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
// commitments associated with TransferRangeProof
|
2021-10-05 06:02:52 -07:00
|
|
|
let (comm_lo, open_lo) = Pedersen::new(amount_lo);
|
|
|
|
let (comm_hi, open_hi) = Pedersen::new(amount_hi);
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2021-10-05 06:02:52 -07:00
|
|
|
let comm_lo: pod::PedersenCommitment = comm_lo.into();
|
|
|
|
let comm_hi: pod::PedersenCommitment = comm_hi.into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
// decryption handles associated with TransferValidityProof
|
2022-01-21 17:56:27 -08:00
|
|
|
let handle_source_lo: pod::DecryptHandle = source_pk.decrypt_handle(&open_lo).into();
|
|
|
|
let handle_dest_lo: pod::DecryptHandle = dest_pk.decrypt_handle(&open_lo).into();
|
|
|
|
let _handle_auditor_lo: pod::DecryptHandle = auditor_pk.decrypt_handle(&open_lo).into();
|
|
|
|
|
|
|
|
let handle_source_hi: pod::DecryptHandle = source_pk.decrypt_handle(&open_hi).into();
|
|
|
|
let handle_dest_hi: pod::DecryptHandle = dest_pk.decrypt_handle(&open_hi).into();
|
|
|
|
let _handle_auditor_hi: pod::DecryptHandle = auditor_pk.decrypt_handle(&open_hi).into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
// source spendable and recipient pending
|
2022-01-21 17:56:27 -08:00
|
|
|
let source_open = PedersenOpening::new_rand();
|
|
|
|
let dest_open = PedersenOpening::new_rand();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2021-09-30 11:11:53 -07:00
|
|
|
let source_spendable_ct: pod::ElGamalCiphertext =
|
2021-09-30 10:25:36 -07:00
|
|
|
source_pk.encrypt_with(77_u64, &source_open).into();
|
2021-09-30 11:11:53 -07:00
|
|
|
let dest_pending_ct: pod::ElGamalCiphertext =
|
|
|
|
dest_pk.encrypt_with(77_u64, &dest_open).into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
// program arithmetic for the source account
|
2021-09-30 11:11:53 -07:00
|
|
|
let source_lo_ct: pod::ElGamalCiphertext = (comm_lo, handle_source_lo).into();
|
|
|
|
let source_hi_ct: pod::ElGamalCiphertext = (comm_hi, handle_source_hi).into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
let final_source_spendable =
|
2022-10-19 01:36:57 -07:00
|
|
|
ops::subtract_with_lo_hi(&source_spendable_ct, &source_lo_ct, &source_hi_ct).unwrap();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
|
|
|
let final_source_open =
|
2022-10-19 01:36:57 -07:00
|
|
|
source_open - (open_lo.clone() + open_hi.clone() * Scalar::from(TWO_16));
|
2021-09-30 11:11:53 -07:00
|
|
|
let expected_source: pod::ElGamalCiphertext =
|
2021-09-30 10:25:36 -07:00
|
|
|
source_pk.encrypt_with(22_u64, &final_source_open).into();
|
|
|
|
assert_eq!(expected_source, final_source_spendable);
|
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
// program arithemtic for the destination account
|
2021-09-30 11:11:53 -07:00
|
|
|
let dest_lo_ct: pod::ElGamalCiphertext = (comm_lo, handle_dest_lo).into();
|
|
|
|
let dest_hi_ct: pod::ElGamalCiphertext = (comm_hi, handle_dest_hi).into();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let final_dest_pending =
|
|
|
|
ops::add_with_lo_hi(&dest_pending_ct, &dest_lo_ct, &dest_hi_ct).unwrap();
|
2021-09-30 10:25:36 -07:00
|
|
|
|
2022-10-19 01:36:57 -07:00
|
|
|
let final_dest_open = dest_open + (open_lo + open_hi * Scalar::from(TWO_16));
|
2021-09-30 11:11:53 -07:00
|
|
|
let expected_dest_ct: pod::ElGamalCiphertext =
|
2021-09-30 10:25:36 -07:00
|
|
|
dest_pk.encrypt_with(132_u64, &final_dest_open).into();
|
|
|
|
assert_eq!(expected_dest_ct, final_dest_pending);
|
|
|
|
}
|
|
|
|
}
|