Add additional zkp for fee (#23112)

* zk-token-sdk: add equality proof for fee

* zk-token-sdk: tweak some naming conventions for readability

* zk-token-sdk: add verify withdraw withheld instruction

* zk-token-sdk: add test for withdraw withheld verification

* zk-token-sdk: more renaming of variables for readability

* zk-token-sdk: cargo fmt

* zk-token-sdk: minor

* zk-token-sdk: resolve bpf compilation warnings

* zk-token-sdk: minor update to doc
This commit is contained in:
samkim-crypto 2022-02-17 12:45:07 -05:00 committed by GitHub
parent 1a68f81f89
commit b4100a9b5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 849 additions and 254 deletions

View File

@ -49,6 +49,10 @@ pub fn process_instruction(
ic_msg!(invoke_context, "VerifyWithdraw");
verify::<WithdrawData>(input, invoke_context)
}
ProofInstruction::VerifyWithdrawWithheldTokens => {
ic_msg!(invoke_context, "VerifyWithdraw");
verify::<WithdrawData>(input, invoke_context)
}
ProofInstruction::VerifyTransfer => {
ic_msg!(invoke_context, "VerifyTransfer");
verify::<TransferData>(input, invoke_context)

View File

@ -98,7 +98,7 @@ impl CloseAccountProof {
) -> Self {
let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
CloseAccountProof {
Self {
proof: proof.into(),
}
}

View File

@ -2,6 +2,7 @@ pub mod close_account;
pub mod transfer;
pub mod transfer_with_fee;
pub mod withdraw;
pub mod withdraw_withheld;
#[cfg(not(target_arch = "bpf"))]
use {
@ -17,6 +18,7 @@ use {
pub use {
close_account::CloseAccountData, transfer::TransferData,
transfer_with_fee::TransferWithFeeData, withdraw::WithdrawData,
withdraw_withheld::WithdrawWithheldTokensData,
};
/// Constant for 2^32

View File

@ -15,7 +15,9 @@ use {
errors::ProofError,
instruction::{combine_u32_ciphertexts, split_u64_into_u32, Role, Verifiable, TWO_32},
range_proof::RangeProof,
sigma_proofs::{equality_proof::EqualityProof, validity_proof::AggregatedValidityProof},
sigma_proofs::{
equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof,
},
transcript::TranscriptProtocol,
},
arrayref::{array_ref, array_refs},
@ -23,14 +25,21 @@ use {
std::convert::TryInto,
};
#[cfg(not(target_arch = "bpf"))]
const TRANSFER_SOURCE_AMOUNT_BIT_LENGTH: usize = 64;
#[cfg(not(target_arch = "bpf"))]
const TRANSFER_AMOUNT_LO_BIT_LENGTH: usize = 32;
#[cfg(not(target_arch = "bpf"))]
const TRANSFER_AMOUNT_HI_BIT_LENGTH: usize = 32;
#[derive(Clone)]
#[repr(C)]
#[cfg(not(target_arch = "bpf"))]
pub struct TransferAmountEncryption {
pub commitment: PedersenCommitment,
pub source: DecryptHandle,
pub dest: DecryptHandle,
pub auditor: DecryptHandle,
pub handle_source: DecryptHandle,
pub handle_dest: DecryptHandle,
pub handle_auditor: DecryptHandle,
}
#[cfg(not(target_arch = "bpf"))]
@ -44,9 +53,9 @@ impl TransferAmountEncryption {
let (commitment, opening) = Pedersen::new(amount);
let transfer_amount_encryption = Self {
commitment,
source: pubkey_source.decrypt_handle(&opening),
dest: pubkey_dest.decrypt_handle(&opening),
auditor: pubkey_auditor.decrypt_handle(&opening),
handle_source: pubkey_source.decrypt_handle(&opening),
handle_dest: pubkey_dest.decrypt_handle(&opening),
handle_auditor: pubkey_auditor.decrypt_handle(&opening),
};
(transfer_amount_encryption, opening)
@ -55,9 +64,9 @@ impl TransferAmountEncryption {
pub fn to_pod(&self) -> pod::TransferAmountEncryption {
pod::TransferAmountEncryption {
commitment: self.commitment.into(),
source: self.source.into(),
dest: self.dest.into(),
auditor: self.auditor.into(),
handle_source: self.handle_source.into(),
handle_dest: self.handle_dest.into(),
handle_auditor: self.handle_auditor.into(),
}
}
}
@ -113,12 +122,12 @@ impl TransferData {
let transfer_amount_lo_source = ElGamalCiphertext {
commitment: ciphertext_lo.commitment,
handle: ciphertext_lo.source,
handle: ciphertext_lo.handle_source,
};
let transfer_amount_hi_source = ElGamalCiphertext {
commitment: ciphertext_hi.commitment,
handle: ciphertext_hi.source,
handle: ciphertext_hi.handle_source,
};
let ciphertext_new_source = ciphertext_old_source
@ -126,9 +135,9 @@ impl TransferData {
// generate transcript and append all public inputs
let pod_transfer_pubkeys = pod::TransferPubkeys {
source: keypair_source.public.into(),
dest: (*pubkey_dest).into(),
auditor: (*pubkey_auditor).into(),
pubkey_source: keypair_source.public.into(),
pubkey_dest: (*pubkey_dest).into(),
pubkey_auditor: (*pubkey_auditor).into(),
};
let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.into();
let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.into();
@ -165,9 +174,9 @@ impl TransferData {
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
let handle_lo = match role {
Role::Source => ciphertext_lo.source,
Role::Dest => ciphertext_lo.dest,
Role::Auditor => ciphertext_lo.auditor,
Role::Source => ciphertext_lo.handle_source,
Role::Dest => ciphertext_lo.handle_dest,
Role::Auditor => ciphertext_lo.handle_auditor,
};
Ok(ElGamalCiphertext {
@ -181,9 +190,9 @@ impl TransferData {
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
let handle_hi = match role {
Role::Source => ciphertext_hi.source,
Role::Dest => ciphertext_hi.dest,
Role::Auditor => ciphertext_hi.auditor,
Role::Source => ciphertext_hi.handle_source,
Role::Dest => ciphertext_hi.handle_dest,
Role::Auditor => ciphertext_hi.handle_auditor,
};
Ok(ElGamalCiphertext {
@ -247,7 +256,7 @@ pub struct TransferProof {
pub commitment_new_source: pod::PedersenCommitment,
/// Associated equality proof
pub equality_proof: pod::EqualityProof,
pub equality_proof: pod::CtxtCommEqualityProof,
/// Associated ciphertext validity proof
pub validity_proof: pod::AggregatedValidityProof,
@ -267,19 +276,19 @@ impl TransferProof {
) -> Transcript {
let mut transcript = Transcript::new(b"transfer-proof");
transcript.append_pubkey(b"pubkey_source", &transfer_pubkeys.source);
transcript.append_pubkey(b"pubkey_dest", &transfer_pubkeys.dest);
transcript.append_pubkey(b"pubkey_auditor", &transfer_pubkeys.auditor);
transcript.append_pubkey(b"pubkey-source", &transfer_pubkeys.pubkey_source);
transcript.append_pubkey(b"pubkey-dest", &transfer_pubkeys.pubkey_dest);
transcript.append_pubkey(b"pubkey-auditor", &transfer_pubkeys.pubkey_auditor);
transcript.append_commitment(b"comm-lo-amount", &ciphertext_lo.commitment);
transcript.append_handle(b"handle-lo-source", &ciphertext_lo.source);
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.dest);
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.auditor);
transcript.append_handle(b"handle-lo-source", &ciphertext_lo.handle_source);
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.handle_dest);
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.handle_auditor);
transcript.append_commitment(b"comm-hi-amount", &ciphertext_hi.commitment);
transcript.append_handle(b"handle-hi-source", &ciphertext_hi.source);
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.dest);
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.auditor);
transcript.append_handle(b"handle-hi-source", &ciphertext_hi.handle_source);
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.handle_dest);
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.handle_auditor);
transcript.append_ciphertext(b"ciphertext-new-source", ciphertext_new_source);
@ -302,7 +311,7 @@ impl TransferProof {
transcript.append_commitment(b"commitment-new-source", &pod_commitment_new_source);
// generate equality_proof
let equality_proof = EqualityProof::new(
let equality_proof = CtxtCommEqualityProof::new(
keypair_source,
ciphertext_new_source,
source_new_balance,
@ -325,7 +334,11 @@ impl TransferProof {
transfer_amount_lo as u64,
transfer_amount_hi as u64,
],
vec![64, 32, 32],
vec![
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
TRANSFER_AMOUNT_LO_BIT_LENGTH,
TRANSFER_AMOUNT_HI_BIT_LENGTH,
],
vec![&opening_source, opening_lo, opening_hi],
transcript,
);
@ -343,13 +356,13 @@ impl TransferProof {
ciphertext_lo: &TransferAmountEncryption,
ciphertext_hi: &TransferAmountEncryption,
transfer_pubkeys: &TransferPubkeys,
new_spendable_ciphertext: &ElGamalCiphertext,
ciphertext_new_spendable: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), ProofError> {
transcript.append_commitment(b"commitment-new-source", &self.commitment_new_source);
let commitment: PedersenCommitment = self.commitment_new_source.try_into()?;
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
let range_proof: RangeProof = self.range_proof.try_into()?;
@ -357,25 +370,24 @@ impl TransferProof {
//
// TODO: we can also consider verifying equality and range proof in a batch
equality_proof.verify(
&transfer_pubkeys.source,
new_spendable_ciphertext,
&transfer_pubkeys.pubkey_source,
ciphertext_new_spendable,
&commitment,
transcript,
)?;
println!("equality pass");
// verify validity proof
aggregated_validity_proof.verify(
(&transfer_pubkeys.dest, &transfer_pubkeys.auditor),
(
&transfer_pubkeys.pubkey_dest,
&transfer_pubkeys.pubkey_auditor,
),
(&ciphertext_lo.commitment, &ciphertext_hi.commitment),
(&ciphertext_lo.dest, &ciphertext_hi.dest),
(&ciphertext_lo.auditor, &ciphertext_hi.auditor),
(&ciphertext_lo.handle_dest, &ciphertext_hi.handle_dest),
(&ciphertext_lo.handle_auditor, &ciphertext_hi.handle_auditor),
transcript,
)?;
println!("validity pass");
// verify range proof
let commitment_new_source = self.commitment_new_source.try_into()?;
range_proof.verify(
@ -397,9 +409,9 @@ impl TransferProof {
#[repr(C)]
#[cfg(not(target_arch = "bpf"))]
pub struct TransferPubkeys {
pub source: ElGamalPubkey,
pub dest: ElGamalPubkey,
pub auditor: ElGamalPubkey,
pub pubkey_source: ElGamalPubkey,
pub pubkey_dest: ElGamalPubkey,
pub pubkey_auditor: ElGamalPubkey,
}
#[cfg(not(target_arch = "bpf"))]
@ -407,24 +419,26 @@ impl TransferPubkeys {
// TODO: use constructor instead
pub fn to_bytes(&self) -> [u8; 96] {
let mut bytes = [0u8; 96];
bytes[..32].copy_from_slice(&self.source.to_bytes());
bytes[32..64].copy_from_slice(&self.dest.to_bytes());
bytes[64..96].copy_from_slice(&self.auditor.to_bytes());
bytes[..32].copy_from_slice(&self.pubkey_source.to_bytes());
bytes[32..64].copy_from_slice(&self.pubkey_dest.to_bytes());
bytes[64..96].copy_from_slice(&self.pubkey_auditor.to_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
let bytes = array_ref![bytes, 0, 96];
let (source, dest, auditor) = array_refs![bytes, 32, 32, 32];
let (pubkey_source, pubkey_dest, pubkey_auditor) = array_refs![bytes, 32, 32, 32];
let source = ElGamalPubkey::from_bytes(source).ok_or(ProofError::Verification)?;
let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?;
let auditor = ElGamalPubkey::from_bytes(auditor).ok_or(ProofError::Verification)?;
let pubkey_source =
ElGamalPubkey::from_bytes(pubkey_source).ok_or(ProofError::Verification)?;
let pubkey_dest = ElGamalPubkey::from_bytes(pubkey_dest).ok_or(ProofError::Verification)?;
let pubkey_auditor =
ElGamalPubkey::from_bytes(pubkey_auditor).ok_or(ProofError::Verification)?;
Ok(Self {
source,
dest,
auditor,
pubkey_source,
pubkey_dest,
pubkey_auditor,
})
}
}

View File

@ -19,7 +19,7 @@ use {
},
range_proof::RangeProof,
sigma_proofs::{
equality_proof::EqualityProof,
equality_proof::CtxtCommEqualityProof,
fee_proof::FeeSigmaProof,
validity_proof::{AggregatedValidityProof, ValidityProof},
},
@ -33,11 +33,20 @@ use {
};
#[cfg(not(target_arch = "bpf"))]
const FEE_DENOMINATOR: u64 = 10000;
const MAX_FEE_BASIS_POINTS: u64 = 10000;
#[cfg(not(target_arch = "bpf"))]
const TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH: usize = 64;
#[cfg(not(target_arch = "bpf"))]
const TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH: usize = 32;
#[cfg(not(target_arch = "bpf"))]
const TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH: usize = 32;
#[cfg(not(target_arch = "bpf"))]
const TRANSFER_WITH_FEE_DELTA_BIT_LENGTH: usize = 64;
#[cfg(not(target_arch = "bpf"))]
lazy_static::lazy_static! {
pub static ref COMMITMENT_FEE_DENOMINATOR: PedersenCommitment = Pedersen::encode(FEE_DENOMINATOR);
pub static ref COMMITMENT_MAX_FEE_BASIS_POINTS: PedersenCommitment = Pedersen::encode(MAX_FEE_BASIS_POINTS);
}
// #[derive(Clone, Copy, Pod, Zeroable)]
@ -74,7 +83,7 @@ impl TransferWithFeeData {
keypair_source: &ElGamalKeypair,
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
fee_parameters: FeeParameters,
pubkey_fee_collector: &ElGamalPubkey,
pubkey_withdraw_withheld_authority: &ElGamalPubkey,
) -> Result<Self, ProofError> {
// split and encrypt transfer amount
let (amount_lo, amount_hi) = split_u64_into_u32(transfer_amount);
@ -99,12 +108,12 @@ impl TransferWithFeeData {
let transfer_amount_lo_source = ElGamalCiphertext {
commitment: ciphertext_lo.commitment,
handle: ciphertext_lo.source,
handle: ciphertext_lo.handle_source,
};
let transfer_amount_hi_source = ElGamalCiphertext {
commitment: ciphertext_hi.commitment,
handle: ciphertext_hi.source,
handle: ciphertext_hi.handle_source,
};
let ciphertext_new_source = ciphertext_old_source
@ -119,15 +128,18 @@ impl TransferWithFeeData {
u64::conditional_select(&fee_parameters.maximum_fee, &fee_amount, below_max);
// u64::conditional_select(&fee_amount, &fee_parameters.maximum_fee, below_max);
let (ciphertext_fee, opening_fee) =
FeeEncryption::new(fee_to_encrypt, pubkey_dest, pubkey_fee_collector);
let (ciphertext_fee, opening_fee) = FeeEncryption::new(
fee_to_encrypt,
pubkey_dest,
pubkey_withdraw_withheld_authority,
);
// generate transcript and append all public inputs
let pod_transfer_with_fee_pubkeys = pod::TransferWithFeePubkeys {
source: keypair_source.public.into(),
dest: (*pubkey_dest).into(),
auditor: (*pubkey_auditor).into(),
fee_collector: (*pubkey_fee_collector).into(),
pubkey_source: keypair_source.public.into(),
pubkey_dest: (*pubkey_dest).into(),
pubkey_auditor: (*pubkey_auditor).into(),
pubkey_withdraw_withheld_authority: (*pubkey_withdraw_withheld_authority).into(),
};
let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.to_pod();
let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.to_pod();
@ -150,7 +162,7 @@ impl TransferWithFeeData {
(new_spendable_balance, &ciphertext_new_source),
(fee_amount, &ciphertext_fee, &opening_fee),
delta_fee,
pubkey_fee_collector,
pubkey_withdraw_withheld_authority,
fee_parameters,
&mut transcript,
);
@ -171,9 +183,9 @@ impl TransferWithFeeData {
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
let handle_lo = match role {
Role::Source => ciphertext_lo.source,
Role::Dest => ciphertext_lo.dest,
Role::Auditor => ciphertext_lo.auditor,
Role::Source => ciphertext_lo.handle_source,
Role::Dest => ciphertext_lo.handle_dest,
Role::Auditor => ciphertext_lo.handle_auditor,
};
Ok(ElGamalCiphertext {
@ -187,9 +199,9 @@ impl TransferWithFeeData {
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
let handle_hi = match role {
Role::Source => ciphertext_hi.source,
Role::Dest => ciphertext_hi.dest,
Role::Auditor => ciphertext_hi.auditor,
Role::Source => ciphertext_hi.handle_source,
Role::Dest => ciphertext_hi.handle_dest,
Role::Auditor => ciphertext_hi.handle_auditor,
};
Ok(ElGamalCiphertext {
@ -256,7 +268,7 @@ impl Verifiable for TransferWithFeeData {
pub struct TransferWithFeeProof {
pub commitment_new_source: pod::PedersenCommitment,
pub commitment_claimed: pod::PedersenCommitment,
pub equality_proof: pod::EqualityProof,
pub equality_proof: pod::CtxtCommEqualityProof,
pub ciphertext_amount_validity_proof: pod::AggregatedValidityProof,
pub fee_sigma_proof: pod::FeeSigmaProof,
pub ciphertext_fee_validity_proof: pod::ValidityProof,
@ -275,29 +287,32 @@ impl TransferWithFeeProof {
) -> Transcript {
let mut transcript = Transcript::new(b"FeeProof");
transcript.append_pubkey(b"pubkey_source", &transfer_with_fee_pubkeys.source);
transcript.append_pubkey(b"pubkey_dest", &transfer_with_fee_pubkeys.dest);
transcript.append_pubkey(b"pubkey_auditor", &transfer_with_fee_pubkeys.auditor);
transcript.append_pubkey(b"pubkey-source", &transfer_with_fee_pubkeys.pubkey_source);
transcript.append_pubkey(b"pubkey-dest", &transfer_with_fee_pubkeys.pubkey_dest);
transcript.append_pubkey(b"pubkey-auditor", &transfer_with_fee_pubkeys.pubkey_auditor);
transcript.append_pubkey(
b"pubkey_fee_collector",
&transfer_with_fee_pubkeys.fee_collector,
b"pubkey_withdraw_withheld_authority",
&transfer_with_fee_pubkeys.pubkey_withdraw_withheld_authority,
);
transcript.append_commitment(b"comm-lo-amount", &ciphertext_lo.commitment);
transcript.append_handle(b"handle-lo-source", &ciphertext_lo.source);
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.dest);
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.auditor);
transcript.append_handle(b"handle-lo-source", &ciphertext_lo.handle_source);
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.handle_dest);
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.handle_auditor);
transcript.append_commitment(b"comm-hi-amount", &ciphertext_hi.commitment);
transcript.append_handle(b"handle-hi-source", &ciphertext_hi.source);
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.dest);
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.auditor);
transcript.append_handle(b"handle-hi-source", &ciphertext_hi.handle_source);
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.handle_dest);
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.handle_auditor);
transcript.append_ciphertext(b"ctxt-new-source", ciphertext_new_source);
transcript.append_commitment(b"comm-fee", &ciphertext_fee.commitment);
transcript.append_handle(b"handle-fee-dest", &ciphertext_fee.dest);
transcript.append_handle(b"handle-fee-auditor", &ciphertext_fee.fee_collector);
transcript.append_handle(b"fee-dest-handle", &ciphertext_fee.handle_dest);
transcript.append_handle(
b"handle-fee-auditor",
&ciphertext_fee.handle_withdraw_withheld_authority,
);
transcript
}
@ -313,7 +328,7 @@ impl TransferWithFeeProof {
(fee_amount, ciphertext_fee, opening_fee): (u64, &FeeEncryption, &PedersenOpening),
delta_fee: u64,
pubkey_fee_collector: &ElGamalPubkey,
pubkey_withdraw_withheld_authority: &ElGamalPubkey,
fee_parameters: FeeParameters,
transcript: &mut Transcript,
) -> Self {
@ -331,7 +346,7 @@ impl TransferWithFeeProof {
transcript.append_commitment(b"commitment-claimed", &pod_commitment_claimed);
// generate equality_proof
let equality_proof = EqualityProof::new(
let equality_proof = CtxtCommEqualityProof::new(
keypair_source,
ciphertext_new_source,
source_new_balance,
@ -363,7 +378,7 @@ impl TransferWithFeeProof {
);
let ciphertext_fee_validity_proof = ValidityProof::new(
(pubkey_dest, pubkey_fee_collector),
(pubkey_dest, pubkey_withdraw_withheld_authority),
fee_amount,
opening_fee,
transcript,
@ -376,11 +391,14 @@ impl TransferWithFeeProof {
transfer_amount_lo as u64,
transfer_amount_hi as u64,
delta_fee,
FEE_DENOMINATOR - delta_fee,
MAX_FEE_BASIS_POINTS - delta_fee,
],
vec![
64, 32, 32, 64, // double check
64,
TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH,
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH,
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
],
vec![
&opening_source,
@ -420,7 +438,7 @@ impl TransferWithFeeProof {
let commitment_new_source: PedersenCommitment = self.commitment_new_source.try_into()?;
let commitment_claimed: PedersenCommitment = self.commitment_claimed.try_into()?;
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
let ciphertext_amount_validity_proof: AggregatedValidityProof =
self.ciphertext_amount_validity_proof.try_into()?;
let fee_sigma_proof: FeeSigmaProof = self.fee_sigma_proof.try_into()?;
@ -430,7 +448,7 @@ impl TransferWithFeeProof {
// verify equality proof
equality_proof.verify(
&transfer_with_fee_pubkeys.source,
&transfer_with_fee_pubkeys.pubkey_source,
new_spendable_ciphertext,
&commitment_new_source,
transcript,
@ -439,12 +457,12 @@ impl TransferWithFeeProof {
// verify that the transfer amount is encrypted correctly
ciphertext_amount_validity_proof.verify(
(
&transfer_with_fee_pubkeys.dest,
&transfer_with_fee_pubkeys.auditor,
&transfer_with_fee_pubkeys.pubkey_dest,
&transfer_with_fee_pubkeys.pubkey_auditor,
),
(&ciphertext_lo.commitment, &ciphertext_hi.commitment),
(&ciphertext_lo.dest, &ciphertext_hi.dest),
(&ciphertext_lo.auditor, &ciphertext_hi.auditor),
(&ciphertext_lo.handle_dest, &ciphertext_hi.handle_dest),
(&ciphertext_lo.handle_auditor, &ciphertext_hi.handle_auditor),
transcript,
)?;
@ -467,14 +485,17 @@ impl TransferWithFeeProof {
ciphertext_fee_validity_proof.verify(
&ciphertext_fee.commitment,
(
&transfer_with_fee_pubkeys.dest,
&transfer_with_fee_pubkeys.fee_collector,
&transfer_with_fee_pubkeys.pubkey_dest,
&transfer_with_fee_pubkeys.pubkey_withdraw_withheld_authority,
),
(
&ciphertext_fee.handle_dest,
&ciphertext_fee.handle_withdraw_withheld_authority,
),
(&ciphertext_fee.dest, &ciphertext_fee.fee_collector),
transcript,
)?;
let commitment_claimed_negated = &(*COMMITMENT_FEE_DENOMINATOR) - &commitment_claimed;
let commitment_claimed_negated = &(*COMMITMENT_MAX_FEE_BASIS_POINTS) - &commitment_claimed;
range_proof.verify(
vec![
&commitment_new_source,
@ -496,38 +517,42 @@ impl TransferWithFeeProof {
#[repr(C)]
#[cfg(not(target_arch = "bpf"))]
pub struct TransferWithFeePubkeys {
pub source: ElGamalPubkey,
pub dest: ElGamalPubkey,
pub auditor: ElGamalPubkey,
pub fee_collector: ElGamalPubkey,
pub pubkey_source: ElGamalPubkey,
pub pubkey_dest: ElGamalPubkey,
pub pubkey_auditor: ElGamalPubkey,
pub pubkey_withdraw_withheld_authority: ElGamalPubkey,
}
#[cfg(not(target_arch = "bpf"))]
impl TransferWithFeePubkeys {
pub fn to_bytes(&self) -> [u8; 128] {
let mut bytes = [0u8; 128];
bytes[..32].copy_from_slice(&self.source.to_bytes());
bytes[32..64].copy_from_slice(&self.dest.to_bytes());
bytes[64..96].copy_from_slice(&self.auditor.to_bytes());
bytes[96..128].copy_from_slice(&self.fee_collector.to_bytes());
bytes[..32].copy_from_slice(&self.pubkey_source.to_bytes());
bytes[32..64].copy_from_slice(&self.pubkey_dest.to_bytes());
bytes[64..96].copy_from_slice(&self.pubkey_auditor.to_bytes());
bytes[96..128].copy_from_slice(&self.pubkey_withdraw_withheld_authority.to_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
let bytes = array_ref![bytes, 0, 128];
let (source, dest, auditor, fee_collector) = array_refs![bytes, 32, 32, 32, 32];
let (pubkey_source, pubkey_dest, pubkey_auditor, pubkey_withdraw_withheld_authority) =
array_refs![bytes, 32, 32, 32, 32];
let source = ElGamalPubkey::from_bytes(source).ok_or(ProofError::Verification)?;
let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?;
let auditor = ElGamalPubkey::from_bytes(auditor).ok_or(ProofError::Verification)?;
let fee_collector =
ElGamalPubkey::from_bytes(fee_collector).ok_or(ProofError::Verification)?;
let pubkey_source =
ElGamalPubkey::from_bytes(pubkey_source).ok_or(ProofError::Verification)?;
let pubkey_dest = ElGamalPubkey::from_bytes(pubkey_dest).ok_or(ProofError::Verification)?;
let pubkey_auditor =
ElGamalPubkey::from_bytes(pubkey_auditor).ok_or(ProofError::Verification)?;
let pubkey_withdraw_withheld_authority =
ElGamalPubkey::from_bytes(pubkey_withdraw_withheld_authority)
.ok_or(ProofError::Verification)?;
Ok(Self {
source,
dest,
auditor,
fee_collector,
pubkey_source,
pubkey_dest,
pubkey_auditor,
pubkey_withdraw_withheld_authority,
})
}
}
@ -537,8 +562,8 @@ impl TransferWithFeePubkeys {
#[cfg(not(target_arch = "bpf"))]
pub struct FeeEncryption {
pub commitment: PedersenCommitment,
pub dest: DecryptHandle,
pub fee_collector: DecryptHandle,
pub handle_dest: DecryptHandle,
pub handle_withdraw_withheld_authority: DecryptHandle,
}
#[cfg(not(target_arch = "bpf"))]
@ -546,13 +571,13 @@ impl FeeEncryption {
pub fn new(
amount: u64,
pubkey_dest: &ElGamalPubkey,
pubkey_fee_collector: &ElGamalPubkey,
pubkey_withdraw_withheld: &ElGamalPubkey,
) -> (Self, PedersenOpening) {
let (commitment, opening) = Pedersen::new(amount);
let fee_encryption = Self {
commitment,
dest: pubkey_dest.decrypt_handle(&opening),
fee_collector: pubkey_fee_collector.decrypt_handle(&opening),
handle_dest: pubkey_dest.decrypt_handle(&opening),
handle_withdraw_withheld_authority: pubkey_withdraw_withheld.decrypt_handle(&opening),
};
(fee_encryption, opening)
@ -561,8 +586,8 @@ impl FeeEncryption {
pub fn to_pod(&self) -> pod::FeeEncryption {
pod::FeeEncryption {
commitment: self.commitment.into(),
dest: self.dest.into(),
fee_collector: self.fee_collector.into(),
handle_dest: self.handle_dest.into(),
handle_withdraw_withheld_authority: self.handle_withdraw_withheld_authority.into(),
}
}
}
@ -601,8 +626,8 @@ impl FeeParameters {
fn calculate_fee(transfer_amount: u64, fee_rate_basis_points: u16) -> (u64, u64) {
let fee_scaled = (transfer_amount as u128) * (fee_rate_basis_points as u128);
let fee = (fee_scaled / FEE_DENOMINATOR as u128) as u64;
let rem = (fee_scaled % FEE_DENOMINATOR as u128) as u64;
let fee = (fee_scaled / MAX_FEE_BASIS_POINTS as u128) as u64;
let rem = (fee_scaled % MAX_FEE_BASIS_POINTS as u128) as u64;
if rem == 0 {
(fee, rem)
@ -620,10 +645,10 @@ fn compute_delta_commitment_and_opening(
) -> (PedersenCommitment, PedersenOpening) {
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
let commitment_delta = commitment_fee * Scalar::from(FEE_DENOMINATOR)
let commitment_delta = commitment_fee * Scalar::from(MAX_FEE_BASIS_POINTS)
- &(&combine_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar);
let opening_delta = opening_fee * Scalar::from(FEE_DENOMINATOR)
let opening_delta = opening_fee * Scalar::from(MAX_FEE_BASIS_POINTS)
- &(&combine_u32_openings(opening_lo, opening_hi) * &fee_rate_scalar);
(commitment_delta, opening_delta)
@ -638,7 +663,7 @@ fn compute_delta_commitment(
) -> PedersenCommitment {
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
commitment_fee * Scalar::from(FEE_DENOMINATOR)
commitment_fee * Scalar::from(MAX_FEE_BASIS_POINTS)
- &(&combine_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar)
}
@ -651,7 +676,7 @@ mod test {
let keypair_source = ElGamalKeypair::new_rand();
let pubkey_dest = ElGamalKeypair::new_rand().public;
let pubkey_auditor = ElGamalKeypair::new_rand().public;
let pubkey_fee_collector = ElGamalKeypair::new_rand().public;
let pubkey_withdraw_withheld_authority = ElGamalKeypair::new_rand().public;
let spendable_balance: u64 = 120;
let spendable_ciphertext = keypair_source.public.encrypt(spendable_balance);
@ -669,7 +694,7 @@ mod test {
&keypair_source,
(&pubkey_dest, &pubkey_auditor),
fee_parameters,
&pubkey_fee_collector,
&pubkey_withdraw_withheld_authority,
)
.unwrap();

View File

@ -12,13 +12,16 @@ use {
errors::ProofError,
instruction::Verifiable,
range_proof::RangeProof,
sigma_proofs::equality_proof::EqualityProof,
sigma_proofs::equality_proof::CtxtCommEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
#[cfg(not(target_arch = "bpf"))]
const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64;
/// This struct includes the cryptographic proof *and* the account data information needed to verify
/// the proof
///
@ -94,7 +97,7 @@ pub struct WithdrawProof {
pub commitment: pod::PedersenCommitment,
/// Associated equality proof
pub equality_proof: pod::EqualityProof,
pub equality_proof: pod::CtxtCommEqualityProof,
/// Associated range proof
pub range_proof: pod::RangeProof64, // 672 bytes
@ -128,7 +131,7 @@ impl WithdrawProof {
transcript.append_commitment(b"commitment", &pod_commitment);
// generate equality_proof
let equality_proof = EqualityProof::new(
let equality_proof = CtxtCommEqualityProof::new(
keypair,
final_ciphertext,
final_balance,
@ -139,7 +142,7 @@ impl WithdrawProof {
let range_proof =
RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript);
WithdrawProof {
Self {
commitment: pod_commitment,
equality_proof: equality_proof.try_into().expect("equality proof"),
range_proof: range_proof.try_into().expect("range proof"),
@ -155,7 +158,7 @@ impl WithdrawProof {
transcript.append_commitment(b"commitment", &self.commitment);
let commitment: PedersenCommitment = self.commitment.try_into()?;
let equality_proof: EqualityProof = self.equality_proof.try_into()?;
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
let range_proof: RangeProof = self.range_proof.try_into()?;
// verify equality proof
@ -166,7 +169,11 @@ impl WithdrawProof {
// verify range proof
//
// TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment`
range_proof.verify(vec![&commitment], vec![64_usize], transcript)?;
range_proof.verify(
vec![&commitment],
vec![WITHDRAW_AMOUNT_BIT_LENGTH],
transcript,
)?;
Ok(())
}

View File

@ -0,0 +1,214 @@
use {
crate::zk_token_elgamal::pod,
bytemuck::{Pod, Zeroable},
};
#[cfg(not(target_arch = "bpf"))]
use {
crate::{
encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::PedersenOpening,
},
errors::ProofError,
instruction::Verifiable,
sigma_proofs::equality_proof::CtxtCtxtEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
/// This struct includes the cryptographic proof *and* the account data information needed to verify
/// the proof
///
/// - The pre-instruction should call WithdrawData::verify_proof(&self)
/// - The actual program should check that `current_ct` is consistent with what is
/// currently stored in the confidential token account TODO: update this statement
///
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawWithheldTokensData {
pub pubkey_withdraw_withheld_authority: pod::ElGamalPubkey,
pub pubkey_dest: pod::ElGamalPubkey,
pub ciphertext_withdraw_withheld_authority: pod::ElGamalCiphertext,
pub ciphertext_dest: pod::ElGamalCiphertext,
pub proof: WithdrawWithheldTokensProof,
}
impl WithdrawWithheldTokensData {
#[cfg(not(target_arch = "bpf"))]
pub fn new(
keypair_withdraw_withheld_authority: &ElGamalKeypair,
pubkey_dest: &ElGamalPubkey,
ciphertext_withdraw_withheld_authority: &ElGamalCiphertext,
amount: u64,
) -> Result<Self, ProofError> {
let opening_dest = PedersenOpening::new_rand();
let ciphertext_dest = pubkey_dest.encrypt_with(amount, &opening_dest);
let pod_pubkey_withdraw_withheld_authority =
pod::ElGamalPubkey(keypair_withdraw_withheld_authority.public.to_bytes());
let pod_pubkey_dest = pod::ElGamalPubkey(pubkey_dest.to_bytes());
let pod_ciphertext_withdraw_withheld_authority =
pod::ElGamalCiphertext(ciphertext_withdraw_withheld_authority.to_bytes());
let pod_ciphertext_dest = pod::ElGamalCiphertext(ciphertext_dest.to_bytes());
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
&pod_pubkey_withdraw_withheld_authority,
&pod_pubkey_dest,
&pod_ciphertext_withdraw_withheld_authority,
&pod_ciphertext_dest,
);
let proof = WithdrawWithheldTokensProof::new(
keypair_withdraw_withheld_authority,
pubkey_dest,
ciphertext_withdraw_withheld_authority,
amount,
&opening_dest,
&mut transcript,
);
Ok(Self {
pubkey_withdraw_withheld_authority: pod_pubkey_withdraw_withheld_authority,
pubkey_dest: pod_pubkey_dest,
ciphertext_withdraw_withheld_authority: pod_ciphertext_withdraw_withheld_authority,
ciphertext_dest: pod_ciphertext_dest,
proof,
})
}
}
#[cfg(not(target_arch = "bpf"))]
impl Verifiable for WithdrawWithheldTokensData {
fn verify(&self) -> Result<(), ProofError> {
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
&self.pubkey_withdraw_withheld_authority,
&self.pubkey_dest,
&self.ciphertext_withdraw_withheld_authority,
&self.ciphertext_dest,
);
let pubkey_withdraw_withheld_authority =
self.pubkey_withdraw_withheld_authority.try_into()?;
let pubkey_dest = self.pubkey_dest.try_into()?;
let ciphertext_withdraw_withheld_authority =
self.ciphertext_withdraw_withheld_authority.try_into()?;
let ciphertext_dest = self.ciphertext_dest.try_into()?;
self.proof.verify(
&pubkey_withdraw_withheld_authority,
&pubkey_dest,
&ciphertext_withdraw_withheld_authority,
&ciphertext_dest,
&mut transcript,
)
}
}
/// This struct represents the cryptographic proof component that certifies the account's solvency
/// for withdrawal
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
#[allow(non_snake_case)]
pub struct WithdrawWithheldTokensProof {
pub proof: pod::CtxtCtxtEqualityProof,
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl WithdrawWithheldTokensProof {
fn transcript_new(
pubkey_withdraw_withheld_authority: &pod::ElGamalPubkey,
pubkey_dest: &pod::ElGamalPubkey,
ciphertext_withdraw_withheld_authority: &pod::ElGamalCiphertext,
ciphertext_dest: &pod::ElGamalCiphertext,
) -> Transcript {
let mut transcript = Transcript::new(b"WithdrawWithheldTokensProof");
transcript.append_pubkey(
b"withdraw-withheld-authority-pubkey",
pubkey_withdraw_withheld_authority,
);
transcript.append_pubkey(b"dest-pubkey", pubkey_dest);
transcript.append_ciphertext(
b"ciphertext-withdraw-withheld-authority",
ciphertext_withdraw_withheld_authority,
);
transcript.append_ciphertext(b"ciphertext-dest", ciphertext_dest);
transcript
}
pub fn new(
keypair_withdraw_withheld_authority: &ElGamalKeypair,
pubkey_dest: &ElGamalPubkey,
ciphertext_withdraw_withheld_authority: &ElGamalCiphertext,
amount: u64,
opening_dest: &PedersenOpening,
transcript: &mut Transcript,
) -> Self {
let equality_proof = CtxtCtxtEqualityProof::new(
keypair_withdraw_withheld_authority,
pubkey_dest,
ciphertext_withdraw_withheld_authority,
amount,
opening_dest,
transcript,
);
Self {
proof: equality_proof.into(),
}
}
pub fn verify(
&self,
pubkey_source: &ElGamalPubkey,
pubkey_dest: &ElGamalPubkey,
ciphertext_source: &ElGamalCiphertext,
ciphertext_dest: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), ProofError> {
let proof: CtxtCtxtEqualityProof = self.proof.try_into()?;
proof.verify(
pubkey_source,
pubkey_dest,
ciphertext_source,
ciphertext_dest,
transcript,
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_close_account_correctness() {
let keypair_withdraw_withheld_authority = ElGamalKeypair::new_rand();
let keypair_dest = ElGamalKeypair::new_rand();
let amount: u64 = 55;
let ciphertext_withdraw_withheld_authority =
keypair_withdraw_withheld_authority.public.encrypt(amount);
let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
&keypair_withdraw_withheld_authority,
&keypair_dest.public,
&ciphertext_withdraw_withheld_authority,
amount,
)
.unwrap();
assert!(withdraw_withheld_tokens_data.verify().is_ok());
}
}

View File

@ -5,6 +5,8 @@
//! commitment pair encrypts/encodes the same message. To generate the proof, a prover must provide
//! the decryption key for the ciphertext and the Pedersen opening for the commitment.
//!
//! TODO: verify with respect to ciphertext
//!
//! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect
//! zero-knowledge in the random oracle model.
@ -34,7 +36,7 @@ use {
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct EqualityProof {
pub struct CtxtCommEqualityProof {
Y_0: CompressedRistretto,
Y_1: CompressedRistretto,
Y_2: CompressedRistretto,
@ -45,7 +47,7 @@ pub struct EqualityProof {
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl EqualityProof {
impl CtxtCommEqualityProof {
/// Equality proof constructor.
///
/// The function does *not* hash the public key, ciphertext, or commitment into the transcript.
@ -63,8 +65,8 @@ impl EqualityProof {
/// * `opening` - The opening associated with the main Pedersen commitment to be proved
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn new(
elgamal_keypair: &ElGamalKeypair,
ciphertext: &ElGamalCiphertext,
keypair_source: &ElGamalKeypair,
ciphertext_source: &ElGamalCiphertext,
amount: u64,
opening: &PedersenOpening,
transcript: &mut Transcript,
@ -72,10 +74,10 @@ impl EqualityProof {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_EG = elgamal_keypair.public.get_point();
let D_EG = ciphertext.handle.get_point();
let P_source = keypair_source.public.get_point();
let D_source = ciphertext_source.handle.get_point();
let s = elgamal_keypair.secret.get_scalar();
let s = keypair_source.secret.get_scalar();
let x = Scalar::from(amount);
let r = opening.get_scalar();
@ -84,8 +86,9 @@ impl EqualityProof {
let mut y_x = Scalar::random(&mut OsRng);
let mut y_r = Scalar::random(&mut OsRng);
let Y_0 = (&y_s * P_EG).compress();
let Y_1 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_EG]).compress();
let Y_0 = (&y_s * P_source).compress();
let Y_1 =
RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_source]).compress();
let Y_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&(*G), &(*H)]).compress();
// record masking factors in the transcript
@ -106,7 +109,7 @@ impl EqualityProof {
y_x.zeroize();
y_r.zeroize();
EqualityProof {
CtxtCommEqualityProof {
Y_0,
Y_1,
Y_2,
@ -116,7 +119,7 @@ impl EqualityProof {
}
}
/// Equality proof verifier.
/// Equality proof verifier. TODO: wrt commitment
///
/// * `elgamal_pubkey` - The ElGamal pubkey associated with the ciphertext to be proved
/// * `ciphertext` - The main ElGamal ciphertext to be proved
@ -124,18 +127,18 @@ impl EqualityProof {
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn verify(
self,
elgamal_pubkey: &ElGamalPubkey,
ciphertext: &ElGamalCiphertext,
commitment: &PedersenCommitment,
pubkey_source: &ElGamalPubkey,
ciphertext_source: &ElGamalCiphertext,
commitment_dest: &PedersenCommitment,
transcript: &mut Transcript,
) -> Result<(), EqualityProofError> {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_EG = elgamal_pubkey.get_point();
let C_EG = ciphertext.commitment.get_point();
let D_EG = ciphertext.handle.get_point();
let C_Ped = commitment.get_point();
let P_source = pubkey_source.get_point();
let C_source = ciphertext_source.commitment.get_point();
let D_source = ciphertext_source.handle.get_point();
let C_dest = commitment_dest.get_point();
// include Y_0, Y_1, Y_2 to transcript and extract challenges
transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
@ -169,17 +172,17 @@ impl EqualityProof {
&ww_negated, // -ww
],
vec![
P_EG, // P_EG
&(*H), // H
&Y_0, // Y_0
&(*G), // G
D_EG, // D_EG
C_EG, // C_EG
&Y_1, // Y_1
&(*G), // G
&(*H), // H
C_Ped, // C_Ped
&Y_2, // Y_2
P_source, // P_source
&(*H), // H
&Y_0, // Y_0
&(*G), // G
D_source, // D_source
C_source, // C_source
&Y_1, // Y_1
&(*G), // G
&(*H), // H
C_dest, // C_dest
&Y_2, // Y_2
],
);
@ -213,7 +216,7 @@ impl EqualityProof {
let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(EqualityProofError::Format)?;
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(EqualityProofError::Format)?;
Ok(EqualityProof {
Ok(CtxtCommEqualityProof {
Y_0,
Y_1,
Y_2,
@ -224,71 +227,290 @@ impl EqualityProof {
}
}
/// Equality proof.
///
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct CtxtCtxtEqualityProof {
Y_0: CompressedRistretto,
Y_1: CompressedRistretto,
Y_2: CompressedRistretto,
Y_3: CompressedRistretto,
z_s: Scalar,
z_x: Scalar,
z_r: Scalar,
}
#[allow(non_snake_case)]
#[cfg(not(target_arch = "bpf"))]
impl CtxtCtxtEqualityProof {
/// Equality proof constructor.
///
/// The function does *not* hash the public key, ciphertext, or commitment into the transcript.
/// For security, the caller (the main protocol) should hash these public components prior to
/// invoking this constructor.
///
/// This function is randomized. It uses `OsRng` internally to generate random scalars.
///
/// Note that the proof constructor does not take the actual Pedersen commitment as input; it
/// takes the associated Pedersen opening instead.
///
/// * `elgamal_keypair` - The ElGamal keypair associated with the ciphertext to be proved
/// * `ciphertext` - The main ElGamal ciphertext to be proved
/// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment
/// * `opening` - The opening associated with the main Pedersen commitment to be proved
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn new(
keypair_source: &ElGamalKeypair,
pubkey_dest: &ElGamalPubkey,
ciphertext_source: &ElGamalCiphertext,
amount: u64,
opening_dest: &PedersenOpening,
transcript: &mut Transcript,
) -> Self {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_source = keypair_source.public.get_point();
let D_source = ciphertext_source.handle.get_point();
let P_dest = pubkey_dest.get_point();
let s = keypair_source.secret.get_scalar();
let x = Scalar::from(amount);
let r = opening_dest.get_scalar();
// generate random masking factors that also serves as nonces
let mut y_s = Scalar::random(&mut OsRng);
let mut y_x = Scalar::random(&mut OsRng);
let mut y_r = Scalar::random(&mut OsRng);
let Y_0 = (&y_s * P_source).compress();
let Y_1 =
RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_source]).compress();
let Y_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&(*G), &(*H)]).compress();
let Y_3 = (&y_r * P_dest).compress();
// record masking factors in the transcript
transcript.append_point(b"Y_0", &Y_0);
transcript.append_point(b"Y_1", &Y_1);
transcript.append_point(b"Y_2", &Y_2);
transcript.append_point(b"Y_3", &Y_3);
let c = transcript.challenge_scalar(b"c");
transcript.challenge_scalar(b"w");
// compute the masked values
let z_s = &(&c * s) + &y_s;
let z_x = &(&c * &x) + &y_x;
let z_r = &(&c * r) + &y_r;
// zeroize random scalars
y_s.zeroize();
y_x.zeroize();
y_r.zeroize();
CtxtCtxtEqualityProof {
Y_0,
Y_1,
Y_2,
Y_3,
z_s,
z_x,
z_r,
}
}
/// Equality proof verifier. TODO: wrt commitment
///
/// * `elgamal_pubkey` - The ElGamal pubkey associated with the ciphertext to be proved
/// * `ciphertext` - The main ElGamal ciphertext to be proved
/// * `commitment` - The main Pedersen commitment to be proved
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn verify(
self,
pubkey_source: &ElGamalPubkey,
pubkey_dest: &ElGamalPubkey,
ciphertext_source: &ElGamalCiphertext,
ciphertext_dest: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), EqualityProofError> {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_source = pubkey_source.get_point();
let C_source = ciphertext_source.commitment.get_point();
let D_source = ciphertext_source.handle.get_point();
let P_dest = pubkey_dest.get_point();
let C_dest = ciphertext_dest.commitment.get_point();
let D_dest = ciphertext_dest.handle.get_point();
// include Y_0, Y_1, Y_2 to transcript and extract challenges
transcript.validate_and_append_point(b"Y_0", &self.Y_0)?;
transcript.validate_and_append_point(b"Y_1", &self.Y_1)?;
transcript.validate_and_append_point(b"Y_2", &self.Y_2)?;
transcript.validate_and_append_point(b"Y_3", &self.Y_3)?;
let c = transcript.challenge_scalar(b"c");
let w = transcript.challenge_scalar(b"w"); // w used for batch verification
let ww = &w * &w;
let www = &w * &ww;
let w_negated = -&w;
let ww_negated = -&ww;
let www_negated = -&www;
// check that the required algebraic condition holds
let Y_0 = self.Y_0.decompress().ok_or(EqualityProofError::Format)?;
let Y_1 = self.Y_1.decompress().ok_or(EqualityProofError::Format)?;
let Y_2 = self.Y_2.decompress().ok_or(EqualityProofError::Format)?;
let Y_3 = self.Y_3.decompress().ok_or(EqualityProofError::Format)?;
let check = RistrettoPoint::vartime_multiscalar_mul(
vec![
&self.z_s, // z_s
&(-&c), // -c
&(-&Scalar::one()), // -identity
&(&w * &self.z_x), // w * z_x
&(&w * &self.z_s), // w * z_s
&(&w_negated * &c), // -w * c
&w_negated, // -w
&(&ww * &self.z_x), // ww * z_x
&(&ww * &self.z_r), // ww * z_r
&(&ww_negated * &c), // -ww * c
&ww_negated, // -ww
&(&www * &self.z_r), // z_r
&(&www_negated * &c), // -www * c
&www_negated,
],
vec![
P_source, // P_source
&(*H), // H
&Y_0, // Y_0
&(*G), // G
D_source, // D_source
C_source, // C_source
&Y_1, // Y_1
&(*G), // G
&(*H), // H
C_dest, // C_dest
&Y_2, // Y_2
P_dest, // P_dest
D_dest, // D_dest
&Y_3, // Y_3
],
);
if check.is_identity() {
Ok(())
} else {
Err(EqualityProofError::AlgebraicRelation)
}
}
pub fn to_bytes(&self) -> [u8; 224] {
let mut buf = [0_u8; 224];
buf[..32].copy_from_slice(self.Y_0.as_bytes());
buf[32..64].copy_from_slice(self.Y_1.as_bytes());
buf[64..96].copy_from_slice(self.Y_2.as_bytes());
buf[96..128].copy_from_slice(self.Y_3.as_bytes());
buf[128..160].copy_from_slice(self.z_s.as_bytes());
buf[160..192].copy_from_slice(self.z_x.as_bytes());
buf[192..224].copy_from_slice(self.z_r.as_bytes());
buf
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EqualityProofError> {
let bytes = array_ref![bytes, 0, 224];
let (Y_0, Y_1, Y_2, Y_3, z_s, z_x, z_r) = array_refs![bytes, 32, 32, 32, 32, 32, 32, 32];
let Y_0 = CompressedRistretto::from_slice(Y_0);
let Y_1 = CompressedRistretto::from_slice(Y_1);
let Y_2 = CompressedRistretto::from_slice(Y_2);
let Y_3 = CompressedRistretto::from_slice(Y_3);
let z_s = Scalar::from_canonical_bytes(*z_s).ok_or(EqualityProofError::Format)?;
let z_x = Scalar::from_canonical_bytes(*z_x).ok_or(EqualityProofError::Format)?;
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(EqualityProofError::Format)?;
Ok(CtxtCtxtEqualityProof {
Y_0,
Y_1,
Y_2,
Y_3,
z_s,
z_x,
z_r,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::encryption::{elgamal::ElGamalSecretKey, pedersen::Pedersen};
#[test]
fn test_equality_proof_correctness() {
fn test_ciphertext_commitment_equality_proof_correctness() {
// success case
let elgamal_keypair = ElGamalKeypair::new_rand();
let keypair_source = ElGamalKeypair::new_rand();
let message: u64 = 55;
let ciphertext = elgamal_keypair.public.encrypt(message);
let (commitment, opening) = Pedersen::new(message);
let ciphertext_source = keypair_source.public.encrypt(message);
let (commitment_dest, opening_dest) = Pedersen::new(message);
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = EqualityProof::new(
&elgamal_keypair,
&ciphertext,
let proof = CtxtCommEqualityProof::new(
&keypair_source,
&ciphertext_source,
message,
&opening,
&opening_dest,
&mut transcript_prover,
);
assert!(proof
.verify(
&elgamal_keypair.public,
&ciphertext,
&commitment,
&keypair_source.public,
&ciphertext_source,
&commitment_dest,
&mut transcript_verifier
)
.is_ok());
// fail case: encrypted and committed messages are different
let elgamal_keypair = ElGamalKeypair::new_rand();
let keypair_source = ElGamalKeypair::new_rand();
let encrypted_message: u64 = 55;
let committed_message: u64 = 77;
let ciphertext = elgamal_keypair.public.encrypt(encrypted_message);
let (commitment, opening) = Pedersen::new(committed_message);
let ciphertext_source = keypair_source.public.encrypt(encrypted_message);
let (commitment_dest, opening_dest) = Pedersen::new(committed_message);
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = EqualityProof::new(
&elgamal_keypair,
&ciphertext,
let proof = CtxtCommEqualityProof::new(
&keypair_source,
&ciphertext_source,
message,
&opening,
&opening_dest,
&mut transcript_prover,
);
assert!(proof
.verify(
&elgamal_keypair.public,
&ciphertext,
&commitment,
&keypair_source.public,
&ciphertext_source,
&commitment_dest,
&mut transcript_verifier
)
.is_err());
}
#[test]
fn test_equality_proof_edge_cases() {
fn test_ciphertext_commitment_equality_proof_edge_cases() {
// if ElGamal public key zero (public key is invalid), then the proof should always reject
let public = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap();
let secret = ElGamalSecretKey::new_rand();
@ -302,7 +524,7 @@ mod test {
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = EqualityProof::new(
let proof = CtxtCommEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
@ -331,7 +553,7 @@ mod test {
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = EqualityProof::new(
let proof = CtxtCommEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
@ -360,7 +582,7 @@ mod test {
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = EqualityProof::new(
let proof = CtxtCommEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
@ -388,7 +610,7 @@ mod test {
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = EqualityProof::new(
let proof = CtxtCommEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
@ -405,4 +627,72 @@ mod test {
)
.is_ok());
}
#[test]
fn test_ciphertext_ciphertext_equality_proof_correctness() {
// success case
let keypair_source = ElGamalKeypair::new_rand();
let keypair_dest = ElGamalKeypair::new_rand();
let message: u64 = 55;
let ciphertext_source = keypair_source.public.encrypt(message);
let opening_dest = PedersenOpening::new_rand();
let ciphertext_dest = keypair_dest.public.encrypt_with(message, &opening_dest);
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = CtxtCtxtEqualityProof::new(
&keypair_source,
&keypair_dest.public,
&ciphertext_source,
message,
&opening_dest,
&mut transcript_prover,
);
assert!(proof
.verify(
&keypair_source.public,
&keypair_dest.public,
&ciphertext_source,
&ciphertext_dest,
&mut transcript_verifier
)
.is_ok());
// fail case: encrypted and committed messages are different
let message_source: u64 = 55;
let message_dest: u64 = 77;
let ciphertext_source = keypair_source.public.encrypt(message_source);
let opening_dest = PedersenOpening::new_rand();
let ciphertext_dest = keypair_dest
.public
.encrypt_with(message_dest, &opening_dest);
let mut transcript_prover = Transcript::new(b"Test");
let mut transcript_verifier = Transcript::new(b"Test");
let proof = CtxtCtxtEqualityProof::new(
&keypair_source,
&keypair_dest.public,
&ciphertext_source,
message,
&opening_dest,
&mut transcript_prover,
);
assert!(proof
.verify(
&keypair_source.public,
&keypair_dest.public,
&ciphertext_source,
&ciphertext_dest,
&mut transcript_verifier
)
.is_err());
}
}

View File

@ -3,8 +3,8 @@
//!
//! The module contains implementations of the following proof systems that work on Pedersen
//! commitments and twisted ElGamal ciphertexts:
//! - Equality proof: can be used to certify that a twisted ElGamal ciphertext and a Pedersen
//! commitment encrypt/encode the same message.
//! - Equality proof: can be used to certify that a twisted ElGamal ciphertext encrypts the same
//! message as either a Pedersen commitment or another ElGamal ciphertext.
//! - Validity proof: can be used to certify that a twisted ElGamal ciphertext is a properly-formed
//! ciphertext with respect to a pair of ElGamal public keys.
//! - Zero-balance proof: can be used to certify that a twisted ElGamal ciphertext encrypts the

View File

@ -27,7 +27,7 @@ mod target_arch {
},
range_proof::{errors::RangeProofError, RangeProof},
sigma_proofs::{
equality_proof::EqualityProof,
equality_proof::{CtxtCommEqualityProof, CtxtCtxtEqualityProof},
errors::*,
fee_proof::FeeSigmaProof,
validity_proof::{AggregatedValidityProof, ValidityProof},
@ -151,16 +151,30 @@ mod target_arch {
}
}
impl From<EqualityProof> for pod::EqualityProof {
fn from(proof: EqualityProof) -> Self {
impl From<CtxtCommEqualityProof> for pod::CtxtCommEqualityProof {
fn from(proof: CtxtCommEqualityProof) -> Self {
Self(proof.to_bytes())
}
}
impl TryFrom<pod::EqualityProof> for EqualityProof {
impl TryFrom<pod::CtxtCommEqualityProof> for CtxtCommEqualityProof {
type Error = EqualityProofError;
fn try_from(pod: pod::EqualityProof) -> Result<Self, Self::Error> {
fn try_from(pod: pod::CtxtCommEqualityProof) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
impl From<CtxtCtxtEqualityProof> for pod::CtxtCtxtEqualityProof {
fn from(proof: CtxtCtxtEqualityProof) -> Self {
Self(proof.to_bytes())
}
}
impl TryFrom<pod::CtxtCtxtEqualityProof> for CtxtCtxtEqualityProof {
type Error = EqualityProofError;
fn try_from(pod: pod::CtxtCtxtEqualityProof) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
@ -313,9 +327,9 @@ mod target_arch {
impl From<TransferPubkeys> for pod::TransferPubkeys {
fn from(keys: TransferPubkeys) -> Self {
Self {
source: keys.source.into(),
dest: keys.dest.into(),
auditor: keys.auditor.into(),
pubkey_source: keys.pubkey_source.into(),
pubkey_dest: keys.pubkey_dest.into(),
pubkey_auditor: keys.pubkey_auditor.into(),
}
}
}
@ -325,9 +339,9 @@ mod target_arch {
fn try_from(pod: pod::TransferPubkeys) -> Result<Self, Self::Error> {
Ok(Self {
source: pod.source.try_into()?,
dest: pod.dest.try_into()?,
auditor: pod.auditor.try_into()?,
pubkey_source: pod.pubkey_source.try_into()?,
pubkey_dest: pod.pubkey_dest.try_into()?,
pubkey_auditor: pod.pubkey_auditor.try_into()?,
})
}
}
@ -335,10 +349,10 @@ mod target_arch {
impl From<TransferWithFeePubkeys> for pod::TransferWithFeePubkeys {
fn from(keys: TransferWithFeePubkeys) -> Self {
Self {
source: keys.source.into(),
dest: keys.dest.into(),
auditor: keys.auditor.into(),
fee_collector: keys.fee_collector.into(),
pubkey_source: keys.pubkey_source.into(),
pubkey_dest: keys.pubkey_dest.into(),
pubkey_auditor: keys.pubkey_auditor.into(),
pubkey_withdraw_withheld_authority: keys.pubkey_withdraw_withheld_authority.into(),
}
}
}
@ -348,10 +362,12 @@ mod target_arch {
fn try_from(pod: pod::TransferWithFeePubkeys) -> Result<Self, Self::Error> {
Ok(Self {
source: pod.source.try_into()?,
dest: pod.dest.try_into()?,
auditor: pod.auditor.try_into()?,
fee_collector: pod.fee_collector.try_into()?,
pubkey_source: pod.pubkey_source.try_into()?,
pubkey_dest: pod.pubkey_dest.try_into()?,
pubkey_auditor: pod.pubkey_auditor.try_into()?,
pubkey_withdraw_withheld_authority: pod
.pubkey_withdraw_withheld_authority
.try_into()?,
})
}
}
@ -360,9 +376,9 @@ mod target_arch {
fn from(ciphertext: TransferAmountEncryption) -> Self {
Self {
commitment: ciphertext.commitment.into(),
source: ciphertext.source.into(),
dest: ciphertext.dest.into(),
auditor: ciphertext.auditor.into(),
handle_source: ciphertext.handle_source.into(),
handle_dest: ciphertext.handle_dest.into(),
handle_auditor: ciphertext.handle_auditor.into(),
}
}
}
@ -373,9 +389,9 @@ mod target_arch {
fn try_from(pod: pod::TransferAmountEncryption) -> Result<Self, Self::Error> {
Ok(Self {
commitment: pod.commitment.try_into()?,
source: pod.source.try_into()?,
dest: pod.dest.try_into()?,
auditor: pod.auditor.try_into()?,
handle_source: pod.handle_source.try_into()?,
handle_dest: pod.handle_dest.try_into()?,
handle_auditor: pod.handle_auditor.try_into()?,
})
}
}
@ -384,8 +400,10 @@ mod target_arch {
fn from(ciphertext: FeeEncryption) -> Self {
Self {
commitment: ciphertext.commitment.into(),
dest: ciphertext.dest.into(),
fee_collector: ciphertext.fee_collector.into(),
handle_dest: ciphertext.handle_dest.into(),
handle_withdraw_withheld_authority: ciphertext
.handle_withdraw_withheld_authority
.into(),
}
}
}
@ -396,8 +414,10 @@ mod target_arch {
fn try_from(pod: pod::FeeEncryption) -> Result<Self, Self::Error> {
Ok(Self {
commitment: pod.commitment.try_into()?,
dest: pod.dest.try_into()?,
fee_collector: pod.fee_collector.try_into()?,
handle_dest: pod.handle_dest.try_into()?,
handle_withdraw_withheld_authority: pod
.handle_withdraw_withheld_authority
.try_into()?,
})
}
}

View File

@ -83,15 +83,25 @@ impl fmt::Debug for DecryptHandle {
}
}
/// Serialization of equality proofs
/// Serialization of `CtxtCommEqualityProof`
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct EqualityProof(pub [u8; 192]);
pub struct CtxtCommEqualityProof(pub [u8; 192]);
// `EqualityProof` is a Pod and Zeroable.
// `CtxtCommEqualityProof` is a Pod and Zeroable.
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
unsafe impl Zeroable for EqualityProof {}
unsafe impl Pod for EqualityProof {}
unsafe impl Zeroable for CtxtCommEqualityProof {}
unsafe impl Pod for CtxtCommEqualityProof {}
/// Serialization of `CtxtCtxtEqualityProof`
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct CtxtCtxtEqualityProof(pub [u8; 224]);
// `CtxtCtxtEqualityProof` is a Pod and Zeroable.
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
unsafe impl Zeroable for CtxtCtxtEqualityProof {}
unsafe impl Pod for CtxtCtxtEqualityProof {}
/// Serialization of validity proofs
#[derive(Clone, Copy)]
@ -184,40 +194,35 @@ impl Default for AeCiphertext {
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferPubkeys {
pub source: ElGamalPubkey,
pub dest: ElGamalPubkey,
pub auditor: ElGamalPubkey,
pub pubkey_source: ElGamalPubkey,
pub pubkey_dest: ElGamalPubkey,
pub pubkey_auditor: ElGamalPubkey,
}
// pub struct TransferPubkeys(pub [u8; 96]);
// unsafe impl Zeroable for TransferPubkeys {}
// unsafe impl Pod for TransferPubkeys {}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferWithFeePubkeys {
pub source: ElGamalPubkey,
pub dest: ElGamalPubkey,
pub auditor: ElGamalPubkey,
pub fee_collector: ElGamalPubkey,
pub pubkey_source: ElGamalPubkey,
pub pubkey_dest: ElGamalPubkey,
pub pubkey_auditor: ElGamalPubkey,
pub pubkey_withdraw_withheld_authority: ElGamalPubkey,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferAmountEncryption {
pub commitment: PedersenCommitment,
pub source: DecryptHandle,
pub dest: DecryptHandle,
pub auditor: DecryptHandle,
pub handle_source: DecryptHandle,
pub handle_dest: DecryptHandle,
pub handle_auditor: DecryptHandle,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct FeeEncryption {
pub commitment: PedersenCommitment,
pub dest: DecryptHandle,
pub fee_collector: DecryptHandle,
pub handle_dest: DecryptHandle,
pub handle_withdraw_withheld_authority: DecryptHandle,
}
#[derive(Clone, Copy, Pod, Zeroable)]

View File

@ -30,6 +30,16 @@ pub enum ProofInstruction {
///
VerifyWithdraw,
/// Verify a `WithdrawWithheldTokensData` struct
///
/// Accounts expected by this instruction:
/// None
///
/// Data expected by this instruction:
/// `WithdrawWithheldTokensData`
///
VerifyWithdrawWithheldTokens,
/// Verify a `TransferData` struct
///
/// Accounts expected by this instruction:
@ -83,6 +93,10 @@ pub fn verify_withdraw(proof_data: &WithdrawData) -> Instruction {
ProofInstruction::VerifyWithdraw.encode(proof_data)
}
pub fn verify_withdraw_withheld_tokens(proof_data: &WithdrawWithheldTokensData) -> Instruction {
ProofInstruction::VerifyWithdrawWithheldTokens.encode(proof_data)
}
pub fn verify_transfer(proof_data: &TransferData) -> Instruction {
ProofInstruction::VerifyTransfer.encode(proof_data)
}