[zk-token-sdk] Add ciphertext-commitment equality proof instruction (#31808)

* add ciphertext-commitment proof data

* add ciphertext-commitment proof instruction

* update proof program processor for ciphertext-commitment equality proof

* cargo fmt

* update compute units

* rename submodule `ctxt_comm_equality` to `ciphertext_commitment_equality`

* update import statements

* fix mixed conflict

* remove `native_programs_consume_cu`
This commit is contained in:
samkim-crypto 2023-05-27 11:25:29 +09:00 committed by GitHub
parent 93efc13bbf
commit 2ebf38efd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 245 additions and 1 deletions

View File

@ -21,7 +21,7 @@ use {
std::mem::size_of,
};
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 10] = [
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 11] = [
ProofInstruction::VerifyZeroBalance,
ProofInstruction::VerifyWithdraw,
ProofInstruction::VerifyCiphertextCiphertextEquality,
@ -32,6 +32,7 @@ const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 10] = [
ProofInstruction::VerifyBatchedRangeProofU64,
ProofInstruction::VerifyBatchedRangeProofU128,
ProofInstruction::VerifyBatchedRangeProofU256,
ProofInstruction::VerifyCiphertextCommitmentEquality,
];
#[tokio::test]
@ -518,6 +519,59 @@ async fn test_batched_range_proof_u256() {
.await;
}
#[tokio::test]
async fn test_ciphertext_commitment_equality() {
let keypair = ElGamalKeypair::new_rand();
let amount: u64 = 55;
let ciphertext = keypair.public.encrypt(amount);
let (commitment, opening) = Pedersen::new(amount);
let success_proof_data = CiphertextCommitmentEqualityProofData::new(
&keypair,
&ciphertext,
&commitment,
&opening,
amount,
)
.unwrap();
let incorrect_keypair = ElGamalKeypair {
public: ElGamalKeypair::new_rand().public,
secret: ElGamalKeypair::new_rand().secret,
};
let fail_proof_data = CiphertextCommitmentEqualityProofData::new(
&incorrect_keypair,
&ciphertext,
&commitment,
&opening,
amount,
)
.unwrap();
test_verify_proof_without_context(
ProofInstruction::VerifyCiphertextCommitmentEquality,
&success_proof_data,
&fail_proof_data,
)
.await;
test_verify_proof_with_context(
ProofInstruction::VerifyCiphertextCommitmentEquality,
size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>(),
&success_proof_data,
&fail_proof_data,
)
.await;
test_close_context_state(
ProofInstruction::VerifyCiphertextCommitmentEquality,
size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>(),
&success_proof_data,
)
.await;
}
async fn test_verify_proof_without_context<T, U>(
proof_instruction: ProofInstruction,
success_proof_data: &T,

View File

@ -234,5 +234,15 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
invoke_context,
)
}
ProofInstruction::VerifyCiphertextCommitmentEquality => {
invoke_context
.consume_checked(6_424)
.map_err(|_| InstructionError::ComputationalBudgetExceeded)?;
ic_msg!(invoke_context, "VerifyCiphertextCommitmentEquality");
process_verify_proof::<
CiphertextCommitmentEqualityProofData,
CiphertextCommitmentEqualityProofContext,
>(invoke_context)
}
}
});

View File

