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");
|
ic_msg!(invoke_context, "VerifyWithdraw");
|
||||||
verify::<WithdrawData>(input, invoke_context)
|
verify::<WithdrawData>(input, invoke_context)
|
||||||
}
|
}
|
||||||
|
ProofInstruction::VerifyWithdrawWithheldTokens => {
|
||||||
|
ic_msg!(invoke_context, "VerifyWithdraw");
|
||||||
|
verify::<WithdrawData>(input, invoke_context)
|
||||||
|
}
|
||||||
ProofInstruction::VerifyTransfer => {
|
ProofInstruction::VerifyTransfer => {
|
||||||
ic_msg!(invoke_context, "VerifyTransfer");
|
ic_msg!(invoke_context, "VerifyTransfer");
|
||||||
verify::<TransferData>(input, invoke_context)
|
verify::<TransferData>(input, invoke_context)
|
||||||
|
|
|
@ -98,7 +98,7 @@ impl CloseAccountProof {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
|
let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
|
||||||
|
|
||||||
CloseAccountProof {
|
Self {
|
||||||
proof: proof.into(),
|
proof: proof.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub mod close_account;
|
||||||
pub mod transfer;
|
pub mod transfer;
|
||||||
pub mod transfer_with_fee;
|
pub mod transfer_with_fee;
|
||||||
pub mod withdraw;
|
pub mod withdraw;
|
||||||
|
pub mod withdraw_withheld;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
use {
|
use {
|
||||||
|
@ -17,6 +18,7 @@ use {
|
||||||
pub use {
|
pub use {
|
||||||
close_account::CloseAccountData, transfer::TransferData,
|
close_account::CloseAccountData, transfer::TransferData,
|
||||||
transfer_with_fee::TransferWithFeeData, withdraw::WithdrawData,
|
transfer_with_fee::TransferWithFeeData, withdraw::WithdrawData,
|
||||||
|
withdraw_withheld::WithdrawWithheldTokensData,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Constant for 2^32
|
/// Constant for 2^32
|
||||||
|
|
|
@ -15,7 +15,9 @@ use {
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
instruction::{combine_u32_ciphertexts, split_u64_into_u32, Role, Verifiable, TWO_32},
|
instruction::{combine_u32_ciphertexts, split_u64_into_u32, Role, Verifiable, TWO_32},
|
||||||
range_proof::RangeProof,
|
range_proof::RangeProof,
|
||||||
sigma_proofs::{equality_proof::EqualityProof, validity_proof::AggregatedValidityProof},
|
sigma_proofs::{
|
||||||
|
equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof,
|
||||||
|
},
|
||||||
transcript::TranscriptProtocol,
|
transcript::TranscriptProtocol,
|
||||||
},
|
},
|
||||||
arrayref::{array_ref, array_refs},
|
arrayref::{array_ref, array_refs},
|
||||||
|
@ -23,14 +25,21 @@ use {
|
||||||
std::convert::TryInto,
|
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)]
|
#[derive(Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub struct TransferAmountEncryption {
|
pub struct TransferAmountEncryption {
|
||||||
pub commitment: PedersenCommitment,
|
pub commitment: PedersenCommitment,
|
||||||
pub source: DecryptHandle,
|
pub handle_source: DecryptHandle,
|
||||||
pub dest: DecryptHandle,
|
pub handle_dest: DecryptHandle,
|
||||||
pub auditor: DecryptHandle,
|
pub handle_auditor: DecryptHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
@ -44,9 +53,9 @@ impl TransferAmountEncryption {
|
||||||
let (commitment, opening) = Pedersen::new(amount);
|
let (commitment, opening) = Pedersen::new(amount);
|
||||||
let transfer_amount_encryption = Self {
|
let transfer_amount_encryption = Self {
|
||||||
commitment,
|
commitment,
|
||||||
source: pubkey_source.decrypt_handle(&opening),
|
handle_source: pubkey_source.decrypt_handle(&opening),
|
||||||
dest: pubkey_dest.decrypt_handle(&opening),
|
handle_dest: pubkey_dest.decrypt_handle(&opening),
|
||||||
auditor: pubkey_auditor.decrypt_handle(&opening),
|
handle_auditor: pubkey_auditor.decrypt_handle(&opening),
|
||||||
};
|
};
|
||||||
|
|
||||||
(transfer_amount_encryption, opening)
|
(transfer_amount_encryption, opening)
|
||||||
|
@ -55,9 +64,9 @@ impl TransferAmountEncryption {
|
||||||
pub fn to_pod(&self) -> pod::TransferAmountEncryption {
|
pub fn to_pod(&self) -> pod::TransferAmountEncryption {
|
||||||
pod::TransferAmountEncryption {
|
pod::TransferAmountEncryption {
|
||||||
commitment: self.commitment.into(),
|
commitment: self.commitment.into(),
|
||||||
source: self.source.into(),
|
handle_source: self.handle_source.into(),
|
||||||
dest: self.dest.into(),
|
handle_dest: self.handle_dest.into(),
|
||||||
auditor: self.auditor.into(),
|
handle_auditor: self.handle_auditor.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,12 +122,12 @@ impl TransferData {
|
||||||
|
|
||||||
let transfer_amount_lo_source = ElGamalCiphertext {
|
let transfer_amount_lo_source = ElGamalCiphertext {
|
||||||
commitment: ciphertext_lo.commitment,
|
commitment: ciphertext_lo.commitment,
|
||||||
handle: ciphertext_lo.source,
|
handle: ciphertext_lo.handle_source,
|
||||||
};
|
};
|
||||||
|
|
||||||
let transfer_amount_hi_source = ElGamalCiphertext {
|
let transfer_amount_hi_source = ElGamalCiphertext {
|
||||||
commitment: ciphertext_hi.commitment,
|
commitment: ciphertext_hi.commitment,
|
||||||
handle: ciphertext_hi.source,
|
handle: ciphertext_hi.handle_source,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ciphertext_new_source = ciphertext_old_source
|
let ciphertext_new_source = ciphertext_old_source
|
||||||
|
@ -126,9 +135,9 @@ impl TransferData {
|
||||||
|
|
||||||
// generate transcript and append all public inputs
|
// generate transcript and append all public inputs
|
||||||
let pod_transfer_pubkeys = pod::TransferPubkeys {
|
let pod_transfer_pubkeys = pod::TransferPubkeys {
|
||||||
source: keypair_source.public.into(),
|
pubkey_source: keypair_source.public.into(),
|
||||||
dest: (*pubkey_dest).into(),
|
pubkey_dest: (*pubkey_dest).into(),
|
||||||
auditor: (*pubkey_auditor).into(),
|
pubkey_auditor: (*pubkey_auditor).into(),
|
||||||
};
|
};
|
||||||
let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.into();
|
let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.into();
|
||||||
let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.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 ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
|
||||||
|
|
||||||
let handle_lo = match role {
|
let handle_lo = match role {
|
||||||
Role::Source => ciphertext_lo.source,
|
Role::Source => ciphertext_lo.handle_source,
|
||||||
Role::Dest => ciphertext_lo.dest,
|
Role::Dest => ciphertext_lo.handle_dest,
|
||||||
Role::Auditor => ciphertext_lo.auditor,
|
Role::Auditor => ciphertext_lo.handle_auditor,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ElGamalCiphertext {
|
Ok(ElGamalCiphertext {
|
||||||
|
@ -181,9 +190,9 @@ impl TransferData {
|
||||||
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
|
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
|
||||||
|
|
||||||
let handle_hi = match role {
|
let handle_hi = match role {
|
||||||
Role::Source => ciphertext_hi.source,
|
Role::Source => ciphertext_hi.handle_source,
|
||||||
Role::Dest => ciphertext_hi.dest,
|
Role::Dest => ciphertext_hi.handle_dest,
|
||||||
Role::Auditor => ciphertext_hi.auditor,
|
Role::Auditor => ciphertext_hi.handle_auditor,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ElGamalCiphertext {
|
Ok(ElGamalCiphertext {
|
||||||
|
@ -247,7 +256,7 @@ pub struct TransferProof {
|
||||||
pub commitment_new_source: pod::PedersenCommitment,
|
pub commitment_new_source: pod::PedersenCommitment,
|
||||||
|
|
||||||
/// Associated equality proof
|
/// Associated equality proof
|
||||||
pub equality_proof: pod::EqualityProof,
|
pub equality_proof: pod::CtxtCommEqualityProof,
|
||||||
|
|
||||||
/// Associated ciphertext validity proof
|
/// Associated ciphertext validity proof
|
||||||
pub validity_proof: pod::AggregatedValidityProof,
|
pub validity_proof: pod::AggregatedValidityProof,
|
||||||
|
@ -267,19 +276,19 @@ impl TransferProof {
|
||||||
) -> Transcript {
|
) -> Transcript {
|
||||||
let mut transcript = Transcript::new(b"transfer-proof");
|
let mut transcript = Transcript::new(b"transfer-proof");
|
||||||
|
|
||||||
transcript.append_pubkey(b"pubkey_source", &transfer_pubkeys.source);
|
transcript.append_pubkey(b"pubkey-source", &transfer_pubkeys.pubkey_source);
|
||||||
transcript.append_pubkey(b"pubkey_dest", &transfer_pubkeys.dest);
|
transcript.append_pubkey(b"pubkey-dest", &transfer_pubkeys.pubkey_dest);
|
||||||
transcript.append_pubkey(b"pubkey_auditor", &transfer_pubkeys.auditor);
|
transcript.append_pubkey(b"pubkey-auditor", &transfer_pubkeys.pubkey_auditor);
|
||||||
|
|
||||||
transcript.append_commitment(b"comm-lo-amount", &ciphertext_lo.commitment);
|
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-source", &ciphertext_lo.handle_source);
|
||||||
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.dest);
|
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.handle_dest);
|
||||||
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.auditor);
|
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.handle_auditor);
|
||||||
|
|
||||||
transcript.append_commitment(b"comm-hi-amount", &ciphertext_hi.commitment);
|
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-source", &ciphertext_hi.handle_source);
|
||||||
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.dest);
|
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.handle_dest);
|
||||||
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.auditor);
|
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.handle_auditor);
|
||||||
|
|
||||||
transcript.append_ciphertext(b"ciphertext-new-source", ciphertext_new_source);
|
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);
|
transcript.append_commitment(b"commitment-new-source", &pod_commitment_new_source);
|
||||||
|
|
||||||
// generate equality_proof
|
// generate equality_proof
|
||||||
let equality_proof = EqualityProof::new(
|
let equality_proof = CtxtCommEqualityProof::new(
|
||||||
keypair_source,
|
keypair_source,
|
||||||
ciphertext_new_source,
|
ciphertext_new_source,
|
||||||
source_new_balance,
|
source_new_balance,
|
||||||
|
@ -325,7 +334,11 @@ impl TransferProof {
|
||||||
transfer_amount_lo as u64,
|
transfer_amount_lo as u64,
|
||||||
transfer_amount_hi 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],
|
vec![&opening_source, opening_lo, opening_hi],
|
||||||
transcript,
|
transcript,
|
||||||
);
|
);
|
||||||
|
@ -343,13 +356,13 @@ impl TransferProof {
|
||||||
ciphertext_lo: &TransferAmountEncryption,
|
ciphertext_lo: &TransferAmountEncryption,
|
||||||
ciphertext_hi: &TransferAmountEncryption,
|
ciphertext_hi: &TransferAmountEncryption,
|
||||||
transfer_pubkeys: &TransferPubkeys,
|
transfer_pubkeys: &TransferPubkeys,
|
||||||
new_spendable_ciphertext: &ElGamalCiphertext,
|
ciphertext_new_spendable: &ElGamalCiphertext,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), ProofError> {
|
) -> Result<(), ProofError> {
|
||||||
transcript.append_commitment(b"commitment-new-source", &self.commitment_new_source);
|
transcript.append_commitment(b"commitment-new-source", &self.commitment_new_source);
|
||||||
|
|
||||||
let commitment: PedersenCommitment = self.commitment_new_source.try_into()?;
|
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 aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
|
||||||
let range_proof: RangeProof = self.range_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
|
// TODO: we can also consider verifying equality and range proof in a batch
|
||||||
equality_proof.verify(
|
equality_proof.verify(
|
||||||
&transfer_pubkeys.source,
|
&transfer_pubkeys.pubkey_source,
|
||||||
new_spendable_ciphertext,
|
ciphertext_new_spendable,
|
||||||
&commitment,
|
&commitment,
|
||||||
transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!("equality pass");
|
|
||||||
|
|
||||||
// verify validity proof
|
// verify validity proof
|
||||||
aggregated_validity_proof.verify(
|
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.commitment, &ciphertext_hi.commitment),
|
||||||
(&ciphertext_lo.dest, &ciphertext_hi.dest),
|
(&ciphertext_lo.handle_dest, &ciphertext_hi.handle_dest),
|
||||||
(&ciphertext_lo.auditor, &ciphertext_hi.auditor),
|
(&ciphertext_lo.handle_auditor, &ciphertext_hi.handle_auditor),
|
||||||
transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!("validity pass");
|
|
||||||
|
|
||||||
// verify range proof
|
// verify range proof
|
||||||
let commitment_new_source = self.commitment_new_source.try_into()?;
|
let commitment_new_source = self.commitment_new_source.try_into()?;
|
||||||
range_proof.verify(
|
range_proof.verify(
|
||||||
|
@ -397,9 +409,9 @@ impl TransferProof {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub struct TransferPubkeys {
|
pub struct TransferPubkeys {
|
||||||
pub source: ElGamalPubkey,
|
pub pubkey_source: ElGamalPubkey,
|
||||||
pub dest: ElGamalPubkey,
|
pub pubkey_dest: ElGamalPubkey,
|
||||||
pub auditor: ElGamalPubkey,
|
pub pubkey_auditor: ElGamalPubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
@ -407,24 +419,26 @@ impl TransferPubkeys {
|
||||||
// TODO: use constructor instead
|
// TODO: use constructor instead
|
||||||
pub fn to_bytes(&self) -> [u8; 96] {
|
pub fn to_bytes(&self) -> [u8; 96] {
|
||||||
let mut bytes = [0u8; 96];
|
let mut bytes = [0u8; 96];
|
||||||
bytes[..32].copy_from_slice(&self.source.to_bytes());
|
bytes[..32].copy_from_slice(&self.pubkey_source.to_bytes());
|
||||||
bytes[32..64].copy_from_slice(&self.dest.to_bytes());
|
bytes[32..64].copy_from_slice(&self.pubkey_dest.to_bytes());
|
||||||
bytes[64..96].copy_from_slice(&self.auditor.to_bytes());
|
bytes[64..96].copy_from_slice(&self.pubkey_auditor.to_bytes());
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||||
let bytes = array_ref![bytes, 0, 96];
|
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 pubkey_source =
|
||||||
let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?;
|
ElGamalPubkey::from_bytes(pubkey_source).ok_or(ProofError::Verification)?;
|
||||||
let auditor = ElGamalPubkey::from_bytes(auditor).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 {
|
Ok(Self {
|
||||||
source,
|
pubkey_source,
|
||||||
dest,
|
pubkey_dest,
|
||||||
auditor,
|
pubkey_auditor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use {
|
||||||
},
|
},
|
||||||
range_proof::RangeProof,
|
range_proof::RangeProof,
|
||||||
sigma_proofs::{
|
sigma_proofs::{
|
||||||
equality_proof::EqualityProof,
|
equality_proof::CtxtCommEqualityProof,
|
||||||
fee_proof::FeeSigmaProof,
|
fee_proof::FeeSigmaProof,
|
||||||
validity_proof::{AggregatedValidityProof, ValidityProof},
|
validity_proof::{AggregatedValidityProof, ValidityProof},
|
||||||
},
|
},
|
||||||
|
@ -33,11 +33,20 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[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"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
lazy_static::lazy_static! {
|
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)]
|
// #[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
@ -74,7 +83,7 @@ impl TransferWithFeeData {
|
||||||
keypair_source: &ElGamalKeypair,
|
keypair_source: &ElGamalKeypair,
|
||||||
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
|
(pubkey_dest, pubkey_auditor): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
fee_parameters: FeeParameters,
|
fee_parameters: FeeParameters,
|
||||||
pubkey_fee_collector: &ElGamalPubkey,
|
pubkey_withdraw_withheld_authority: &ElGamalPubkey,
|
||||||
) -> Result<Self, ProofError> {
|
) -> Result<Self, ProofError> {
|
||||||
// split and encrypt transfer amount
|
// split and encrypt transfer amount
|
||||||
let (amount_lo, amount_hi) = split_u64_into_u32(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 {
|
let transfer_amount_lo_source = ElGamalCiphertext {
|
||||||
commitment: ciphertext_lo.commitment,
|
commitment: ciphertext_lo.commitment,
|
||||||
handle: ciphertext_lo.source,
|
handle: ciphertext_lo.handle_source,
|
||||||
};
|
};
|
||||||
|
|
||||||
let transfer_amount_hi_source = ElGamalCiphertext {
|
let transfer_amount_hi_source = ElGamalCiphertext {
|
||||||
commitment: ciphertext_hi.commitment,
|
commitment: ciphertext_hi.commitment,
|
||||||
handle: ciphertext_hi.source,
|
handle: ciphertext_hi.handle_source,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ciphertext_new_source = ciphertext_old_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_parameters.maximum_fee, &fee_amount, below_max);
|
||||||
// u64::conditional_select(&fee_amount, &fee_parameters.maximum_fee, below_max);
|
// u64::conditional_select(&fee_amount, &fee_parameters.maximum_fee, below_max);
|
||||||
|
|
||||||
let (ciphertext_fee, opening_fee) =
|
let (ciphertext_fee, opening_fee) = FeeEncryption::new(
|
||||||
FeeEncryption::new(fee_to_encrypt, pubkey_dest, pubkey_fee_collector);
|
fee_to_encrypt,
|
||||||
|
pubkey_dest,
|
||||||
|
pubkey_withdraw_withheld_authority,
|
||||||
|
);
|
||||||
|
|
||||||
// generate transcript and append all public inputs
|
// generate transcript and append all public inputs
|
||||||
let pod_transfer_with_fee_pubkeys = pod::TransferWithFeePubkeys {
|
let pod_transfer_with_fee_pubkeys = pod::TransferWithFeePubkeys {
|
||||||
source: keypair_source.public.into(),
|
pubkey_source: keypair_source.public.into(),
|
||||||
dest: (*pubkey_dest).into(),
|
pubkey_dest: (*pubkey_dest).into(),
|
||||||
auditor: (*pubkey_auditor).into(),
|
pubkey_auditor: (*pubkey_auditor).into(),
|
||||||
fee_collector: (*pubkey_fee_collector).into(),
|
pubkey_withdraw_withheld_authority: (*pubkey_withdraw_withheld_authority).into(),
|
||||||
};
|
};
|
||||||
let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.to_pod();
|
let pod_ciphertext_lo: pod::TransferAmountEncryption = ciphertext_lo.to_pod();
|
||||||
let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.to_pod();
|
let pod_ciphertext_hi: pod::TransferAmountEncryption = ciphertext_hi.to_pod();
|
||||||
|
@ -150,7 +162,7 @@ impl TransferWithFeeData {
|
||||||
(new_spendable_balance, &ciphertext_new_source),
|
(new_spendable_balance, &ciphertext_new_source),
|
||||||
(fee_amount, &ciphertext_fee, &opening_fee),
|
(fee_amount, &ciphertext_fee, &opening_fee),
|
||||||
delta_fee,
|
delta_fee,
|
||||||
pubkey_fee_collector,
|
pubkey_withdraw_withheld_authority,
|
||||||
fee_parameters,
|
fee_parameters,
|
||||||
&mut transcript,
|
&mut transcript,
|
||||||
);
|
);
|
||||||
|
@ -171,9 +183,9 @@ impl TransferWithFeeData {
|
||||||
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
|
let ciphertext_lo: TransferAmountEncryption = self.ciphertext_lo.try_into()?;
|
||||||
|
|
||||||
let handle_lo = match role {
|
let handle_lo = match role {
|
||||||
Role::Source => ciphertext_lo.source,
|
Role::Source => ciphertext_lo.handle_source,
|
||||||
Role::Dest => ciphertext_lo.dest,
|
Role::Dest => ciphertext_lo.handle_dest,
|
||||||
Role::Auditor => ciphertext_lo.auditor,
|
Role::Auditor => ciphertext_lo.handle_auditor,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ElGamalCiphertext {
|
Ok(ElGamalCiphertext {
|
||||||
|
@ -187,9 +199,9 @@ impl TransferWithFeeData {
|
||||||
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
|
let ciphertext_hi: TransferAmountEncryption = self.ciphertext_hi.try_into()?;
|
||||||
|
|
||||||
let handle_hi = match role {
|
let handle_hi = match role {
|
||||||
Role::Source => ciphertext_hi.source,
|
Role::Source => ciphertext_hi.handle_source,
|
||||||
Role::Dest => ciphertext_hi.dest,
|
Role::Dest => ciphertext_hi.handle_dest,
|
||||||
Role::Auditor => ciphertext_hi.auditor,
|
Role::Auditor => ciphertext_hi.handle_auditor,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ElGamalCiphertext {
|
Ok(ElGamalCiphertext {
|
||||||
|
@ -256,7 +268,7 @@ impl Verifiable for TransferWithFeeData {
|
||||||
pub struct TransferWithFeeProof {
|
pub struct TransferWithFeeProof {
|
||||||
pub commitment_new_source: pod::PedersenCommitment,
|
pub commitment_new_source: pod::PedersenCommitment,
|
||||||
pub commitment_claimed: 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 ciphertext_amount_validity_proof: pod::AggregatedValidityProof,
|
||||||
pub fee_sigma_proof: pod::FeeSigmaProof,
|
pub fee_sigma_proof: pod::FeeSigmaProof,
|
||||||
pub ciphertext_fee_validity_proof: pod::ValidityProof,
|
pub ciphertext_fee_validity_proof: pod::ValidityProof,
|
||||||
|
@ -275,29 +287,32 @@ impl TransferWithFeeProof {
|
||||||
) -> Transcript {
|
) -> Transcript {
|
||||||
let mut transcript = Transcript::new(b"FeeProof");
|
let mut transcript = Transcript::new(b"FeeProof");
|
||||||
|
|
||||||
transcript.append_pubkey(b"pubkey_source", &transfer_with_fee_pubkeys.source);
|
transcript.append_pubkey(b"pubkey-source", &transfer_with_fee_pubkeys.pubkey_source);
|
||||||
transcript.append_pubkey(b"pubkey_dest", &transfer_with_fee_pubkeys.dest);
|
transcript.append_pubkey(b"pubkey-dest", &transfer_with_fee_pubkeys.pubkey_dest);
|
||||||
transcript.append_pubkey(b"pubkey_auditor", &transfer_with_fee_pubkeys.auditor);
|
transcript.append_pubkey(b"pubkey-auditor", &transfer_with_fee_pubkeys.pubkey_auditor);
|
||||||
transcript.append_pubkey(
|
transcript.append_pubkey(
|
||||||
b"pubkey_fee_collector",
|
b"pubkey_withdraw_withheld_authority",
|
||||||
&transfer_with_fee_pubkeys.fee_collector,
|
&transfer_with_fee_pubkeys.pubkey_withdraw_withheld_authority,
|
||||||
);
|
);
|
||||||
|
|
||||||
transcript.append_commitment(b"comm-lo-amount", &ciphertext_lo.commitment);
|
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-source", &ciphertext_lo.handle_source);
|
||||||
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.dest);
|
transcript.append_handle(b"handle-lo-dest", &ciphertext_lo.handle_dest);
|
||||||
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.auditor);
|
transcript.append_handle(b"handle-lo-auditor", &ciphertext_lo.handle_auditor);
|
||||||
|
|
||||||
transcript.append_commitment(b"comm-hi-amount", &ciphertext_hi.commitment);
|
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-source", &ciphertext_hi.handle_source);
|
||||||
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.dest);
|
transcript.append_handle(b"handle-hi-dest", &ciphertext_hi.handle_dest);
|
||||||
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.auditor);
|
transcript.append_handle(b"handle-hi-auditor", &ciphertext_hi.handle_auditor);
|
||||||
|
|
||||||
transcript.append_ciphertext(b"ctxt-new-source", ciphertext_new_source);
|
transcript.append_ciphertext(b"ctxt-new-source", ciphertext_new_source);
|
||||||
|
|
||||||
transcript.append_commitment(b"comm-fee", &ciphertext_fee.commitment);
|
transcript.append_commitment(b"comm-fee", &ciphertext_fee.commitment);
|
||||||
transcript.append_handle(b"handle-fee-dest", &ciphertext_fee.dest);
|
transcript.append_handle(b"fee-dest-handle", &ciphertext_fee.handle_dest);
|
||||||
transcript.append_handle(b"handle-fee-auditor", &ciphertext_fee.fee_collector);
|
transcript.append_handle(
|
||||||
|
b"handle-fee-auditor",
|
||||||
|
&ciphertext_fee.handle_withdraw_withheld_authority,
|
||||||
|
);
|
||||||
|
|
||||||
transcript
|
transcript
|
||||||
}
|
}
|
||||||
|
@ -313,7 +328,7 @@ impl TransferWithFeeProof {
|
||||||
|
|
||||||
(fee_amount, ciphertext_fee, opening_fee): (u64, &FeeEncryption, &PedersenOpening),
|
(fee_amount, ciphertext_fee, opening_fee): (u64, &FeeEncryption, &PedersenOpening),
|
||||||
delta_fee: u64,
|
delta_fee: u64,
|
||||||
pubkey_fee_collector: &ElGamalPubkey,
|
pubkey_withdraw_withheld_authority: &ElGamalPubkey,
|
||||||
fee_parameters: FeeParameters,
|
fee_parameters: FeeParameters,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -331,7 +346,7 @@ impl TransferWithFeeProof {
|
||||||
transcript.append_commitment(b"commitment-claimed", &pod_commitment_claimed);
|
transcript.append_commitment(b"commitment-claimed", &pod_commitment_claimed);
|
||||||
|
|
||||||
// generate equality_proof
|
// generate equality_proof
|
||||||
let equality_proof = EqualityProof::new(
|
let equality_proof = CtxtCommEqualityProof::new(
|
||||||
keypair_source,
|
keypair_source,
|
||||||
ciphertext_new_source,
|
ciphertext_new_source,
|
||||||
source_new_balance,
|
source_new_balance,
|
||||||
|
@ -363,7 +378,7 @@ impl TransferWithFeeProof {
|
||||||
);
|
);
|
||||||
|
|
||||||
let ciphertext_fee_validity_proof = ValidityProof::new(
|
let ciphertext_fee_validity_proof = ValidityProof::new(
|
||||||
(pubkey_dest, pubkey_fee_collector),
|
(pubkey_dest, pubkey_withdraw_withheld_authority),
|
||||||
fee_amount,
|
fee_amount,
|
||||||
opening_fee,
|
opening_fee,
|
||||||
transcript,
|
transcript,
|
||||||
|
@ -376,11 +391,14 @@ impl TransferWithFeeProof {
|
||||||
transfer_amount_lo as u64,
|
transfer_amount_lo as u64,
|
||||||
transfer_amount_hi as u64,
|
transfer_amount_hi as u64,
|
||||||
delta_fee,
|
delta_fee,
|
||||||
FEE_DENOMINATOR - delta_fee,
|
MAX_FEE_BASIS_POINTS - delta_fee,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
64, 32, 32, 64, // double check
|
TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH,
|
||||||
64,
|
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![
|
vec![
|
||||||
&opening_source,
|
&opening_source,
|
||||||
|
@ -420,7 +438,7 @@ impl TransferWithFeeProof {
|
||||||
let commitment_new_source: PedersenCommitment = self.commitment_new_source.try_into()?;
|
let commitment_new_source: PedersenCommitment = self.commitment_new_source.try_into()?;
|
||||||
let commitment_claimed: PedersenCommitment = self.commitment_claimed.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 =
|
let ciphertext_amount_validity_proof: AggregatedValidityProof =
|
||||||
self.ciphertext_amount_validity_proof.try_into()?;
|
self.ciphertext_amount_validity_proof.try_into()?;
|
||||||
let fee_sigma_proof: FeeSigmaProof = self.fee_sigma_proof.try_into()?;
|
let fee_sigma_proof: FeeSigmaProof = self.fee_sigma_proof.try_into()?;
|
||||||
|
@ -430,7 +448,7 @@ impl TransferWithFeeProof {
|
||||||
|
|
||||||
// verify equality proof
|
// verify equality proof
|
||||||
equality_proof.verify(
|
equality_proof.verify(
|
||||||
&transfer_with_fee_pubkeys.source,
|
&transfer_with_fee_pubkeys.pubkey_source,
|
||||||
new_spendable_ciphertext,
|
new_spendable_ciphertext,
|
||||||
&commitment_new_source,
|
&commitment_new_source,
|
||||||
transcript,
|
transcript,
|
||||||
|
@ -439,12 +457,12 @@ impl TransferWithFeeProof {
|
||||||
// verify that the transfer amount is encrypted correctly
|
// verify that the transfer amount is encrypted correctly
|
||||||
ciphertext_amount_validity_proof.verify(
|
ciphertext_amount_validity_proof.verify(
|
||||||
(
|
(
|
||||||
&transfer_with_fee_pubkeys.dest,
|
&transfer_with_fee_pubkeys.pubkey_dest,
|
||||||
&transfer_with_fee_pubkeys.auditor,
|
&transfer_with_fee_pubkeys.pubkey_auditor,
|
||||||
),
|
),
|
||||||
(&ciphertext_lo.commitment, &ciphertext_hi.commitment),
|
(&ciphertext_lo.commitment, &ciphertext_hi.commitment),
|
||||||
(&ciphertext_lo.dest, &ciphertext_hi.dest),
|
(&ciphertext_lo.handle_dest, &ciphertext_hi.handle_dest),
|
||||||
(&ciphertext_lo.auditor, &ciphertext_hi.auditor),
|
(&ciphertext_lo.handle_auditor, &ciphertext_hi.handle_auditor),
|
||||||
transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -467,14 +485,17 @@ impl TransferWithFeeProof {
|
||||||
ciphertext_fee_validity_proof.verify(
|
ciphertext_fee_validity_proof.verify(
|
||||||
&ciphertext_fee.commitment,
|
&ciphertext_fee.commitment,
|
||||||
(
|
(
|
||||||
&transfer_with_fee_pubkeys.dest,
|
&transfer_with_fee_pubkeys.pubkey_dest,
|
||||||
&transfer_with_fee_pubkeys.fee_collector,
|
&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,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let commitment_claimed_negated = &(*COMMITMENT_FEE_DENOMINATOR) - &commitment_claimed;
|
let commitment_claimed_negated = &(*COMMITMENT_MAX_FEE_BASIS_POINTS) - &commitment_claimed;
|
||||||
range_proof.verify(
|
range_proof.verify(
|
||||||
vec![
|
vec![
|
||||||
&commitment_new_source,
|
&commitment_new_source,
|
||||||
|
@ -496,38 +517,42 @@ impl TransferWithFeeProof {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub struct TransferWithFeePubkeys {
|
pub struct TransferWithFeePubkeys {
|
||||||
pub source: ElGamalPubkey,
|
pub pubkey_source: ElGamalPubkey,
|
||||||
pub dest: ElGamalPubkey,
|
pub pubkey_dest: ElGamalPubkey,
|
||||||
pub auditor: ElGamalPubkey,
|
pub pubkey_auditor: ElGamalPubkey,
|
||||||
pub fee_collector: ElGamalPubkey,
|
pub pubkey_withdraw_withheld_authority: ElGamalPubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl TransferWithFeePubkeys {
|
impl TransferWithFeePubkeys {
|
||||||
pub fn to_bytes(&self) -> [u8; 128] {
|
pub fn to_bytes(&self) -> [u8; 128] {
|
||||||
let mut bytes = [0u8; 128];
|
let mut bytes = [0u8; 128];
|
||||||
bytes[..32].copy_from_slice(&self.source.to_bytes());
|
bytes[..32].copy_from_slice(&self.pubkey_source.to_bytes());
|
||||||
bytes[32..64].copy_from_slice(&self.dest.to_bytes());
|
bytes[32..64].copy_from_slice(&self.pubkey_dest.to_bytes());
|
||||||
bytes[64..96].copy_from_slice(&self.auditor.to_bytes());
|
bytes[64..96].copy_from_slice(&self.pubkey_auditor.to_bytes());
|
||||||
bytes[96..128].copy_from_slice(&self.fee_collector.to_bytes());
|
bytes[96..128].copy_from_slice(&self.pubkey_withdraw_withheld_authority.to_bytes());
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ProofError> {
|
||||||
let bytes = array_ref![bytes, 0, 128];
|
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 pubkey_source =
|
||||||
let dest = ElGamalPubkey::from_bytes(dest).ok_or(ProofError::Verification)?;
|
ElGamalPubkey::from_bytes(pubkey_source).ok_or(ProofError::Verification)?;
|
||||||
let auditor = ElGamalPubkey::from_bytes(auditor).ok_or(ProofError::Verification)?;
|
let pubkey_dest = ElGamalPubkey::from_bytes(pubkey_dest).ok_or(ProofError::Verification)?;
|
||||||
let fee_collector =
|
let pubkey_auditor =
|
||||||
ElGamalPubkey::from_bytes(fee_collector).ok_or(ProofError::Verification)?;
|
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 {
|
Ok(Self {
|
||||||
source,
|
pubkey_source,
|
||||||
dest,
|
pubkey_dest,
|
||||||
auditor,
|
pubkey_auditor,
|
||||||
fee_collector,
|
pubkey_withdraw_withheld_authority,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -537,8 +562,8 @@ impl TransferWithFeePubkeys {
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub struct FeeEncryption {
|
pub struct FeeEncryption {
|
||||||
pub commitment: PedersenCommitment,
|
pub commitment: PedersenCommitment,
|
||||||
pub dest: DecryptHandle,
|
pub handle_dest: DecryptHandle,
|
||||||
pub fee_collector: DecryptHandle,
|
pub handle_withdraw_withheld_authority: DecryptHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
@ -546,13 +571,13 @@ impl FeeEncryption {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
amount: u64,
|
amount: u64,
|
||||||
pubkey_dest: &ElGamalPubkey,
|
pubkey_dest: &ElGamalPubkey,
|
||||||
pubkey_fee_collector: &ElGamalPubkey,
|
pubkey_withdraw_withheld: &ElGamalPubkey,
|
||||||
) -> (Self, PedersenOpening) {
|
) -> (Self, PedersenOpening) {
|
||||||
let (commitment, opening) = Pedersen::new(amount);
|
let (commitment, opening) = Pedersen::new(amount);
|
||||||
let fee_encryption = Self {
|
let fee_encryption = Self {
|
||||||
commitment,
|
commitment,
|
||||||
dest: pubkey_dest.decrypt_handle(&opening),
|
handle_dest: pubkey_dest.decrypt_handle(&opening),
|
||||||
fee_collector: pubkey_fee_collector.decrypt_handle(&opening),
|
handle_withdraw_withheld_authority: pubkey_withdraw_withheld.decrypt_handle(&opening),
|
||||||
};
|
};
|
||||||
|
|
||||||
(fee_encryption, opening)
|
(fee_encryption, opening)
|
||||||
|
@ -561,8 +586,8 @@ impl FeeEncryption {
|
||||||
pub fn to_pod(&self) -> pod::FeeEncryption {
|
pub fn to_pod(&self) -> pod::FeeEncryption {
|
||||||
pod::FeeEncryption {
|
pod::FeeEncryption {
|
||||||
commitment: self.commitment.into(),
|
commitment: self.commitment.into(),
|
||||||
dest: self.dest.into(),
|
handle_dest: self.handle_dest.into(),
|
||||||
fee_collector: self.fee_collector.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) {
|
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_scaled = (transfer_amount as u128) * (fee_rate_basis_points as u128);
|
||||||
|
|
||||||
let fee = (fee_scaled / FEE_DENOMINATOR as u128) as u64;
|
let fee = (fee_scaled / MAX_FEE_BASIS_POINTS as u128) as u64;
|
||||||
let rem = (fee_scaled % FEE_DENOMINATOR as u128) as u64;
|
let rem = (fee_scaled % MAX_FEE_BASIS_POINTS as u128) as u64;
|
||||||
|
|
||||||
if rem == 0 {
|
if rem == 0 {
|
||||||
(fee, rem)
|
(fee, rem)
|
||||||
|
@ -620,10 +645,10 @@ fn compute_delta_commitment_and_opening(
|
||||||
) -> (PedersenCommitment, PedersenOpening) {
|
) -> (PedersenCommitment, PedersenOpening) {
|
||||||
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
|
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);
|
- &(&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);
|
- &(&combine_u32_openings(opening_lo, opening_hi) * &fee_rate_scalar);
|
||||||
|
|
||||||
(commitment_delta, opening_delta)
|
(commitment_delta, opening_delta)
|
||||||
|
@ -638,7 +663,7 @@ fn compute_delta_commitment(
|
||||||
) -> PedersenCommitment {
|
) -> PedersenCommitment {
|
||||||
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
|
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)
|
- &(&combine_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,7 +676,7 @@ mod test {
|
||||||
let keypair_source = ElGamalKeypair::new_rand();
|
let keypair_source = ElGamalKeypair::new_rand();
|
||||||
let pubkey_dest = ElGamalKeypair::new_rand().public;
|
let pubkey_dest = ElGamalKeypair::new_rand().public;
|
||||||
let pubkey_auditor = 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_balance: u64 = 120;
|
||||||
let spendable_ciphertext = keypair_source.public.encrypt(spendable_balance);
|
let spendable_ciphertext = keypair_source.public.encrypt(spendable_balance);
|
||||||
|
@ -669,7 +694,7 @@ mod test {
|
||||||
&keypair_source,
|
&keypair_source,
|
||||||
(&pubkey_dest, &pubkey_auditor),
|
(&pubkey_dest, &pubkey_auditor),
|
||||||
fee_parameters,
|
fee_parameters,
|
||||||
&pubkey_fee_collector,
|
&pubkey_withdraw_withheld_authority,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,16 @@ use {
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
instruction::Verifiable,
|
instruction::Verifiable,
|
||||||
range_proof::RangeProof,
|
range_proof::RangeProof,
|
||||||
sigma_proofs::equality_proof::EqualityProof,
|
sigma_proofs::equality_proof::CtxtCommEqualityProof,
|
||||||
transcript::TranscriptProtocol,
|
transcript::TranscriptProtocol,
|
||||||
},
|
},
|
||||||
merlin::Transcript,
|
merlin::Transcript,
|
||||||
std::convert::TryInto,
|
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
|
/// This struct includes the cryptographic proof *and* the account data information needed to verify
|
||||||
/// the proof
|
/// the proof
|
||||||
///
|
///
|
||||||
|
@ -94,7 +97,7 @@ pub struct WithdrawProof {
|
||||||
pub commitment: pod::PedersenCommitment,
|
pub commitment: pod::PedersenCommitment,
|
||||||
|
|
||||||
/// Associated equality proof
|
/// Associated equality proof
|
||||||
pub equality_proof: pod::EqualityProof,
|
pub equality_proof: pod::CtxtCommEqualityProof,
|
||||||
|
|
||||||
/// Associated range proof
|
/// Associated range proof
|
||||||
pub range_proof: pod::RangeProof64, // 672 bytes
|
pub range_proof: pod::RangeProof64, // 672 bytes
|
||||||
|
@ -128,7 +131,7 @@ impl WithdrawProof {
|
||||||
transcript.append_commitment(b"commitment", &pod_commitment);
|
transcript.append_commitment(b"commitment", &pod_commitment);
|
||||||
|
|
||||||
// generate equality_proof
|
// generate equality_proof
|
||||||
let equality_proof = EqualityProof::new(
|
let equality_proof = CtxtCommEqualityProof::new(
|
||||||
keypair,
|
keypair,
|
||||||
final_ciphertext,
|
final_ciphertext,
|
||||||
final_balance,
|
final_balance,
|
||||||
|
@ -139,7 +142,7 @@ impl WithdrawProof {
|
||||||
let range_proof =
|
let range_proof =
|
||||||
RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript);
|
RangeProof::new(vec![final_balance], vec![64], vec![&opening], transcript);
|
||||||
|
|
||||||
WithdrawProof {
|
Self {
|
||||||
commitment: pod_commitment,
|
commitment: pod_commitment,
|
||||||
equality_proof: equality_proof.try_into().expect("equality proof"),
|
equality_proof: equality_proof.try_into().expect("equality proof"),
|
||||||
range_proof: range_proof.try_into().expect("range proof"),
|
range_proof: range_proof.try_into().expect("range proof"),
|
||||||
|
@ -155,7 +158,7 @@ impl WithdrawProof {
|
||||||
transcript.append_commitment(b"commitment", &self.commitment);
|
transcript.append_commitment(b"commitment", &self.commitment);
|
||||||
|
|
||||||
let commitment: PedersenCommitment = self.commitment.try_into()?;
|
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()?;
|
let range_proof: RangeProof = self.range_proof.try_into()?;
|
||||||
|
|
||||||
// verify equality proof
|
// verify equality proof
|
||||||
|
@ -166,7 +169,11 @@ impl WithdrawProof {
|
||||||
// verify range proof
|
// verify range proof
|
||||||
//
|
//
|
||||||
// TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment`
|
// 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(())
|
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
|
//! 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.
|
//! 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
|
//! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect
|
||||||
//! zero-knowledge in the random oracle model.
|
//! 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.
|
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EqualityProof {
|
pub struct CtxtCommEqualityProof {
|
||||||
Y_0: CompressedRistretto,
|
Y_0: CompressedRistretto,
|
||||||
Y_1: CompressedRistretto,
|
Y_1: CompressedRistretto,
|
||||||
Y_2: CompressedRistretto,
|
Y_2: CompressedRistretto,
|
||||||
|
@ -45,7 +47,7 @@ pub struct EqualityProof {
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl EqualityProof {
|
impl CtxtCommEqualityProof {
|
||||||
/// Equality proof constructor.
|
/// Equality proof constructor.
|
||||||
///
|
///
|
||||||
/// The function does *not* hash the public key, ciphertext, or commitment into the transcript.
|
/// 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
|
/// * `opening` - The opening associated with the main Pedersen commitment to be proved
|
||||||
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||||
pub fn new(
|
pub fn new(
|
||||||
elgamal_keypair: &ElGamalKeypair,
|
keypair_source: &ElGamalKeypair,
|
||||||
ciphertext: &ElGamalCiphertext,
|
ciphertext_source: &ElGamalCiphertext,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
opening: &PedersenOpening,
|
opening: &PedersenOpening,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
|
@ -72,10 +74,10 @@ impl EqualityProof {
|
||||||
transcript.equality_proof_domain_sep();
|
transcript.equality_proof_domain_sep();
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
// extract the relevant scalar and Ristretto points from the inputs
|
||||||
let P_EG = elgamal_keypair.public.get_point();
|
let P_source = keypair_source.public.get_point();
|
||||||
let D_EG = ciphertext.handle.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 x = Scalar::from(amount);
|
||||||
let r = opening.get_scalar();
|
let r = opening.get_scalar();
|
||||||
|
|
||||||
|
@ -84,8 +86,9 @@ impl EqualityProof {
|
||||||
let mut y_x = Scalar::random(&mut OsRng);
|
let mut y_x = Scalar::random(&mut OsRng);
|
||||||
let mut y_r = Scalar::random(&mut OsRng);
|
let mut y_r = Scalar::random(&mut OsRng);
|
||||||
|
|
||||||
let Y_0 = (&y_s * P_EG).compress();
|
let Y_0 = (&y_s * P_source).compress();
|
||||||
let Y_1 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_s], vec![&(*G), D_EG]).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_2 = RistrettoPoint::multiscalar_mul(vec![&y_x, &y_r], vec![&(*G), &(*H)]).compress();
|
||||||
|
|
||||||
// record masking factors in the transcript
|
// record masking factors in the transcript
|
||||||
|
@ -106,7 +109,7 @@ impl EqualityProof {
|
||||||
y_x.zeroize();
|
y_x.zeroize();
|
||||||
y_r.zeroize();
|
y_r.zeroize();
|
||||||
|
|
||||||
EqualityProof {
|
CtxtCommEqualityProof {
|
||||||
Y_0,
|
Y_0,
|
||||||
Y_1,
|
Y_1,
|
||||||
Y_2,
|
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
|
/// * `elgamal_pubkey` - The ElGamal pubkey associated with the ciphertext to be proved
|
||||||
/// * `ciphertext` - The main ElGamal 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
|
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
self,
|
self,
|
||||||
elgamal_pubkey: &ElGamalPubkey,
|
pubkey_source: &ElGamalPubkey,
|
||||||
ciphertext: &ElGamalCiphertext,
|
ciphertext_source: &ElGamalCiphertext,
|
||||||
commitment: &PedersenCommitment,
|
commitment_dest: &PedersenCommitment,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), EqualityProofError> {
|
) -> Result<(), EqualityProofError> {
|
||||||
transcript.equality_proof_domain_sep();
|
transcript.equality_proof_domain_sep();
|
||||||
|
|
||||||
// extract the relevant scalar and Ristretto points from the inputs
|
// extract the relevant scalar and Ristretto points from the inputs
|
||||||
let P_EG = elgamal_pubkey.get_point();
|
let P_source = pubkey_source.get_point();
|
||||||
let C_EG = ciphertext.commitment.get_point();
|
let C_source = ciphertext_source.commitment.get_point();
|
||||||
let D_EG = ciphertext.handle.get_point();
|
let D_source = ciphertext_source.handle.get_point();
|
||||||
let C_Ped = commitment.get_point();
|
let C_dest = commitment_dest.get_point();
|
||||||
|
|
||||||
// include Y_0, Y_1, Y_2 to transcript and extract challenges
|
// 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_0", &self.Y_0)?;
|
||||||
|
@ -169,16 +172,16 @@ impl EqualityProof {
|
||||||
&ww_negated, // -ww
|
&ww_negated, // -ww
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
P_EG, // P_EG
|
P_source, // P_source
|
||||||
&(*H), // H
|
&(*H), // H
|
||||||
&Y_0, // Y_0
|
&Y_0, // Y_0
|
||||||
&(*G), // G
|
&(*G), // G
|
||||||
D_EG, // D_EG
|
D_source, // D_source
|
||||||
C_EG, // C_EG
|
C_source, // C_source
|
||||||
&Y_1, // Y_1
|
&Y_1, // Y_1
|
||||||
&(*G), // G
|
&(*G), // G
|
||||||
&(*H), // H
|
&(*H), // H
|
||||||
C_Ped, // C_Ped
|
C_dest, // C_dest
|
||||||
&Y_2, // Y_2
|
&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_x = Scalar::from_canonical_bytes(*z_x).ok_or(EqualityProofError::Format)?;
|
||||||
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(EqualityProofError::Format)?;
|
let z_r = Scalar::from_canonical_bytes(*z_r).ok_or(EqualityProofError::Format)?;
|
||||||
|
|
||||||
Ok(EqualityProof {
|
Ok(CtxtCommEqualityProof {
|
||||||
Y_0,
|
Y_0,
|
||||||
Y_1,
|
Y_1,
|
||||||
Y_2,
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::encryption::{elgamal::ElGamalSecretKey, pedersen::Pedersen};
|
use crate::encryption::{elgamal::ElGamalSecretKey, pedersen::Pedersen};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_equality_proof_correctness() {
|
fn test_ciphertext_commitment_equality_proof_correctness() {
|
||||||
// success case
|
// success case
|
||||||
let elgamal_keypair = ElGamalKeypair::new_rand();
|
let keypair_source = ElGamalKeypair::new_rand();
|
||||||
let message: u64 = 55;
|
let message: u64 = 55;
|
||||||
|
|
||||||
let ciphertext = elgamal_keypair.public.encrypt(message);
|
let ciphertext_source = keypair_source.public.encrypt(message);
|
||||||
let (commitment, opening) = Pedersen::new(message);
|
let (commitment_dest, opening_dest) = Pedersen::new(message);
|
||||||
|
|
||||||
let mut transcript_prover = Transcript::new(b"Test");
|
let mut transcript_prover = Transcript::new(b"Test");
|
||||||
let mut transcript_verifier = Transcript::new(b"Test");
|
let mut transcript_verifier = Transcript::new(b"Test");
|
||||||
|
|
||||||
let proof = EqualityProof::new(
|
let proof = CtxtCommEqualityProof::new(
|
||||||
&elgamal_keypair,
|
&keypair_source,
|
||||||
&ciphertext,
|
&ciphertext_source,
|
||||||
message,
|
message,
|
||||||
&opening,
|
&opening_dest,
|
||||||
&mut transcript_prover,
|
&mut transcript_prover,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(proof
|
assert!(proof
|
||||||
.verify(
|
.verify(
|
||||||
&elgamal_keypair.public,
|
&keypair_source.public,
|
||||||
&ciphertext,
|
&ciphertext_source,
|
||||||
&commitment,
|
&commitment_dest,
|
||||||
&mut transcript_verifier
|
&mut transcript_verifier
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
// fail case: encrypted and committed messages are different
|
// 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 encrypted_message: u64 = 55;
|
||||||
let committed_message: u64 = 77;
|
let committed_message: u64 = 77;
|
||||||
|
|
||||||
let ciphertext = elgamal_keypair.public.encrypt(encrypted_message);
|
let ciphertext_source = keypair_source.public.encrypt(encrypted_message);
|
||||||
let (commitment, opening) = Pedersen::new(committed_message);
|
let (commitment_dest, opening_dest) = Pedersen::new(committed_message);
|
||||||
|
|
||||||
let mut transcript_prover = Transcript::new(b"Test");
|
let mut transcript_prover = Transcript::new(b"Test");
|
||||||
let mut transcript_verifier = Transcript::new(b"Test");
|
let mut transcript_verifier = Transcript::new(b"Test");
|
||||||
|
|
||||||
let proof = EqualityProof::new(
|
let proof = CtxtCommEqualityProof::new(
|
||||||
&elgamal_keypair,
|
&keypair_source,
|
||||||
&ciphertext,
|
&ciphertext_source,
|
||||||
message,
|
message,
|
||||||
&opening,
|
&opening_dest,
|
||||||
&mut transcript_prover,
|
&mut transcript_prover,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(proof
|
assert!(proof
|
||||||
.verify(
|
.verify(
|
||||||
&elgamal_keypair.public,
|
&keypair_source.public,
|
||||||
&ciphertext,
|
&ciphertext_source,
|
||||||
&commitment,
|
&commitment_dest,
|
||||||
&mut transcript_verifier
|
&mut transcript_verifier
|
||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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
|
// if ElGamal public key zero (public key is invalid), then the proof should always reject
|
||||||
let public = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap();
|
let public = ElGamalPubkey::from_bytes(&[0u8; 32]).unwrap();
|
||||||
let secret = ElGamalSecretKey::new_rand();
|
let secret = ElGamalSecretKey::new_rand();
|
||||||
|
@ -302,7 +524,7 @@ mod test {
|
||||||
let mut transcript_prover = Transcript::new(b"Test");
|
let mut transcript_prover = Transcript::new(b"Test");
|
||||||
let mut transcript_verifier = Transcript::new(b"Test");
|
let mut transcript_verifier = Transcript::new(b"Test");
|
||||||
|
|
||||||
let proof = EqualityProof::new(
|
let proof = CtxtCommEqualityProof::new(
|
||||||
&elgamal_keypair,
|
&elgamal_keypair,
|
||||||
&ciphertext,
|
&ciphertext,
|
||||||
message,
|
message,
|
||||||
|
@ -331,7 +553,7 @@ mod test {
|
||||||
let mut transcript_prover = Transcript::new(b"Test");
|
let mut transcript_prover = Transcript::new(b"Test");
|
||||||
let mut transcript_verifier = Transcript::new(b"Test");
|
let mut transcript_verifier = Transcript::new(b"Test");
|
||||||
|
|
||||||
let proof = EqualityProof::new(
|
let proof = CtxtCommEqualityProof::new(
|
||||||
&elgamal_keypair,
|
&elgamal_keypair,
|
||||||
&ciphertext,
|
&ciphertext,
|
||||||
message,
|
message,
|
||||||
|
@ -360,7 +582,7 @@ mod test {
|
||||||
let mut transcript_prover = Transcript::new(b"Test");
|
let mut transcript_prover = Transcript::new(b"Test");
|
||||||
let mut transcript_verifier = Transcript::new(b"Test");
|
let mut transcript_verifier = Transcript::new(b"Test");
|
||||||
|
|
||||||
let proof = EqualityProof::new(
|
let proof = CtxtCommEqualityProof::new(
|
||||||
&elgamal_keypair,
|
&elgamal_keypair,
|
||||||
&ciphertext,
|
&ciphertext,
|
||||||
message,
|
message,
|
||||||
|
@ -388,7 +610,7 @@ mod test {
|
||||||
let mut transcript_prover = Transcript::new(b"Test");
|
let mut transcript_prover = Transcript::new(b"Test");
|
||||||
let mut transcript_verifier = Transcript::new(b"Test");
|
let mut transcript_verifier = Transcript::new(b"Test");
|
||||||
|
|
||||||
let proof = EqualityProof::new(
|
let proof = CtxtCommEqualityProof::new(
|
||||||
&elgamal_keypair,
|
&elgamal_keypair,
|
||||||
&ciphertext,
|
&ciphertext,
|
||||||
message,
|
message,
|
||||||
|
@ -405,4 +627,72 @@ mod test {
|
||||||
)
|
)
|
||||||
.is_ok());
|
.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
|
//! The module contains implementations of the following proof systems that work on Pedersen
|
||||||
//! commitments and twisted ElGamal ciphertexts:
|
//! commitments and twisted ElGamal ciphertexts:
|
||||||
//! - Equality proof: can be used to certify that a twisted ElGamal ciphertext and a Pedersen
|
//! - Equality proof: can be used to certify that a twisted ElGamal ciphertext encrypts the same
|
||||||
//! commitment encrypt/encode the same message.
|
//! 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
|
//! - 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.
|
//! 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
|
//! - 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},
|
range_proof::{errors::RangeProofError, RangeProof},
|
||||||
sigma_proofs::{
|
sigma_proofs::{
|
||||||
equality_proof::EqualityProof,
|
equality_proof::{CtxtCommEqualityProof, CtxtCtxtEqualityProof},
|
||||||
errors::*,
|
errors::*,
|
||||||
fee_proof::FeeSigmaProof,
|
fee_proof::FeeSigmaProof,
|
||||||
validity_proof::{AggregatedValidityProof, ValidityProof},
|
validity_proof::{AggregatedValidityProof, ValidityProof},
|
||||||
|
@ -151,16 +151,30 @@ mod target_arch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EqualityProof> for pod::EqualityProof {
|
impl From<CtxtCommEqualityProof> for pod::CtxtCommEqualityProof {
|
||||||
fn from(proof: EqualityProof) -> Self {
|
fn from(proof: CtxtCommEqualityProof) -> Self {
|
||||||
Self(proof.to_bytes())
|
Self(proof.to_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<pod::EqualityProof> for EqualityProof {
|
impl TryFrom<pod::CtxtCommEqualityProof> for CtxtCommEqualityProof {
|
||||||
type Error = EqualityProofError;
|
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)
|
Self::from_bytes(&pod.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,9 +327,9 @@ mod target_arch {
|
||||||
impl From<TransferPubkeys> for pod::TransferPubkeys {
|
impl From<TransferPubkeys> for pod::TransferPubkeys {
|
||||||
fn from(keys: TransferPubkeys) -> Self {
|
fn from(keys: TransferPubkeys) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: keys.source.into(),
|
pubkey_source: keys.pubkey_source.into(),
|
||||||
dest: keys.dest.into(),
|
pubkey_dest: keys.pubkey_dest.into(),
|
||||||
auditor: keys.auditor.into(),
|
pubkey_auditor: keys.pubkey_auditor.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,9 +339,9 @@ mod target_arch {
|
||||||
|
|
||||||
fn try_from(pod: pod::TransferPubkeys) -> Result<Self, Self::Error> {
|
fn try_from(pod: pod::TransferPubkeys) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
source: pod.source.try_into()?,
|
pubkey_source: pod.pubkey_source.try_into()?,
|
||||||
dest: pod.dest.try_into()?,
|
pubkey_dest: pod.pubkey_dest.try_into()?,
|
||||||
auditor: pod.auditor.try_into()?,
|
pubkey_auditor: pod.pubkey_auditor.try_into()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,10 +349,10 @@ mod target_arch {
|
||||||
impl From<TransferWithFeePubkeys> for pod::TransferWithFeePubkeys {
|
impl From<TransferWithFeePubkeys> for pod::TransferWithFeePubkeys {
|
||||||
fn from(keys: TransferWithFeePubkeys) -> Self {
|
fn from(keys: TransferWithFeePubkeys) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: keys.source.into(),
|
pubkey_source: keys.pubkey_source.into(),
|
||||||
dest: keys.dest.into(),
|
pubkey_dest: keys.pubkey_dest.into(),
|
||||||
auditor: keys.auditor.into(),
|
pubkey_auditor: keys.pubkey_auditor.into(),
|
||||||
fee_collector: keys.fee_collector.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> {
|
fn try_from(pod: pod::TransferWithFeePubkeys) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
source: pod.source.try_into()?,
|
pubkey_source: pod.pubkey_source.try_into()?,
|
||||||
dest: pod.dest.try_into()?,
|
pubkey_dest: pod.pubkey_dest.try_into()?,
|
||||||
auditor: pod.auditor.try_into()?,
|
pubkey_auditor: pod.pubkey_auditor.try_into()?,
|
||||||
fee_collector: pod.fee_collector.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 {
|
fn from(ciphertext: TransferAmountEncryption) -> Self {
|
||||||
Self {
|
Self {
|
||||||
commitment: ciphertext.commitment.into(),
|
commitment: ciphertext.commitment.into(),
|
||||||
source: ciphertext.source.into(),
|
handle_source: ciphertext.handle_source.into(),
|
||||||
dest: ciphertext.dest.into(),
|
handle_dest: ciphertext.handle_dest.into(),
|
||||||
auditor: ciphertext.auditor.into(),
|
handle_auditor: ciphertext.handle_auditor.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,9 +389,9 @@ mod target_arch {
|
||||||
fn try_from(pod: pod::TransferAmountEncryption) -> Result<Self, Self::Error> {
|
fn try_from(pod: pod::TransferAmountEncryption) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
commitment: pod.commitment.try_into()?,
|
commitment: pod.commitment.try_into()?,
|
||||||
source: pod.source.try_into()?,
|
handle_source: pod.handle_source.try_into()?,
|
||||||
dest: pod.dest.try_into()?,
|
handle_dest: pod.handle_dest.try_into()?,
|
||||||
auditor: pod.auditor.try_into()?,
|
handle_auditor: pod.handle_auditor.try_into()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,8 +400,10 @@ mod target_arch {
|
||||||
fn from(ciphertext: FeeEncryption) -> Self {
|
fn from(ciphertext: FeeEncryption) -> Self {
|
||||||
Self {
|
Self {
|
||||||
commitment: ciphertext.commitment.into(),
|
commitment: ciphertext.commitment.into(),
|
||||||
dest: ciphertext.dest.into(),
|
handle_dest: ciphertext.handle_dest.into(),
|
||||||
fee_collector: ciphertext.fee_collector.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> {
|
fn try_from(pod: pod::FeeEncryption) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
commitment: pod.commitment.try_into()?,
|
commitment: pod.commitment.try_into()?,
|
||||||
dest: pod.dest.try_into()?,
|
handle_dest: pod.handle_dest.try_into()?,
|
||||||
fee_collector: pod.fee_collector.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)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[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
|
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
|
||||||
unsafe impl Zeroable for EqualityProof {}
|
unsafe impl Zeroable for CtxtCommEqualityProof {}
|
||||||
unsafe impl Pod for EqualityProof {}
|
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
|
/// Serialization of validity proofs
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -184,40 +194,35 @@ impl Default for AeCiphertext {
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TransferPubkeys {
|
pub struct TransferPubkeys {
|
||||||
pub source: ElGamalPubkey,
|
pub pubkey_source: ElGamalPubkey,
|
||||||
pub dest: ElGamalPubkey,
|
pub pubkey_dest: ElGamalPubkey,
|
||||||
pub auditor: 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)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TransferWithFeePubkeys {
|
pub struct TransferWithFeePubkeys {
|
||||||
pub source: ElGamalPubkey,
|
pub pubkey_source: ElGamalPubkey,
|
||||||
pub dest: ElGamalPubkey,
|
pub pubkey_dest: ElGamalPubkey,
|
||||||
pub auditor: ElGamalPubkey,
|
pub pubkey_auditor: ElGamalPubkey,
|
||||||
pub fee_collector: ElGamalPubkey,
|
pub pubkey_withdraw_withheld_authority: ElGamalPubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TransferAmountEncryption {
|
pub struct TransferAmountEncryption {
|
||||||
pub commitment: PedersenCommitment,
|
pub commitment: PedersenCommitment,
|
||||||
pub source: DecryptHandle,
|
pub handle_source: DecryptHandle,
|
||||||
pub dest: DecryptHandle,
|
pub handle_dest: DecryptHandle,
|
||||||
pub auditor: DecryptHandle,
|
pub handle_auditor: DecryptHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct FeeEncryption {
|
pub struct FeeEncryption {
|
||||||
pub commitment: PedersenCommitment,
|
pub commitment: PedersenCommitment,
|
||||||
pub dest: DecryptHandle,
|
pub handle_dest: DecryptHandle,
|
||||||
pub fee_collector: DecryptHandle,
|
pub handle_withdraw_withheld_authority: DecryptHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
|
|
@ -30,6 +30,16 @@ pub enum ProofInstruction {
|
||||||
///
|
///
|
||||||
VerifyWithdraw,
|
VerifyWithdraw,
|
||||||
|
|
||||||
|
/// Verify a `WithdrawWithheldTokensData` struct
|
||||||
|
///
|
||||||
|
/// Accounts expected by this instruction:
|
||||||
|
/// None
|
||||||
|
///
|
||||||
|
/// Data expected by this instruction:
|
||||||
|
/// `WithdrawWithheldTokensData`
|
||||||
|
///
|
||||||
|
VerifyWithdrawWithheldTokens,
|
||||||
|
|
||||||
/// Verify a `TransferData` struct
|
/// Verify a `TransferData` struct
|
||||||
///
|
///
|
||||||
/// Accounts expected by this instruction:
|
/// Accounts expected by this instruction:
|
||||||
|
@ -83,6 +93,10 @@ pub fn verify_withdraw(proof_data: &WithdrawData) -> Instruction {
|
||||||
ProofInstruction::VerifyWithdraw.encode(proof_data)
|
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 {
|
pub fn verify_transfer(proof_data: &TransferData) -> Instruction {
|
||||||
ProofInstruction::VerifyTransfer.encode(proof_data)
|
ProofInstruction::VerifyTransfer.encode(proof_data)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue