[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:
parent
93efc13bbf
commit
2ebf38efd0
|
@ -21,7 +21,7 @@ use {
|
||||||
std::mem::size_of,
|
std::mem::size_of,
|
||||||
};
|
};
|
||||||
|
|
||||||
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 10] = [
|
const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 11] = [
|
||||||
ProofInstruction::VerifyZeroBalance,
|
ProofInstruction::VerifyZeroBalance,
|
||||||
ProofInstruction::VerifyWithdraw,
|
ProofInstruction::VerifyWithdraw,
|
||||||
ProofInstruction::VerifyCiphertextCiphertextEquality,
|
ProofInstruction::VerifyCiphertextCiphertextEquality,
|
||||||
|
@ -32,6 +32,7 @@ const VERIFY_INSTRUCTION_TYPES: [ProofInstruction; 10] = [
|
||||||
ProofInstruction::VerifyBatchedRangeProofU64,
|
ProofInstruction::VerifyBatchedRangeProofU64,
|
||||||
ProofInstruction::VerifyBatchedRangeProofU128,
|
ProofInstruction::VerifyBatchedRangeProofU128,
|
||||||
ProofInstruction::VerifyBatchedRangeProofU256,
|
ProofInstruction::VerifyBatchedRangeProofU256,
|
||||||
|
ProofInstruction::VerifyCiphertextCommitmentEquality,
|
||||||
];
|
];
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -518,6 +519,59 @@ async fn test_batched_range_proof_u256() {
|
||||||
.await;
|
.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>(
|
async fn test_verify_proof_without_context<T, U>(
|
||||||
proof_instruction: ProofInstruction,
|
proof_instruction: ProofInstruction,
|
||||||
success_proof_data: &T,
|
success_proof_data: &T,
|
||||||
|
|
|
@ -234,5 +234,15 @@ declare_process_instruction!(process_instruction, 0, |invoke_context| {
|
||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod batched_range_proof;
|
pub mod batched_range_proof;
|
||||||
|
pub mod ciphertext_commitment_equality;
|
||||||
pub mod ctxt_ctxt_equality;
|
pub mod ctxt_ctxt_equality;
|
||||||
pub mod pubkey_validity;
|
pub mod pubkey_validity;
|
||||||
pub mod range_proof;
|
pub mod range_proof;
|
||||||
|
@ -26,6 +27,9 @@ pub use {
|
||||||
batched_range_proof_u64::BatchedRangeProofU64Data, BatchedRangeProofContext,
|
batched_range_proof_u64::BatchedRangeProofU64Data, BatchedRangeProofContext,
|
||||||
},
|
},
|
||||||
bytemuck::Pod,
|
bytemuck::Pod,
|
||||||
|
ciphertext_commitment_equality::{
|
||||||
|
CiphertextCommitmentEqualityProofContext, CiphertextCommitmentEqualityProofData,
|
||||||
|
},
|
||||||
ctxt_ctxt_equality::{
|
ctxt_ctxt_equality::{
|
||||||
CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
|
CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData,
|
||||||
},
|
},
|
||||||
|
@ -52,6 +56,7 @@ pub enum ProofType {
|
||||||
BatchedRangeProofU64,
|
BatchedRangeProofU64,
|
||||||
BatchedRangeProofU128,
|
BatchedRangeProofU128,
|
||||||
BatchedRangeProofU256,
|
BatchedRangeProofU256,
|
||||||
|
CiphertextCommitmentEquality,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ZkProofData<T: Pod> {
|
pub trait ZkProofData<T: Pod> {
|
||||||
|
|
|
@ -255,6 +255,27 @@ pub enum ProofInstruction {
|
||||||
/// `BatchedRangeProof256Data`
|
/// `BatchedRangeProof256Data`
|
||||||
///
|
///
|
||||||
VerifyBatchedRangeProofU256,
|
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.
|
/// 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)
|
.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 {
|
impl ProofInstruction {
|
||||||
pub fn encode_verify_proof<T, U>(
|
pub fn encode_verify_proof<T, U>(
|
||||||
&self,
|
&self,
|
||||||
|
|
Loading…
Reference in New Issue