@ -0,0 +1,145 @@
//! The ciphertext-commitment equality proof instruction.
//!
//! 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.
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::{
elgamal::{ElGamalCiphertext, ElGamalKeypair},
pedersen::{PedersenCommitment, PedersenOpening},
},
errors::ProofError,
sigma_proofs::ctxt_comm_equality_proof::CiphertextCommitmentEqualityProof,
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::VerifyCiphertextCommitmentEquality` 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 CiphertextCommitmentEqualityProofData {
pub context: CiphertextCommitmentEqualityProofContext,
pub proof: pod::CiphertextCommitmentEqualityProof,
}
/// The context data needed to verify a ciphertext-commitment equality proof.
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct CiphertextCommitmentEqualityProofContext {
/// The ElGamal pubkey
pub pubkey: pod::ElGamalPubkey, // 32 bytes
/// The ciphertext encrypted under the ElGamal pubkey
pub ciphertext: pod::ElGamalCiphertext, // 64 bytes
/// The Pedersen commitment
pub commitment: pod::PedersenCommitment, // 32 bytes
}
#[cfg(not(target_os = "solana"))]
impl CiphertextCommitmentEqualityProofData {
pub fn new(
keypair: &ElGamalKeypair,
ciphertext: &ElGamalCiphertext,
commitment: &PedersenCommitment,
opening: &PedersenOpening,
amount: u64,
) -> Result<Self, ProofError> {
let context = CiphertextCommitmentEqualityProofContext {
pubkey: pod::ElGamalPubkey(keypair.public.to_bytes()),
ciphertext: pod::ElGamalCiphertext(ciphertext.to_bytes()),
commitment: pod::PedersenCommitment(commitment.to_bytes()),
};
let mut transcript = context.new_transcript();
let proof = CiphertextCommitmentEqualityProof::new(
keypair,
ciphertext,
opening,
amount,
&mut transcript,
);
Ok(CiphertextCommitmentEqualityProofData {
context,
proof: proof.into(),
})
}
}
impl ZkProofData<CiphertextCommitmentEqualityProofContext>
for CiphertextCommitmentEqualityProofData
{
const PROOF_TYPE: ProofType = ProofType::CiphertextCommitmentEquality;
fn context_data(&self) -> &CiphertextCommitmentEqualityProofContext {
&self.context
}
#[cfg(not(target_os = "solana"))]
fn verify_proof(&self) -> Result<(), ProofError> {
let mut transcript = self.context.new_transcript();
let pubkey = self.context.pubkey.try_into()?;
let ciphertext = self.context.ciphertext.try_into()?;
let commitment = self.context.commitment.try_into()?;
let proof: CiphertextCommitmentEqualityProof = self.proof.try_into()?;
proof
.verify(&pubkey, &ciphertext, &commitment, &mut transcript)
.map_err(|e| e.into())
}
}
#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl CiphertextCommitmentEqualityProofContext {
fn new_transcript(&self) -> Transcript {
let mut transcript = Transcript::new(b"CtxtCommEqualityProof");
transcript.append_pubkey(b"pubkey", &self.pubkey);
transcript.append_ciphertext(b"ciphertext", &self.ciphertext);
transcript.append_commitment(b"commitment", &self.commitment);
transcript
}
}
#[cfg(test)]
mod test {
use {
super::*,
crate::encryption::{elgamal::ElGamalKeypair, pedersen::Pedersen},
};
#[test]
fn test_ctxt_comm_equality_proof_correctness() {
let keypair = ElGamalKeypair::new_rand();
let amount: u64 = 55;
let ciphertext = keypair.public.encrypt(amount);
let (commitment, opening) = Pedersen::new(amount);
let proof_data = CiphertextCommitmentEqualityProofData::new(
&keypair,
&ciphertext,
&commitment,
&opening,
amount,
)
.unwrap();
assert!(proof_data.verify_proof().is_ok());
}
}

View File

@ -1,4 +1,5 @@
pub mod batched_range_proof;
pub mod ciphertext_commitment_equality;
pub mod ctxt_ctxt_equality;
pub mod pubkey_validity;
pub mod range_proof;
@ -26,6 +27,9 @@ pub use {
batched_range_proof_u64::BatchedRangeProofU64Data, BatchedRangeProofContext,
},
bytemuck::Pod,
ciphertext_commitment_equality::{
CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData,
},
ctxt_ctxt_equality::{
CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
},
@ -52,6 +56,7 @@ pub enum ProofType {
BatchedRangeProofU64,
BatchedRangeProofU128,
BatchedRangeProofU256,
CiphertextCommitmentEquality,
}
pub trait ZkProofData<T: Pod> {

View File

@ -255,6 +255,27 @@ pub enum ProofInstruction {
/// `BatchedRangeProof256Data`
///
VerifyBatchedRangeProofU256,
/// Verify a ciphertext-commitment equality proof.
///
/// This instruction can be configured to optionally initialize a proof context state account.
/// If creating a context state account, an account must be pre-allocated to the exact size of
/// `ProofContextState<CiphertextCommitmentEqualityProofContext>` and assigned to the ZkToken
/// proof program prior to the execution of this instruction.
///
/// Accounts expected by this instruction:
///
/// * Creating a proof context account
/// 0. `[writable]` The proof context account
/// 1. `[]` The proof context account owner
///
/// * Otherwise
/// None
///
/// Data expected by this instruction:
/// `CiphertextCommitmentEqualityProofData`
///
VerifyCiphertextCommitmentEquality,
}
/// Pubkeys associated with a context state account to be used as parameters to functions.
@ -367,6 +388,15 @@ pub fn verify_batched_verify_range_proof_u256(
.encode_verify_proof(context_state_info, proof_data)
}
/// Create a `VerifyCiphertextCommitmentEquality` instruction.
pub fn verify_ciphertext_commitment_equality(
context_state_info: Option<ContextStateInfo>,
proof_data: &PubkeyValidityData,
) -> Instruction {
ProofInstruction::VerifyCiphertextCommitmentEquality
.encode_verify_proof(context_state_info, proof_data)
}
impl ProofInstruction {
pub fn encode_verify_proof<T, U>(
&self,