[zk-token-sdk] rename and restructure `CloseAccount` and `WithdrawWithheld` proof instructions (#31608)

* rename and restruct ProofInstruction::VerifyCloseAccount to ProofInstruction::VerifyZeroBalance

* rename `PubkeySigmaProof` to `PubkeyValidityProof`

* refactor equality proofs into separate modules

* rename and restructure ProofInstruction::VerifyWithdrawWithheld to ProofInstruction::VerifyCiphertextCiphertextEquality

* add and reorg documentation for the proof instructions

* generalize ciphertext-ciphertext equality proof data constructor

* Update zk-token-sdk/src/instruction/ctxt_ctxt_equality.rs

Co-authored-by: Jon Cinque <joncinque@pm.me>

---------

Co-authored-by: Jon Cinque <joncinque@pm.me>
This commit is contained in:
samkim-crypto 2023-05-18 08:43:00 +09:00 committed by GitHub
parent bd8289e114
commit f9b0691eb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 871 additions and 906 deletions

View File

@ -9,69 +9,79 @@ use {
transaction::{Transaction, TransactionError},
},
solana_zk_token_sdk::{
encryption::elgamal::ElGamalKeypair, instruction::*, zk_token_proof_instruction::*,
zk_token_proof_program, zk_token_proof_state::ProofContextState,
encryption::{elgamal::ElGamalKeypair, pedersen::PedersenOpening},
instruction::*,
zk_token_proof_instruction::*,
zk_token_proof_program,
zk_token_proof_state::ProofContextState,
},
std::mem::size_of,
};
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 6] = [
ProofInstruction::VerifyCloseAccount,
ProofInstruction::VerifyZeroBalance,
ProofInstruction::VerifyWithdraw,
ProofInstruction::VerifyWithdrawWithheldTokens,
ProofInstruction::VerifyCiphertextCiphertextEquality,
ProofInstruction::VerifyTransfer,
ProofInstruction::VerifyTransferWithFee,
ProofInstruction::VerifyPubkeyValidity,
];
#[tokio::test]
async fn test_close_account() {
async fn test_zero_balance() {
let elgamal_keypair = ElGamalKeypair::new_rand();
let zero_ciphertext = elgamal_keypair.public.encrypt(0_u64);
let success_proof_data = CloseAccountData::new(&elgamal_keypair, &zero_ciphertext).unwrap();
let success_proof_data = ZeroBalanceProofData::new(&elgamal_keypair, &zero_ciphertext).unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = CloseAccountData::new(&incorrect_keypair, &zero_ciphertext).unwrap();
let fail_proof_data = ZeroBalanceProofData::new(&incorrect_keypair, &zero_ciphertext).unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyCloseAccount,
ProofInstruction::VerifyZeroBalance,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyCloseAccount,
size_of::<ProofContextState<CloseAccountProofContext>>(),
ProofInstruction::VerifyZeroBalance,
size_of::<ProofContextState<ZeroBalanceProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyCloseAccount,
size_of::<ProofContextState<CloseAccountProofContext>>(),
ProofInstruction::VerifyZeroBalance,
size_of::<ProofContextState<ZeroBalanceProofContext>>(),
&success_proof_data,
)
.await;
}
#[tokio::test]
async fn test_withdraw_withheld_tokens() {
let elgamal_keypair = ElGamalKeypair::new_rand();
async fn test_ciphertext_ciphertext_equality() {
let source_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();
let amount: u64 = 0;
let withdraw_withheld_authority_ciphertext = elgamal_keypair.public.encrypt(amount);
let source_ciphertext = source_keypair.public.encrypt(amount);
let success_proof_data = WithdrawWithheldTokensData::new(
&elgamal_keypair,
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);
let success_proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&withdraw_withheld_authority_ciphertext,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();
@ -80,32 +90,34 @@ async fn test_withdraw_withheld_tokens() {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = WithdrawWithheldTokensData::new(
let fail_proof_data = CiphertextCiphertextEqualityProofData::new(
&incorrect_keypair,
&destination_keypair.public,
&withdraw_withheld_authority_ciphertext,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyWithdrawWithheldTokens,
ProofInstruction::VerifyCiphertextCiphertextEquality,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyWithdrawWithheldTokens,
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
ProofInstruction::VerifyCiphertextCiphertextEquality,
size_of::<ProofContextState<CiphertextCiphertextEqualityProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyWithdrawWithheldTokens,
size_of::<ProofContextState<WithdrawWithheldTokensProofContext>>(),
ProofInstruction::VerifyCiphertextCiphertextEquality,
size_of::<ProofContextState<CiphertextCiphertextEqualityProofContext>>(),
&success_proof_data,
)
.await;

View File

@ -135,14 +135,14 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
ic_msg!(invoke_context, "CloseContextState");
process_close_proof_context(invoke_context)
}
ProofInstruction::VerifyCloseAccount => {
ProofInstruction::VerifyZeroBalance => {
if native_programs_consume_cu {
invoke_context
.consume_checked(6_012)
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
}
ic_msg!(invoke_context, "VerifyCloseAccount");
process_verify_proof::<CloseAccountData, CloseAccountProofContext>(invoke_context)
ic_msg!(invoke_context, "VerifyZeroBalance");
process_verify_proof::<ZeroBalanceProofData, ZeroBalanceProofContext>(invoke_context)
}
ProofInstruction::VerifyWithdraw => {
if native_programs_consume_cu {
@ -153,16 +153,17 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
ic_msg!(invoke_context, "VerifyWithdraw");
process_verify_proof::<WithdrawData, WithdrawProofContext>(invoke_context)
}
ProofInstruction::VerifyWithdrawWithheldTokens => {
ProofInstruction::VerifyCiphertextCiphertextEquality => {
if native_programs_consume_cu {
invoke_context
.consume_checked(7_943)
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
}
ic_msg!(invoke_context, "VerifyWithdrawWithheldTokens");
process_verify_proof::<WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext>(
invoke_context,
)
ic_msg!(invoke_context, "VerifyCiphertextCiphertextEquality");
process_verify_proof::<
CiphertextCiphertextEqualityProofData,
CiphertextCiphertextEqualityProofContext,
>(invoke_context)
}
ProofInstruction::VerifyTransfer => {
if native_programs_consume_cu {

View File

@ -1,153 +0,0 @@
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
errors::ProofError,
sigma_proofs::zero_balance_proof::ZeroBalanceProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// This struct includes the cryptographic proof *and* the account data information needed to verify
/// the proof
///
/// - The pre-instruction should call CloseAccountData::verify_proof(&self)
/// - The actual program should check that `balance` is consistent with what is
/// currently stored in the confidential token account
///
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CloseAccountData {
/// The context data for the close account proof
pub context: CloseAccountProofContext,
/// Proof that the source account available balance is zero
pub proof: CloseAccountProof, // 96 bytes
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CloseAccountProofContext {
/// The source account ElGamal pubkey
pub pubkey: pod::ElGamalPubkey, // 32 bytes
/// The source account available balance in encrypted form
pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
}
#[cfg(not(target_os = "solana"))]
impl CloseAccountData {
pub fn new(
keypair: &ElGamalKeypair,
ciphertext: &ElGamalCiphertext,
) -> Result<Self, ProofError> {
let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes());
let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
let context = CloseAccountProofContext {
pubkey: pod_pubkey,
ciphertext: pod_ciphertext,
};
let mut transcript = CloseAccountProof::transcript_new(&pod_pubkey, &pod_ciphertext);
let proof = CloseAccountProof::new(keypair, ciphertext, &mut transcript);
Ok(CloseAccountData { context, proof })
}
}
impl ZkProofData<CloseAccountProofContext> for CloseAccountData {
const PROOF_TYPE: ProofType = ProofType::CloseAccount;
fn context_data(&self) -> &CloseAccountProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript =
CloseAccountProof::transcript_new(&self.context.pubkey, &self.context.ciphertext);
let pubkey = self.context.pubkey.try_into()?;
let ciphertext = self.context.ciphertext.try_into()?;
self.proof.verify(&pubkey, &ciphertext, &mut transcript)
}
}
/// This struct represents the cryptographic proof component that certifies that the encrypted
/// balance is zero
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
#[allow(non_snake_case)]
pub struct CloseAccountProof {
pub proof: pod::ZeroBalanceProof,
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl CloseAccountProof {
fn transcript_new(
pubkey: &pod::ElGamalPubkey,
ciphertext: &pod::ElGamalCiphertext,
) -> Transcript {
let mut transcript = Transcript::new(b"CloseAccountProof");
transcript.append_pubkey(b"pubkey", pubkey);
transcript.append_ciphertext(b"ciphertext", ciphertext);
transcript
}
pub fn new(
keypair: &ElGamalKeypair,
ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Self {
let proof = ZeroBalanceProof::new(keypair, ciphertext, transcript);
Self {
proof: proof.into(),
}
}
pub fn verify(
&self,
pubkey: &ElGamalPubkey,
ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), ProofError> {
let proof: ZeroBalanceProof = self.proof.try_into()?;
proof.verify(pubkey, ciphertext, transcript)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_close_account_correctness() {
let keypair = ElGamalKeypair::new_rand();
// general case: encryption of 0
let ciphertext = keypair.public.encrypt(0_u64);
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
assert!(close_account_data.verify_proof().is_ok());
// general case: encryption of > 0
let ciphertext = keypair.public.encrypt(1_u64);
let close_account_data = CloseAccountData::new(&keypair, &ciphertext).unwrap();
assert!(close_account_data.verify_proof().is_err());
}
}

View File

@ -0,0 +1,228 @@
//! The ciphertext-ciphertext equality proof instruction.
//!
//! A ciphertext-ciphertext equality proof is defined with respect to two twisted ElGamal
//! ciphertexts. The proof certifies that the two ciphertexts encrypt the same message. To generate
//! the proof, a prover must provide the decryption key for the first ciphertext and the randomness
//! used to generate the second ciphertext.
//!
//! The first ciphertext associated with the proof is referred to as the "source" ciphertext. The
//! second ciphertext associated with the proof is referred to as the "destination" ciphertext.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::PedersenOpening,
},
errors::ProofError,
sigma_proofs::ctxt_ctxt_equality_proof::CiphertextCiphertextEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// The instruction data that is needed for the
/// `ProofInstruction::VerifyCiphertextCiphertextEquality` instruction.
///
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CiphertextCiphertextEqualityProofData {
pub context: CiphertextCiphertextEqualityProofContext,
pub proof: pod::CiphertextCiphertextEqualityProof,
}
/// The context data needed to verify a ciphertext-ciphertext equality proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CiphertextCiphertextEqualityProofContext {
pub source_pubkey: pod::ElGamalPubkey, // 32 bytes
pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
pub source_ciphertext: pod::ElGamalCiphertext, // 64 bytes
pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes
}
#[cfg(not(target_os = "solana"))]
impl CiphertextCiphertextEqualityProofData {
pub fn new(
source_keypair: &ElGamalKeypair,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
destination_ciphertext: &ElGamalCiphertext,
destination_opening: &PedersenOpening,
amount: u64,
) -> Result<Self, ProofError> {
let pod_source_pubkey = pod::ElGamalPubkey(source_keypair.public.to_bytes());
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
let pod_source_ciphertext = pod::ElGamalCiphertext(source_ciphertext.to_bytes());
let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());
let context = CiphertextCiphertextEqualityProofContext {
source_pubkey: pod_source_pubkey,
destination_pubkey: pod_destination_pubkey,
source_ciphertext: pod_source_ciphertext,
destination_ciphertext: pod_destination_ciphertext,
};
let mut transcript = CiphertextCiphertextEqualityProof::transcript_new(
&pod_source_pubkey,
&pod_destination_pubkey,
&pod_source_ciphertext,
&pod_destination_ciphertext,
);
let proof = CiphertextCiphertextEqualityProof::new(
source_keypair,
destination_pubkey,
source_ciphertext,
destination_opening,
amount,
&mut transcript,
)
.into();
Ok(Self { context, proof })
}
}
impl ZkProofData<CiphertextCiphertextEqualityProofContext>
for CiphertextCiphertextEqualityProofData
{
const PROOF_TYPE: ProofType = ProofType::CiphertextCiphertextEquality;
fn context_data(&self) -> &CiphertextCiphertextEqualityProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = CiphertextCiphertextEqualityProof::transcript_new(
&self.context.source_pubkey,
&self.context.destination_pubkey,
&self.context.source_ciphertext,
&self.context.destination_ciphertext,
);
let source_pubkey = self.context.source_pubkey.try_into()?;
let destination_pubkey = self.context.destination_pubkey.try_into()?;
let source_ciphertext = self.context.source_ciphertext.try_into()?;
let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
let proof: CiphertextCiphertextEqualityProof = self.proof.try_into()?;
proof
.verify(
&source_pubkey,
&destination_pubkey,
&source_ciphertext,
&destination_ciphertext,
&mut transcript,
)
.map_err(|e| e.into())
}
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl CiphertextCiphertextEqualityProof {
fn transcript_new(
source_pubkey: &pod::ElGamalPubkey,
destination_pubkey: &pod::ElGamalPubkey,
source_ciphertext: &pod::ElGamalCiphertext,
destination_ciphertext: &pod::ElGamalCiphertext,
) -> Transcript {
let mut transcript = Transcript::new(b"CiphertextCiphertextEqualityProof");
transcript.append_pubkey(b"pubkey-source", source_pubkey);
transcript.append_pubkey(b"pubkey-dest", destination_pubkey);
transcript.append_ciphertext(b"ciphertext-source", source_ciphertext);
transcript.append_ciphertext(b"ciphertext-dest", destination_ciphertext);
transcript
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_ciphertext_ciphertext_instruction_correctness() {
let source_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();
let amount: u64 = 0;
let source_ciphertext = source_keypair.public.encrypt(amount);
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);
let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();
assert!(proof_data.verify_proof().is_ok());
let amount: u64 = 55;
let source_ciphertext = source_keypair.public.encrypt(amount);
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);
let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();
assert!(proof_data.verify_proof().is_ok());
let amount = u64::max_value();
let source_ciphertext = source_keypair.public.encrypt(amount);
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(amount, &destination_opening);
let proof_data = CiphertextCiphertextEqualityProofData::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&destination_opening,
amount,
)
.unwrap();
assert!(proof_data.verify_proof().is_ok());
}
}

View File

@ -1,9 +1,9 @@
pub mod close_account;
pub mod ctxt_ctxt_equality;
pub mod pubkey_validity;
pub mod transfer;
pub mod transfer_with_fee;
pub mod withdraw;
pub mod withdraw_withheld;
pub mod zero_balance;
use num_derive::{FromPrimitive, ToPrimitive};
#[cfg(not(target_os = "solana"))]
@ -19,12 +19,14 @@ use {
};
pub use {
bytemuck::Pod,
close_account::{CloseAccountData, CloseAccountProofContext},
ctxt_ctxt_equality::{
CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
},
pubkey_validity::{PubkeyValidityData, PubkeyValidityProofContext},
transfer::{TransferData, TransferProofContext},
transfer_with_fee::{FeeParameters, TransferWithFeeData, TransferWithFeeProofContext},
withdraw::{WithdrawData, WithdrawProofContext},
withdraw_withheld::{WithdrawWithheldTokensData, WithdrawWithheldTokensProofContext},
zero_balance::{ZeroBalanceProofContext, ZeroBalanceProofData},
};
#[derive(Clone, Copy, Debug, FromPrimitive, ToPrimitive, PartialEq, Eq)]
@ -32,9 +34,9 @@ pub use {
pub enum ProofType {
/// Empty proof type used to distinguish if a proof context account is initialized
Uninitialized,
CloseAccount,
ZeroBalance,
Withdraw,
WithdrawWithheldTokens,
CiphertextCiphertextEquality,
Transfer,
TransferWithFee,
PubkeyValidity,

View File

@ -1,10 +1,15 @@
//! The public-key validity proof instruction.
//!
//! A public-key validity proof system is defined with respect to an ElGamal public key. The proof
//! certifies that a given public key is a valid ElGamal public key (i.e. the prover knows a
//! corresponding secret key). To generate the proof, a prover must provide the secret key for the
//! public key.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::elgamal::{ElGamalKeypair, ElGamalPubkey},
errors::ProofError,
sigma_proofs::pubkey_proof::PubkeySigmaProof,
transcript::TranscriptProtocol,
encryption::elgamal::ElGamalKeypair, errors::ProofError,
sigma_proofs::pubkey_proof::PubkeyValidityProof, transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
@ -17,22 +22,22 @@ use {
bytemuck::{Pod, Zeroable},
};
/// This struct includes the cryptographic proof *and* the account data information needed to
/// verify the proof
/// The instruction data that is needed for the `ProofInstruction::VerifyPubkeyValidity`
/// instruction.
///
/// - The pre-instruction should call PubkeyValidityData::verify_proof(&self)
/// - The actual program should check that the public key in this struct is consistent with what is
/// stored in the confidential token account
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct PubkeyValidityData {
/// The context data for the public key validity proof
pub context: PubkeyValidityProofContext,
pub context: PubkeyValidityProofContext, // 32 bytes
/// Proof that the public key is well-formed
pub proof: PubkeyValidityProof, // 64 bytes
pub proof: pod::PubkeyValidityProof, // 64 bytes
}
/// The context data needed to verify a pubkey validity proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct PubkeyValidityProofContext {
@ -48,7 +53,7 @@ impl PubkeyValidityData {
let context = PubkeyValidityProofContext { pubkey: pod_pubkey };
let mut transcript = PubkeyValidityProof::transcript_new(&pod_pubkey);
let proof = PubkeyValidityProof::new(keypair, &mut transcript);
let proof = PubkeyValidityProof::new(keypair, &mut transcript).into();
Ok(PubkeyValidityData { context, proof })
}
@ -65,18 +70,11 @@ impl ZkProofData<PubkeyValidityProofContext> for PubkeyValidityData {
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = PubkeyValidityProof::transcript_new(&self.context.pubkey);
let pubkey = self.context.pubkey.try_into()?;
self.proof.verify(&pubkey, &mut transcript)
let proof: PubkeyValidityProof = self.proof.try_into()?;
proof.verify(&pubkey, &mut transcript).map_err(|e| e.into())
}
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
#[allow(non_snake_case)]
pub struct PubkeyValidityProof {
/// Associated public-key sigma proof
pub proof: pod::PubkeySigmaProof,
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl PubkeyValidityProof {
@ -85,23 +83,6 @@ impl PubkeyValidityProof {
transcript.append_pubkey(b"pubkey", pubkey);
transcript
}
pub fn new(keypair: &ElGamalKeypair, transcript: &mut Transcript) -> Self {
let proof = PubkeySigmaProof::new(keypair, transcript);
Self {
proof: proof.into(),
}
}
pub fn verify(
&self,
pubkey: &ElGamalPubkey,
transcript: &mut Transcript,
) -> Result<(), ProofError> {
let proof: PubkeySigmaProof = self.proof.try_into()?;
proof.verify(pubkey, transcript)?;
Ok(())
}
}
#[cfg(test)]
@ -109,7 +90,7 @@ mod test {
use super::*;
#[test]
fn test_pubkey_validity_correctness() {
fn test_pubkey_validity_instruction_correctness() {
let keypair = ElGamalKeypair::new_rand();
let pubkey_validity_data = PubkeyValidityData::new(&keypair).unwrap();

View File

@ -11,7 +11,8 @@ use {
instruction::{combine_lo_hi_ciphertexts, split_u64, Role},
range_proof::RangeProof,
sigma_proofs::{
equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof,
ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof,
validity_proof::AggregatedValidityProof,
},
transcript::TranscriptProtocol,
},
@ -42,6 +43,10 @@ lazy_static::lazy_static! {
TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1);
}
/// The instruction data that is needed for the `ProofInstruction::VerifyTransfer` instruction.
///
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferData {
@ -52,6 +57,7 @@ pub struct TransferData {
pub proof: TransferProof,
}
/// The context data needed to verify a transfer proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferProofContext {
@ -252,7 +258,7 @@ pub struct TransferProof {
pub new_source_commitment: pod::PedersenCommitment,
/// Associated equality proof
pub equality_proof: pod::CtxtCommEqualityProof,
pub equality_proof: pod::CiphertextCommitmentEqualityProof,
/// Associated ciphertext validity proof
pub validity_proof: pod::AggregatedValidityProof,
@ -307,11 +313,11 @@ impl TransferProof {
transcript.append_commitment(b"commitment-new-source", &pod_new_source_commitment);
// generate equality_proof
let equality_proof = CtxtCommEqualityProof::new(
let equality_proof = CiphertextCommitmentEqualityProof::new(
source_keypair,
new_source_ciphertext,
source_new_balance,
&source_opening,
source_new_balance,
transcript,
);
@ -377,13 +383,11 @@ impl TransferProof {
transcript.append_commitment(b"commitment-new-source", &self.new_source_commitment);
let commitment: PedersenCommitment = self.new_source_commitment.try_into()?;
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?;
let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
let range_proof: RangeProof = self.range_proof.try_into()?;
// verify equality proof
//
// TODO: we can also consider verifying equality and range proof in a batch
equality_proof.verify(
&transfer_pubkeys.source_pubkey,
ciphertext_new_spendable,

View File

@ -14,7 +14,7 @@ use {
},
range_proof::RangeProof,
sigma_proofs::{
equality_proof::CtxtCommEqualityProof, fee_proof::FeeSigmaProof,
ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof, fee_proof::FeeSigmaProof,
validity_proof::AggregatedValidityProof,
},
transcript::TranscriptProtocol,
@ -60,7 +60,10 @@ lazy_static::lazy_static! {
pub static ref COMMITMENT_MAX_FEE_BASIS_POINTS: PedersenCommitment = Pedersen::encode(MAX_FEE_BASIS_POINTS);
}
// #[derive(Clone, Copy, Pod, Zeroable)]
/// The instruction data that is needed for the `ProofInstruction::TransferWithFee` instruction.
///
/// It includes the cryptographic proof as well as the cotnext data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferWithFeeData {
@ -71,6 +74,7 @@ pub struct TransferWithFeeData {
pub proof: TransferWithFeeProof,
}
/// The context data needed to verify a transfer-with-fee proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct TransferWithFeeProofContext {
@ -386,7 +390,7 @@ impl ZkProofData<TransferWithFeeProofContext> for TransferWithFeeData {
pub struct TransferWithFeeProof {
pub new_source_commitment: pod::PedersenCommitment,
pub claimed_commitment: pod::PedersenCommitment,
pub equality_proof: pod::CtxtCommEqualityProof,
pub equality_proof: pod::CiphertextCommitmentEqualityProof,
pub ciphertext_amount_validity_proof: pod::AggregatedValidityProof,
pub fee_sigma_proof: pod::FeeSigmaProof,
pub fee_ciphertext_validity_proof: pod::AggregatedValidityProof,
@ -472,11 +476,11 @@ impl TransferWithFeeProof {
transcript.append_commitment(b"commitment-new-source", &pod_new_source_commitment);
// generate equality_proof
let equality_proof = CtxtCommEqualityProof::new(
let equality_proof = CiphertextCommitmentEqualityProof::new(
source_keypair,
new_source_ciphertext,
source_new_balance,
&opening_source,
source_new_balance,
transcript,
);
@ -602,7 +606,7 @@ impl TransferWithFeeProof {
let new_source_commitment: PedersenCommitment = self.new_source_commitment.try_into()?;
let claimed_commitment: PedersenCommitment = self.claimed_commitment.try_into()?;
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?;
let ciphertext_amount_validity_proof: AggregatedValidityProof =
self.ciphertext_amount_validity_proof.try_into()?;
let fee_sigma_proof: FeeSigmaProof = self.fee_sigma_proof.try_into()?;

View File

@ -7,7 +7,7 @@ use {
},
errors::ProofError,
range_proof::RangeProof,
sigma_proofs::equality_proof::CtxtCommEqualityProof,
sigma_proofs::ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
@ -24,13 +24,10 @@ use {
#[cfg(not(target_os = "solana"))]
const WITHDRAW_AMOUNT_BIT_LENGTH: usize = 64;
/// 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
/// The instruction data that is needed for the `ProofInstruction::VerifyWithdraw` instruction.
///
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawData {
@ -41,6 +38,7 @@ pub struct WithdrawData {
pub proof: WithdrawProof, // 736 bytes
}
/// The context data needed to verify a withdraw proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawProofContext {
@ -105,8 +103,9 @@ impl ZkProofData<WithdrawProofContext> for WithdrawData {
}
}
/// This struct represents the cryptographic proof component that certifies the account's solvency
/// for withdrawal
/// The withdraw proof.
///
/// It contains a ciphertext-commitment equality proof and a 64-bit range proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
#[allow(non_snake_case)]
@ -115,7 +114,7 @@ pub struct WithdrawProof {
pub commitment: pod::PedersenCommitment,
/// Associated equality proof
pub equality_proof: pod::CtxtCommEqualityProof,
pub equality_proof: pod::CiphertextCommitmentEqualityProof,
/// Associated range proof
pub range_proof: pod::RangeProof64, // 672 bytes
@ -149,11 +148,11 @@ impl WithdrawProof {
transcript.append_commitment(b"commitment", &pod_commitment);
// generate equality_proof
let equality_proof = CtxtCommEqualityProof::new(
let equality_proof = CiphertextCommitmentEqualityProof::new(
keypair,
final_ciphertext,
final_balance,
&opening,
final_balance,
transcript,
);
@ -176,17 +175,13 @@ impl WithdrawProof {
transcript.append_commitment(b"commitment", &self.commitment);
let commitment: PedersenCommitment = self.commitment.try_into()?;
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
let equality_proof: CiphertextCommitmentEqualityProof = self.equality_proof.try_into()?;
let range_proof: RangeProof = self.range_proof.try_into()?;
// verify equality proof
//
// TODO: we can also consider verifying equality and range proof in a batch
equality_proof.verify(pubkey, final_ciphertext, &commitment, transcript)?;
// verify range proof
//
// TODO: double compressing here - consider modifying range proof input type to `PedersenCommitment`
range_proof.verify(
vec![&commitment],
vec![WITHDRAW_AMOUNT_BIT_LENGTH],

View File

@ -1,260 +0,0 @@
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::PedersenOpening,
},
errors::ProofError,
sigma_proofs::equality_proof::CtxtCtxtEqualityProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// This struct includes the cryptographic proof *and* the account data information needed to verify
/// the proof
///
/// - The pre-instruction should call WithdrawWithheldTokensData::verify_proof(&self)
/// - The actual program should check that the ciphertext in this struct is consistent with what is
/// currently stored in the confidential token account
///
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawWithheldTokensData {
pub context: WithdrawWithheldTokensProofContext,
pub proof: WithdrawWithheldTokensProof,
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct WithdrawWithheldTokensProofContext {
pub withdraw_withheld_authority_pubkey: pod::ElGamalPubkey, // 32 bytes
pub destination_pubkey: pod::ElGamalPubkey, // 32 bytes
pub withdraw_withheld_authority_ciphertext: pod::ElGamalCiphertext, // 64 bytes
pub destination_ciphertext: pod::ElGamalCiphertext, // 64 bytes
}
#[cfg(not(target_os = "solana"))]
impl WithdrawWithheldTokensData {
pub fn new(
withdraw_withheld_authority_keypair: &ElGamalKeypair,
destination_pubkey: &ElGamalPubkey,
withdraw_withheld_authority_ciphertext: &ElGamalCiphertext,
amount: u64,
) -> Result<Self, ProofError> {
// encrypt withdraw amount under destination public key
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_pubkey.encrypt_with(amount, &destination_opening);
let pod_withdraw_withheld_authority_pubkey =
pod::ElGamalPubkey(withdraw_withheld_authority_keypair.public.to_bytes());
let pod_destination_pubkey = pod::ElGamalPubkey(destination_pubkey.to_bytes());
let pod_withdraw_withheld_authority_ciphertext =
pod::ElGamalCiphertext(withdraw_withheld_authority_ciphertext.to_bytes());
let pod_destination_ciphertext = pod::ElGamalCiphertext(destination_ciphertext.to_bytes());
let context = WithdrawWithheldTokensProofContext {
withdraw_withheld_authority_pubkey: pod_withdraw_withheld_authority_pubkey,
destination_pubkey: pod_destination_pubkey,
withdraw_withheld_authority_ciphertext: pod_withdraw_withheld_authority_ciphertext,
destination_ciphertext: pod_destination_ciphertext,
};
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
&pod_withdraw_withheld_authority_pubkey,
&pod_destination_pubkey,
&pod_withdraw_withheld_authority_ciphertext,
&pod_destination_ciphertext,
);
let proof = WithdrawWithheldTokensProof::new(
withdraw_withheld_authority_keypair,
destination_pubkey,
withdraw_withheld_authority_ciphertext,
amount,
&destination_opening,
&mut transcript,
);
Ok(Self { context, proof })
}
}
impl ZkProofData<WithdrawWithheldTokensProofContext> for WithdrawWithheldTokensData {
const PROOF_TYPE: ProofType = ProofType::WithdrawWithheldTokens;
fn context_data(&self) -> &WithdrawWithheldTokensProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = WithdrawWithheldTokensProof::transcript_new(
&self.context.withdraw_withheld_authority_pubkey,
&self.context.destination_pubkey,
&self.context.withdraw_withheld_authority_ciphertext,
&self.context.destination_ciphertext,
);
let withdraw_withheld_authority_pubkey =
self.context.withdraw_withheld_authority_pubkey.try_into()?;
let destination_pubkey = self.context.destination_pubkey.try_into()?;
let withdraw_withheld_authority_ciphertext = self
.context
.withdraw_withheld_authority_ciphertext
.try_into()?;
let destination_ciphertext = self.context.destination_ciphertext.try_into()?;
self.proof.verify(
&withdraw_withheld_authority_pubkey,
&destination_pubkey,
&withdraw_withheld_authority_ciphertext,
&destination_ciphertext,
&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_os = "solana"))]
impl WithdrawWithheldTokensProof {
fn transcript_new(
withdraw_withheld_authority_pubkey: &pod::ElGamalPubkey,
destination_pubkey: &pod::ElGamalPubkey,
withdraw_withheld_authority_ciphertext: &pod::ElGamalCiphertext,
destination_ciphertext: &pod::ElGamalCiphertext,
) -> Transcript {
let mut transcript = Transcript::new(b"WithdrawWithheldTokensProof");
transcript.append_pubkey(
b"withdraw-withheld-authority-pubkey",
withdraw_withheld_authority_pubkey,
);
transcript.append_pubkey(b"dest-pubkey", destination_pubkey);
transcript.append_ciphertext(
b"ciphertext-withdraw-withheld-authority",
withdraw_withheld_authority_ciphertext,
);
transcript.append_ciphertext(b"ciphertext-dest", destination_ciphertext);
transcript
}
pub fn new(
withdraw_withheld_authority_keypair: &ElGamalKeypair,
destination_pubkey: &ElGamalPubkey,
withdraw_withheld_authority_ciphertext: &ElGamalCiphertext,
amount: u64,
destination_opening: &PedersenOpening,
transcript: &mut Transcript,
) -> Self {
let equality_proof = CtxtCtxtEqualityProof::new(
withdraw_withheld_authority_keypair,
destination_pubkey,
withdraw_withheld_authority_ciphertext,
amount,
destination_opening,
transcript,
);
Self {
proof: equality_proof.into(),
}
}
pub fn verify(
&self,
source_pubkey: &ElGamalPubkey,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
destination_ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), ProofError> {
let proof: CtxtCtxtEqualityProof = self.proof.try_into()?;
proof.verify(
source_pubkey,
destination_pubkey,
source_ciphertext,
destination_ciphertext,
transcript,
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_withdraw_withheld() {
let withdraw_withheld_authority_keypair = ElGamalKeypair::new_rand();
let dest_keypair = ElGamalKeypair::new_rand();
let amount: u64 = 0;
let withdraw_withheld_authority_ciphertext =
withdraw_withheld_authority_keypair.public.encrypt(amount);
let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
&withdraw_withheld_authority_keypair,
&dest_keypair.public,
&withdraw_withheld_authority_ciphertext,
amount,
)
.unwrap();
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
let amount: u64 = 55;
let withdraw_withheld_authority_ciphertext =
withdraw_withheld_authority_keypair.public.encrypt(amount);
let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
&withdraw_withheld_authority_keypair,
&dest_keypair.public,
&withdraw_withheld_authority_ciphertext,
amount,
)
.unwrap();
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
let amount = u64::max_value();
let withdraw_withheld_authority_ciphertext =
withdraw_withheld_authority_keypair.public.encrypt(amount);
let withdraw_withheld_tokens_data = WithdrawWithheldTokensData::new(
&withdraw_withheld_authority_keypair,
&dest_keypair.public,
&withdraw_withheld_authority_ciphertext,
amount,
)
.unwrap();
assert!(withdraw_withheld_tokens_data.verify_proof().is_ok());
}
}

View File

@ -0,0 +1,127 @@
//! The zero-balance proof instruction.
//!
//! A zero-balance proof is defined with respect to a twisted ElGamal ciphertext. The proof
//! certifies that a given ciphertext encrypts the message 0 in the field (`Scalar::zero()`). To
//! generate the proof, a prover must provide the decryption key for the ciphertext.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair},
errors::ProofError,
sigma_proofs::zero_balance_proof::ZeroBalanceProof,
transcript::TranscriptProtocol,
},
merlin::Transcript,
std::convert::TryInto,
};
use {
crate::{
instruction::{ProofType, ZkProofData},
zk_token_elgamal::pod,
},
bytemuck::{Pod, Zeroable},
};
/// The instruction data that is needed for the `ProofInstruction::ZeroBalance` instruction.
///
/// It includes the cryptographic proof as well as the context data information needed to verify
/// the proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct ZeroBalanceProofData {
/// The context data for the zero-balance proof
pub context: ZeroBalanceProofContext, // 96 bytes
/// Proof that the source account available balance is zero
pub proof: pod::ZeroBalanceProof, // 96 bytes
}
/// The context data needed to verify a zero-balance proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct ZeroBalanceProofContext {
/// The source account ElGamal pubkey
pub pubkey: pod::ElGamalPubkey, // 32 bytes
/// The source account available balance in encrypted form
pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
}
#[cfg(not(target_os = "solana"))]
impl ZeroBalanceProofData {
pub fn new(
keypair: &ElGamalKeypair,
ciphertext: &ElGamalCiphertext,
) -> Result<Self, ProofError> {
let pod_pubkey = pod::ElGamalPubkey(keypair.public.to_bytes());
let pod_ciphertext = pod::ElGamalCiphertext(ciphertext.to_bytes());
let context = ZeroBalanceProofContext {
pubkey: pod_pubkey,
ciphertext: pod_ciphertext,
};
let mut transcript = ZeroBalanceProof::transcript_new(&pod_pubkey, &pod_ciphertext);
let proof = ZeroBalanceProof::new(keypair, ciphertext, &mut transcript).into();
Ok(ZeroBalanceProofData { context, proof })
}
}
impl ZkProofData<ZeroBalanceProofContext> for ZeroBalanceProofData {
const PROOF_TYPE: ProofType = ProofType::ZeroBalance;
fn context_data(&self) -> &ZeroBalanceProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript =
ZeroBalanceProof::transcript_new(&self.context.pubkey, &self.context.ciphertext);
let pubkey = self.context.pubkey.try_into()?;
let ciphertext = self.context.ciphertext.try_into()?;
let proof: ZeroBalanceProof = self.proof.try_into()?;
proof
.verify(&pubkey, &ciphertext, &mut transcript)
.map_err(|e| e.into())
}
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl ZeroBalanceProof {
fn transcript_new(
pubkey: &pod::ElGamalPubkey,
ciphertext: &pod::ElGamalCiphertext,
) -> Transcript {
let mut transcript = Transcript::new(b"ZeroBalanceProof");
transcript.append_pubkey(b"pubkey", pubkey);
transcript.append_ciphertext(b"ciphertext", ciphertext);
transcript
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_zero_balance_proof_instruction_correctness() {
let keypair = ElGamalKeypair::new_rand();
// general case: encryption of 0
let ciphertext = keypair.public.encrypt(0_u64);
let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap();
assert!(zero_balance_proof_data.verify_proof().is_ok());
// general case: encryption of > 0
let ciphertext = keypair.public.encrypt(1_u64);
let zero_balance_proof_data = ZeroBalanceProofData::new(&keypair, &ciphertext).unwrap();
assert!(zero_balance_proof_data.verify_proof().is_err());
}
}

View File

@ -1,11 +1,9 @@
//! The equality sigma proof system.
//! The ciphertext-commitment equality sigma proof system.
//!
//! An equality proof is defined with respect to two cryptographic objects: a twisted ElGamal
//! ciphertext and a Pedersen commitment. The proof certifies that a given ciphertext and
//! commitment pair encrypts/encodes the same message. To generate the proof, a prover must provide
//! the decryption key for the ciphertext and the Pedersen opening for the commitment.
//!
//! TODO: verify with respect to ciphertext
//! A ciphertext-commitment equality proof is defined with respect to a twisted ElGamal ciphertext
//! and a Pedersen commitment. The proof certifies that a given ciphertext and a commitment pair
//! encrypts/encodes the same message. To generate the proof, a prover must provide the decryption
//! key for the first ciphertext and the Pedersen opening for the commitment.
//!
//! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect
//! zero-knowledge in the random oracle model.
@ -39,7 +37,7 @@ use {
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct CtxtCommEqualityProof {
pub struct CiphertextCommitmentEqualityProof {
Y_0: CompressedRistretto,
Y_1: CompressedRistretto,
Y_2: CompressedRistretto,
@ -50,7 +48,7 @@ pub struct CtxtCommEqualityProof {
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl CtxtCommEqualityProof {
impl CiphertextCommitmentEqualityProof {
/// Equality proof constructor. The proof is with respect to a ciphertext and commitment.
///
/// The function does *not* hash the public key, ciphertext, or commitment into the transcript.
@ -70,8 +68,8 @@ impl CtxtCommEqualityProof {
pub fn new(
source_keypair: &ElGamalKeypair,
source_ciphertext: &ElGamalCiphertext,
amount: u64,
opening: &PedersenOpening,
amount: u64,
transcript: &mut Transcript,
) -> Self {
transcript.equality_proof_domain_sep();
@ -112,7 +110,7 @@ impl CtxtCommEqualityProof {
y_x.zeroize();
y_r.zeroize();
CtxtCommEqualityProof {
CiphertextCommitmentEqualityProof {
Y_0,
Y_1,
Y_2,
@ -235,7 +233,7 @@ impl CtxtCommEqualityProof {
let z_r =
Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?;
Ok(CtxtCommEqualityProof {
Ok(CiphertextCommitmentEqualityProof {
Y_0,
Y_1,
Y_2,
@ -246,246 +244,6 @@ impl CtxtCommEqualityProof {
}
}
/// 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_os = "solana"))]
impl CtxtCtxtEqualityProof {
/// Equality proof constructor. The proof is with respect to two ciphertexts.
///
/// 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.
///
/// * `source_keypair` - The ElGamal keypair associated with the first ciphertext to be proved
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ElGamal ciphertext
/// * `source_ciphertext` - The first ElGamal ciphertext
/// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment
/// * `destination_opening` - The opening associated with the second ElGamal ciphertext
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn new(
source_keypair: &ElGamalKeypair,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
amount: u64,
destination_opening: &PedersenOpening,
transcript: &mut Transcript,
) -> Self {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_source = source_keypair.public.get_point();
let D_source = source_ciphertext.handle.get_point();
let P_destination = destination_pubkey.get_point();
let s = source_keypair.secret.get_scalar();
let x = Scalar::from(amount);
let r = destination_opening.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_destination).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. The proof is with respect to two ciphertexts.
///
/// * `source_pubkey` - The ElGamal pubkey associated with the first ciphertext to be proved
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ciphertext to be proved
/// * `source_ciphertext` - The first ElGamal ciphertext to be proved
/// * `destination_ciphertext` - The second ElGamal ciphertext to be proved
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn verify(
self,
source_pubkey: &ElGamalPubkey,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
destination_ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), EqualityProofError> {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_source = source_pubkey.get_point();
let C_source = source_ciphertext.commitment.get_point();
let D_source = source_ciphertext.handle.get_point();
let P_destination = destination_pubkey.get_point();
let C_destination = destination_ciphertext.commitment.get_point();
let D_destination = destination_ciphertext.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(ProofVerificationError::Deserialization)?;
let Y_1 = self
.Y_1
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
let Y_2 = self
.Y_2
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
let Y_3 = self
.Y_3
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
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_destination, // C_destination
&Y_2, // Y_2
P_destination, // P_destination
D_destination, // D_destination
&Y_3, // Y_3
],
);
if check.is_identity() {
Ok(())
} else {
Err(ProofVerificationError::AlgebraicRelation.into())
}
}
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> {
if bytes.len() != 224 {
return Err(ProofVerificationError::Deserialization.into());
}
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(ProofVerificationError::Deserialization)?;
let z_x =
Scalar::from_canonical_bytes(*z_x).ok_or(ProofVerificationError::Deserialization)?;
let z_r =
Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?;
Ok(CtxtCtxtEqualityProof {
Y_0,
Y_1,
Y_2,
Y_3,
z_s,
z_x,
z_r,
})
}
}
#[cfg(test)]
mod test {
use {
@ -505,11 +263,11 @@ mod test {
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCommEqualityProof::new(
let proof = CiphertextCommitmentEqualityProof::new(
&source_keypair,
&source_ciphertext,
message,
&destination_opening,
message,
&mut prover_transcript,
);
@ -533,11 +291,11 @@ mod test {
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCommEqualityProof::new(
let proof = CiphertextCommitmentEqualityProof::new(
&source_keypair,
&source_ciphertext,
message,
&destination_opening,
message,
&mut prover_transcript,
);
@ -566,11 +324,11 @@ mod test {
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCommEqualityProof::new(
let proof = CiphertextCommitmentEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
&opening,
message,
&mut prover_transcript,
);
@ -595,11 +353,11 @@ mod test {
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCommEqualityProof::new(
let proof = CiphertextCommitmentEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
&opening,
message,
&mut prover_transcript,
);
@ -624,11 +382,11 @@ mod test {
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCommEqualityProof::new(
let proof = CiphertextCommitmentEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
&opening,
message,
&mut prover_transcript,
);
@ -652,11 +410,11 @@ mod test {
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCommEqualityProof::new(
let proof = CiphertextCommitmentEqualityProof::new(
&elgamal_keypair,
&ciphertext,
message,
&opening,
message,
&mut prover_transcript,
);
@ -669,74 +427,4 @@ mod test {
)
.is_ok());
}
#[test]
fn test_ciphertext_ciphertext_equality_proof_correctness() {
// success case
let source_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();
let message: u64 = 55;
let source_ciphertext = source_keypair.public.encrypt(message);
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(message, &destination_opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCtxtEqualityProof::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
message,
&destination_opening,
&mut prover_transcript,
);
assert!(proof
.verify(
&source_keypair.public,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&mut verifier_transcript
)
.is_ok());
// fail case: encrypted and committed messages are different
let source_message: u64 = 55;
let destination_message: u64 = 77;
let source_ciphertext = source_keypair.public.encrypt(source_message);
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(destination_message, &destination_opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CtxtCtxtEqualityProof::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
message,
&destination_opening,
&mut prover_transcript,
);
assert!(proof
.verify(
&source_keypair.public,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&mut verifier_transcript
)
.is_err());
}
}

View File

@ -0,0 +1,342 @@
//! The ciphertext-ciphertext equality sigma proof system.
//!
//! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
//! zero-knowledge in the random oracle model.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
pedersen::{PedersenOpening, G, H},
},
errors::ProofVerificationError,
},
curve25519_dalek::traits::MultiscalarMul,
rand::rngs::OsRng,
zeroize::Zeroize,
};
use {
crate::{sigma_proofs::errors::EqualityProofError, transcript::TranscriptProtocol},
arrayref::{array_ref, array_refs},
curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::{IsIdentity, VartimeMultiscalarMul},
},
merlin::Transcript,
};
/// The ciphertext-ciphertext equality proof.
///
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct CiphertextCiphertextEqualityProof {
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_os = "solana"))]
impl CiphertextCiphertextEqualityProof {
/// The ciphertext-ciphertext 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.
///
/// * `source_keypair` - The ElGamal keypair associated with the first ciphertext to be proved
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ElGamal ciphertext
/// * `source_ciphertext` - The first ElGamal ciphertext for which the prover knows a
/// decryption key for
/// * `destination_opening` - The opening (randomness) associated with the second ElGamal ciphertext
/// * `amount` - The message associated with the ElGamal ciphertext and Pedersen commitment
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn new(
source_keypair: &ElGamalKeypair,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
destination_opening: &PedersenOpening,
amount: u64,
transcript: &mut Transcript,
) -> Self {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_source = source_keypair.public.get_point();
let D_source = source_ciphertext.handle.get_point();
let P_destination = destination_pubkey.get_point();
let s = source_keypair.secret.get_scalar();
let x = Scalar::from(amount);
let r = destination_opening.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_destination).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();
CiphertextCiphertextEqualityProof {
Y_0,
Y_1,
Y_2,
Y_3,
z_s,
z_x,
z_r,
}
}
/// The ciphertext-ciphertext equality proof verifier. The proof is with respect to two
/// ciphertexts.
///
/// * `source_pubkey` - The ElGamal pubkey associated with the first ciphertext to be proved
/// * `destination_pubkey` - The ElGamal pubkey associated with the second ciphertext to be proved
/// * `source_ciphertext` - The first ElGamal ciphertext to be proved
/// * `destination_ciphertext` - The second ElGamal ciphertext to be proved
/// * `transcript` - The transcript that does the bookkeeping for the Fiat-Shamir heuristic
pub fn verify(
self,
source_pubkey: &ElGamalPubkey,
destination_pubkey: &ElGamalPubkey,
source_ciphertext: &ElGamalCiphertext,
destination_ciphertext: &ElGamalCiphertext,
transcript: &mut Transcript,
) -> Result<(), EqualityProofError> {
transcript.equality_proof_domain_sep();
// extract the relevant scalar and Ristretto points from the inputs
let P_source = source_pubkey.get_point();
let C_source = source_ciphertext.commitment.get_point();
let D_source = source_ciphertext.handle.get_point();
let P_destination = destination_pubkey.get_point();
let C_destination = destination_ciphertext.commitment.get_point();
let D_destination = destination_ciphertext.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(ProofVerificationError::Deserialization)?;
let Y_1 = self
.Y_1
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
let Y_2 = self
.Y_2
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
let Y_3 = self
.Y_3
.decompress()
.ok_or(ProofVerificationError::Deserialization)?;
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_destination, // C_destination
&Y_2, // Y_2
P_destination, // P_destination
D_destination, // D_destination
&Y_3, // Y_3
],
);
if check.is_identity() {
Ok(())
} else {
Err(ProofVerificationError::AlgebraicRelation.into())
}
}
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> {
if bytes.len() != 224 {
return Err(ProofVerificationError::Deserialization.into());
}
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(ProofVerificationError::Deserialization)?;
let z_x =
Scalar::from_canonical_bytes(*z_x).ok_or(ProofVerificationError::Deserialization)?;
let z_r =
Scalar::from_canonical_bytes(*z_r).ok_or(ProofVerificationError::Deserialization)?;
Ok(CiphertextCiphertextEqualityProof {
Y_0,
Y_1,
Y_2,
Y_3,
z_s,
z_x,
z_r,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_ciphertext_ciphertext_equality_proof_correctness() {
// success case
let source_keypair = ElGamalKeypair::new_rand();
let destination_keypair = ElGamalKeypair::new_rand();
let message: u64 = 55;
let source_ciphertext = source_keypair.public.encrypt(message);
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(message, &destination_opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CiphertextCiphertextEqualityProof::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_opening,
message,
&mut prover_transcript,
);
assert!(proof
.verify(
&source_keypair.public,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&mut verifier_transcript
)
.is_ok());
// fail case: encrypted and committed messages are different
let source_message: u64 = 55;
let destination_message: u64 = 77;
let source_ciphertext = source_keypair.public.encrypt(source_message);
let destination_opening = PedersenOpening::new_rand();
let destination_ciphertext = destination_keypair
.public
.encrypt_with(destination_message, &destination_opening);
let mut prover_transcript = Transcript::new(b"Test");
let mut verifier_transcript = Transcript::new(b"Test");
let proof = CiphertextCiphertextEqualityProof::new(
&source_keypair,
&destination_keypair.public,
&source_ciphertext,
&destination_opening,
message,
&mut prover_transcript,
);
assert!(proof
.verify(
&source_keypair.public,
&destination_keypair.public,
&source_ciphertext,
&destination_ciphertext,
&mut verifier_transcript
)
.is_err());
}
}

View File

@ -15,7 +15,8 @@
//! We refer to the zk-token paper for the formal details and security proofs of these argument
//! systems.
pub mod equality_proof;
pub mod ctxt_comm_equality_proof;
pub mod ctxt_ctxt_equality_proof;
pub mod errors;
pub mod fee_proof;
pub mod pubkey_proof;

View File

@ -1,9 +1,5 @@
//! The public-key (validity) proof system.
//!
//! A public-key proof is defined with respect to an ElGamal public key. The proof certifies that a
//! given public key is a valid ElGamal public key (i.e. the prover knows a corresponding secret
//! key). To generate the proof, a prover must prove the secret key for the public key.
//!
//! The protocol guarantees computational soundness (by the hardness of discrete log) and perfect
//! zero-knowledge in the random oracle model.
@ -35,14 +31,14 @@ use {
/// Contains all the elliptic curve and scalar components that make up the sigma protocol.
#[allow(non_snake_case)]
#[derive(Clone)]
pub struct PubkeySigmaProof {
pub struct PubkeyValidityProof {
Y: CompressedRistretto,
z: Scalar,
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl PubkeySigmaProof {
impl PubkeyValidityProof {
/// Public-key proof constructor.
///
/// The function does *not* hash the public key and ciphertext into the transcript. For
@ -136,7 +132,7 @@ impl PubkeySigmaProof {
let Y = CompressedRistretto::from_slice(Y);
let z = Scalar::from_canonical_bytes(*z).ok_or(ProofVerificationError::Deserialization)?;
Ok(PubkeySigmaProof { Y, z })
Ok(PubkeyValidityProof { Y, z })
}
}
@ -155,7 +151,7 @@ mod test {
let mut prover_transcript = Transcript::new(b"test");
let mut verifier_transcript = Transcript::new(b"test");
let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript);
let proof = PubkeyValidityProof::new(&keypair, &mut prover_transcript);
assert!(proof
.verify(&keypair.public, &mut verifier_transcript)
.is_ok());
@ -166,7 +162,7 @@ mod test {
let mut prover_transcript = Transcript::new(b"test");
let mut verifier_transcript = Transcript::new(b"test");
let proof = PubkeySigmaProof::new(&keypair, &mut prover_transcript);
let proof = PubkeyValidityProof::new(&keypair, &mut prover_transcript);
assert!(proof
.verify(&keypair.public, &mut verifier_transcript)
.is_ok());

View File

@ -1,9 +1,5 @@
//! The zero-balance sigma proof system.
//!
//! A zero-balance proof is defined with respect to a twisted ElGamal ciphertext. The proof
//! certifies that a given ciphertext encrypts the message 0 (`Scalar::zero()`). To generate the
//! proof, a prover must provide the decryption key for the ciphertext.
//!
//! The protocol guarantees computationally soundness (by the hardness of discrete log) and perfect
//! zero-knowledge in the random oracle model.

View File

@ -64,10 +64,11 @@ mod target_arch {
},
range_proof::{errors::RangeProofError, RangeProof},
sigma_proofs::{
equality_proof::{CtxtCommEqualityProof, CtxtCtxtEqualityProof},
ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof,
ctxt_ctxt_equality_proof::CiphertextCiphertextEqualityProof,
errors::*,
fee_proof::FeeSigmaProof,
pubkey_proof::PubkeySigmaProof,
pubkey_proof::PubkeyValidityProof,
validity_proof::{AggregatedValidityProof, ValidityProof},
zero_balance_proof::ZeroBalanceProof,
},
@ -191,30 +192,30 @@ mod target_arch {
}
}
impl From<CtxtCommEqualityProof> for pod::CtxtCommEqualityProof {
fn from(proof: CtxtCommEqualityProof) -> Self {
impl From<CiphertextCommitmentEqualityProof> for pod::CiphertextCommitmentEqualityProof {
fn from(proof: CiphertextCommitmentEqualityProof) -> Self {
Self(proof.to_bytes())
}
}
impl TryFrom<pod::CtxtCommEqualityProof> for CtxtCommEqualityProof {
impl TryFrom<pod::CiphertextCommitmentEqualityProof> for CiphertextCommitmentEqualityProof {
type Error = EqualityProofError;
fn try_from(pod: pod::CtxtCommEqualityProof) -> Result<Self, Self::Error> {
fn try_from(pod: pod::CiphertextCommitmentEqualityProof) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
impl From<CtxtCtxtEqualityProof> for pod::CtxtCtxtEqualityProof {
fn from(proof: CtxtCtxtEqualityProof) -> Self {
impl From<CiphertextCiphertextEqualityProof> for pod::CiphertextCiphertextEqualityProof {
fn from(proof: CiphertextCiphertextEqualityProof) -> Self {
Self(proof.to_bytes())
}
}
impl TryFrom<pod::CtxtCtxtEqualityProof> for CtxtCtxtEqualityProof {
impl TryFrom<pod::CiphertextCiphertextEqualityProof> for CiphertextCiphertextEqualityProof {
type Error = EqualityProofError;
fn try_from(pod: pod::CtxtCtxtEqualityProof) -> Result<Self, Self::Error> {
fn try_from(pod: pod::CiphertextCiphertextEqualityProof) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}
@ -275,16 +276,16 @@ mod target_arch {
}
}
impl From<PubkeySigmaProof> for pod::PubkeySigmaProof {
fn from(proof: PubkeySigmaProof) -> Self {
impl From<PubkeyValidityProof> for pod::PubkeyValidityProof {
fn from(proof: PubkeyValidityProof) -> Self {
Self(proof.to_bytes())
}
}
impl TryFrom<pod::PubkeySigmaProof> for PubkeySigmaProof {
impl TryFrom<pod::PubkeyValidityProof> for PubkeyValidityProof {
type Error = PubkeyValidityProofError;
fn try_from(pod: pod::PubkeySigmaProof) -> Result<Self, Self::Error> {
fn try_from(pod: pod::PubkeyValidityProof) -> Result<Self, Self::Error> {
Self::from_bytes(&pod.0)
}
}

View File

@ -113,25 +113,25 @@ impl fmt::Debug for DecryptHandle {
}
}
/// Serialization of `CtxtCommEqualityProof`
/// Serialization of `CiphertextCommitmentEqualityProof`
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct CtxtCommEqualityProof(pub [u8; 192]);
pub struct CiphertextCommitmentEqualityProof(pub [u8; 192]);
// `CtxtCommEqualityProof` is a Pod and Zeroable.
// `CiphertextCommitmentEqualityProof` is a Pod and Zeroable.
// Add the marker traits manually because `bytemuck` only adds them for some `u8` arrays
unsafe impl Zeroable for CtxtCommEqualityProof {}
unsafe impl Pod for CtxtCommEqualityProof {}
unsafe impl Zeroable for CiphertextCommitmentEqualityProof {}
unsafe impl Pod for CiphertextCommitmentEqualityProof {}
/// Serialization of `CtxtCtxtEqualityProof`
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct CtxtCtxtEqualityProof(pub [u8; 224]);
pub struct CiphertextCiphertextEqualityProof(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 {}
unsafe impl Zeroable for CiphertextCiphertextEqualityProof {}
unsafe impl Pod for CiphertextCiphertextEqualityProof {}
/// Serialization of validity proofs
#[derive(Clone, Copy)]
@ -171,7 +171,7 @@ pub struct FeeSigmaProof(pub [u8; 256]);
/// Serialization of public-key sigma proof
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(transparent)]
pub struct PubkeySigmaProof(pub [u8; 64]);
pub struct PubkeyValidityProof(pub [u8; 64]);
/// Serialization of range proofs for 64-bit numbers (for `Withdraw` instruction)
#[derive(Clone, Copy)]

View File

@ -25,7 +25,7 @@ pub enum ProofInstruction {
///
CloseContextState,
/// Verify a close account zero-knowledge proof.
/// Verify a zero-balance proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
@ -39,9 +39,9 @@ pub enum ProofInstruction {
/// None
///
/// Data expected by this instruction:
/// `CloseAccountData`
/// `ZeroBalanceProofData`
///
VerifyCloseAccount,
VerifyZeroBalance,
/// Verify a withdraw zero-knowledge proof.
///
@ -61,7 +61,7 @@ pub enum ProofInstruction {
///
VerifyWithdraw,
/// Verify a withdraw withheld tokens zero-knowledge proof.
/// Verify a ciphertext-ciphertext equality proof.
///
/// This instruction can be configured to optionally create a proof context state account.
///
@ -75,9 +75,9 @@ pub enum ProofInstruction {
/// None
///
/// Data expected by this instruction:
/// `WithdrawWithheldTokensData`
/// `CiphertextCiphertextEqualityProofData`
///
VerifyWithdrawWithheldTokens,
VerifyCiphertextCiphertextEquality,
/// Verify a transfer zero-knowledge proof.
///
@ -161,12 +161,12 @@ pub fn close_context_state(
}
}
/// Create a `VerifyCloseAccount` instruction.
pub fn verify_close_account(
/// Create a `VerifyZeroBalance` instruction.
pub fn verify_zero_balance(
context_state_info: Option<ContextStateInfo>,
proof_data: &CloseAccountData,
proof_data: &ZeroBalanceProofData,
) -> Instruction {
ProofInstruction::VerifyCloseAccount.encode_verify_proof(context_state_info, proof_data)
ProofInstruction::VerifyZeroBalance.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyWithdraw` instruction.
@ -177,12 +177,12 @@ pub fn verify_withdraw(
ProofInstruction::VerifyWithdraw.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyWithdrawWithheldTokens` instruction.
pub fn verify_withdraw_withheld_tokens(
/// Create a `VerifyCiphertextCiphertextEquality` instruction.
pub fn verify_ciphertext_ciphertext_equality(
context_state_info: Option<ContextStateInfo>,
proof_data: &WithdrawWithheldTokensData,
proof_data: &CiphertextCiphertextEqualityProofData,
) -> Instruction {
ProofInstruction::VerifyWithdrawWithheldTokens
ProofInstruction::VerifyCiphertextCiphertextEquality
.encode_verify_proof(context_state_info, proof_data)
}