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:
parent
1a68f81f89
commit
b4100a9b5d
|
@ -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)
|
||||
|
|
|
@ -98,7 +98,7 @@ impl CloseAccountProof {
|
|||
) -> Self {
|
||||
let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
|
||||
|
||||
CloseAccountProof {
|
||||
Self {
|
||||
proof: proof.into(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue