Skip to content

Commit

Permalink
[zk-token-sdk] divide fee encryption into two ciphertexts (solana-lab…
Browse files Browse the repository at this point in the history
…s#28472)

* divide fee encryption into two ciphertexts

* clippy

* update range proof

* add fee ciphertext decryption

* clean up split_u64 function

* remove unnecessary casting
  • Loading branch information
samkim-crypto authored and nickfrosty committed Jan 4, 2023
1 parent cbba06f commit 20ffd06
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 265 deletions.
8 changes: 6 additions & 2 deletions zk-token-sdk/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ pub enum ProofError {
#[error("proof generation failed")]
Generation,
#[error("proof failed to verify")]
Verification,
#[error("range proof failed to verify")]
RangeProof,
#[error("equality proof failed to verify")]
EqualityProof,
Expand All @@ -30,6 +28,12 @@ pub enum ProofError {
CiphertextDeserialization,
#[error("invalid scalar data")]
ScalarDeserialization,
#[error("invalid public key data")]
PubkeyDeserialization,
#[error("ciphertext does not exist in proof data")]
MissingCiphertext,
#[error("transfer amount split failed")]
TransferSplit,
}

#[derive(Error, Clone, Debug, Eq, PartialEq)]
Expand Down
33 changes: 17 additions & 16 deletions zk-token-sdk/src/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use {
errors::ProofError,
},
curve25519_dalek::scalar::Scalar,
subtle::ConstantTimeEq,
};
pub use {
close_account::CloseAccountData, pubkey_validity::PubkeyValidityData, transfer::TransferData,
Expand All @@ -32,30 +31,32 @@ pub trait Verifiable {
#[derive(Debug, Copy, Clone)]
pub enum Role {
Source,
Dest,
Destination,
Auditor,
WithdrawWithheldAuthority,
}

/// Takes in a 64-bit number `amount` and a bit length `bit_length`. It returns:
/// - the `bit_length` low bits of `amount` interpretted as u64
/// - the (64 - `bit_length`) high bits of `amount` interpretted as u64
#[cfg(not(target_os = "solana"))]
pub fn split_u64(
amount: u64,
lo_bit_length: usize,
hi_bit_length: usize,
) -> Result<(u64, u64), ProofError> {
assert!(lo_bit_length <= 64);
assert!(hi_bit_length <= 64);

if !bool::from((amount >> (lo_bit_length + hi_bit_length)).ct_eq(&0u64)) {
return Err(ProofError::TransferAmount);
pub fn split_u64(amount: u64, bit_length: usize) -> (u64, u64) {
if bit_length == 64 {
(amount, 0)
} else {
let lo = amount << (64 - bit_length) >> (64 - bit_length);
let hi = amount >> bit_length;
(lo, hi)
}
}

let lo = amount << (64 - lo_bit_length) >> (64 - lo_bit_length);
let hi = amount >> lo_bit_length;

Ok((lo, hi))
#[cfg(not(target_os = "solana"))]
pub fn combine_lo_hi_u64(amount_lo: u64, amount_hi: u64, bit_length: usize) -> u64 {
if bit_length == 64 {
amount_lo
} else {
amount_lo + (amount_hi << bit_length)
}
}

#[cfg(not(target_os = "solana"))]
Expand Down
75 changes: 32 additions & 43 deletions zk-token-sdk/src/instruction/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,7 @@ impl TransferData {
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
) -> Result<Self, ProofError> {
// split and encrypt transfer amount
let (amount_lo, amount_hi) = split_u64(
transfer_amount,
TRANSFER_AMOUNT_LO_BITS,
TRANSFER_AMOUNT_HI_BITS,
)?;
let (amount_lo, amount_hi) = split_u64(transfer_amount, TRANSFER_AMOUNT_LO_BITS);

let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
amount_lo,
Expand Down Expand Up @@ -151,31 +147,41 @@ impl TransferData {
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;

let handle_lo = match role {
Role::Source => ciphertext_lo.source_handle,
Role::Dest => ciphertext_lo.destination_handle,
Role::Auditor => ciphertext_lo.auditor_handle,
Role::Source => Some(ciphertext_lo.source_handle),
Role::Destination => Some(ciphertext_lo.destination_handle),
Role::Auditor => Some(ciphertext_lo.auditor_handle),
Role::WithdrawWithheldAuthority => None,
};

Ok(ElGamalCiphertext {
commitment: ciphertext_lo.commitment,
handle: handle_lo,
})
if let Some(handle) = handle_lo {
Ok(ElGamalCiphertext {
commitment: ciphertext_lo.commitment,
handle,
})
} else {
Err(ProofError::MissingCiphertext)
}
}

/// Extracts the lo ciphertexts associated with a transfer data
fn ciphertext_hi(&self, role: Role) -> Result<ElGamalCiphertext, ProofError> {
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;

let handle_hi = match role {
Role::Source => ciphertext_hi.source_handle,
Role::Dest => ciphertext_hi.destination_handle,
Role::Auditor => ciphertext_hi.auditor_handle,
Role::Source => Some(ciphertext_hi.source_handle),
Role::Destination => Some(ciphertext_hi.destination_handle),
Role::Auditor => Some(ciphertext_hi.auditor_handle),
Role::WithdrawWithheldAuthority => None,
};

Ok(ElGamalCiphertext {
commitment: ciphertext_hi.commitment,
handle: handle_hi,
})
if let Some(handle) = handle_hi {
Ok(ElGamalCiphertext {
commitment: ciphertext_hi.commitment,
handle,
})
} else {
Err(ProofError::MissingCiphertext)
}
}

/// Decrypts transfer amount from transfer data
Expand Down Expand Up @@ -449,11 +455,11 @@ impl TransferPubkeys {
let (source_pubkey, destination_pubkey, auditor_pubkey) = array_refs![bytes, 32, 32, 32];

let source_pubkey =
ElGamalPubkey::from_bytes(source_pubkey).ok_or(ProofError::Verification)?;
ElGamalPubkey::from_bytes(source_pubkey).ok_or(ProofError::Decryption)?;
let destination_pubkey =
ElGamalPubkey::from_bytes(destination_pubkey).ok_or(ProofError::Verification)?;
ElGamalPubkey::from_bytes(destination_pubkey).ok_or(ProofError::Decryption)?;
let auditor_pubkey =
ElGamalPubkey::from_bytes(auditor_pubkey).ok_or(ProofError::Verification)?;
ElGamalPubkey::from_bytes(auditor_pubkey).ok_or(ProofError::Decryption)?;

Ok(Self {
source_pubkey,
Expand Down Expand Up @@ -574,26 +580,7 @@ mod test {

assert!(transfer_data.verify().is_ok());

// Case 4: transfer amount too big

// create source account spendable ciphertext
let spendable_balance: u64 = u64::max_value();
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);

// transfer amount
let transfer_amount: u64 = 1u64 << (TRANSFER_AMOUNT_LO_BITS + TRANSFER_AMOUNT_HI_BITS);

// create transfer data
let transfer_data = TransferData::new(
transfer_amount,
(spendable_balance, &spendable_ciphertext),
&source_keypair,
(&dest_pk, &auditor_pk),
);

assert!(transfer_data.is_err());

// Case 5: invalid destination or auditor pubkey
// Case 4: invalid destination or auditor pubkey
let spendable_balance: u64 = 0;
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);

Expand Down Expand Up @@ -667,7 +654,9 @@ mod test {
);

assert_eq!(
transfer_data.decrypt_amount(Role::Dest, &dest_sk).unwrap(),
transfer_data
.decrypt_amount(Role::Destination, &dest_sk)
.unwrap(),
550000_u64,
);

Expand Down
Loading

0 comments on commit 20ffd06

Please sign in to comment.