forked from anza-xyz/agave
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[zk-sdk] Add error types and zero ciphertext instruction (anza-xyz#1453)
* add `ProofGenerationError` and `ProofVerificationError` * add `zero_ciphertext` proof data module from zk-token-sdk verbatim * clean up `zero_ciphertext` proof data module * add `VerifyZeroCiphertext` instruction * cargo fmt * remove `ProofGenerationError::FeeCalculation` * remove `ProofGenerationError::NotEnoughFunds`
- Loading branch information
1 parent
32cdbd6
commit 9035690
Showing
4 changed files
with
263 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,50 @@ | ||
use thiserror::Error; | ||
use { | ||
crate::{ | ||
errors::ElGamalError, | ||
range_proof::errors::{RangeProofGenerationError, RangeProofVerificationError}, | ||
sigma_proofs::errors::*, | ||
}, | ||
thiserror::Error, | ||
}; | ||
|
||
#[cfg(not(target_os = "solana"))] | ||
#[derive(Error, Clone, Debug, Eq, PartialEq)] | ||
pub enum ProofGenerationError { | ||
#[error("illegal number of commitments")] | ||
IllegalCommitmentLength, | ||
#[error("illegal amount bit length")] | ||
IllegalAmountBitLength, | ||
#[error("invalid commitment")] | ||
InvalidCommitment, | ||
#[error("range proof generation failed")] | ||
RangeProof(#[from] RangeProofGenerationError), | ||
#[error("unexpected proof length")] | ||
ProofLength, | ||
} | ||
|
||
#[derive(Error, Clone, Debug, Eq, PartialEq)] | ||
pub enum ProofVerificationError { | ||
#[error("range proof verification failed")] | ||
RangeProof(#[from] RangeProofVerificationError), | ||
#[error("sigma proof verification failed")] | ||
SigmaProof(SigmaProofType, SigmaProofVerificationError), | ||
#[error("ElGamal ciphertext or public key error")] | ||
ElGamal(#[from] ElGamalError), | ||
#[error("Invalid proof context")] | ||
ProofContext, | ||
#[error("illegal commitment length")] | ||
IllegalCommitmentLength, | ||
#[error("illegal amount bit length")] | ||
IllegalAmountBitLength, | ||
} | ||
|
||
#[derive(Clone, Debug, Eq, PartialEq)] | ||
pub enum SigmaProofType { | ||
ZeroCiphertext, | ||
} | ||
|
||
impl From<ZeroCiphertextProofVerificationError> for ProofVerificationError { | ||
fn from(err: ZeroCiphertextProofVerificationError) -> Self { | ||
Self::SigmaProof(SigmaProofType::ZeroCiphertext, err.0) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
zk-sdk/src/elgamal_program/proof_data/zero_ciphertext.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
//! The zero-ciphertext proof instruction. | ||
//! | ||
//! A zero-ciphertext 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. | ||
use { | ||
crate::{ | ||
elgamal_program::{ | ||
errors::{ProofGenerationError, ProofVerificationError}, | ||
proof_data::{ProofType, ZkProofData}, | ||
}, | ||
encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, | ||
sigma_proofs::pod::PodZeroCiphertextProof, | ||
}, | ||
bytemuck::{bytes_of, Pod, Zeroable}, | ||
}; | ||
#[cfg(not(target_os = "solana"))] | ||
use { | ||
crate::{ | ||
encryption::elgamal::{ElGamalCiphertext, ElGamalKeypair}, | ||
sigma_proofs::zero_ciphertext::ZeroCiphertextProof, | ||
}, | ||
merlin::Transcript, | ||
std::convert::TryInto, | ||
}; | ||
|
||
/// The instruction data that is needed for the `ProofInstruction::ZeroCiphertext` 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 ZeroCiphertextProofData { | ||
/// The context data for the zero-ciphertext proof | ||
pub context: ZeroCiphertextProofContext, // 96 bytes | ||
|
||
/// Proof that the ciphertext is zero | ||
pub proof: PodZeroCiphertextProof, // 96 bytes | ||
} | ||
|
||
/// The context data needed to verify a zero-ciphertext proof. | ||
#[derive(Clone, Copy, Pod, Zeroable)] | ||
#[repr(C)] | ||
pub struct ZeroCiphertextProofContext { | ||
/// The ElGamal pubkey associated with the ElGamal ciphertext | ||
pub pubkey: PodElGamalPubkey, // 32 bytes | ||
|
||
/// The ElGamal ciphertext that encrypts zero | ||
pub ciphertext: PodElGamalCiphertext, // 64 bytes | ||
} | ||
|
||
#[cfg(not(target_os = "solana"))] | ||
impl ZeroCiphertextProofData { | ||
pub fn new( | ||
keypair: &ElGamalKeypair, | ||
ciphertext: &ElGamalCiphertext, | ||
) -> Result<Self, ProofGenerationError> { | ||
let pod_pubkey = PodElGamalPubkey(keypair.pubkey().into()); | ||
let pod_ciphertext = PodElGamalCiphertext(ciphertext.to_bytes()); | ||
|
||
let context = ZeroCiphertextProofContext { | ||
pubkey: pod_pubkey, | ||
ciphertext: pod_ciphertext, | ||
}; | ||
|
||
let mut transcript = context.new_transcript(); | ||
let proof = ZeroCiphertextProof::new(keypair, ciphertext, &mut transcript).into(); | ||
|
||
Ok(ZeroCiphertextProofData { context, proof }) | ||
} | ||
} | ||
|
||
impl ZkProofData<ZeroCiphertextProofContext> for ZeroCiphertextProofData { | ||
const PROOF_TYPE: ProofType = ProofType::ZeroCiphertext; | ||
|
||
fn context_data(&self) -> &ZeroCiphertextProofContext { | ||
&self.context | ||
} | ||
|
||
#[cfg(not(target_os = "solana"))] | ||
fn verify_proof(&self) -> Result<(), ProofVerificationError> { | ||
let mut transcript = self.context.new_transcript(); | ||
let pubkey = self.context.pubkey.try_into()?; | ||
let ciphertext = self.context.ciphertext.try_into()?; | ||
let proof: ZeroCiphertextProof = 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 ZeroCiphertextProofContext { | ||
fn new_transcript(&self) -> Transcript { | ||
let mut transcript = Transcript::new(b"zero-ciphertext-instruction"); | ||
|
||
transcript.append_message(b"pubkey", bytes_of(&self.pubkey)); | ||
transcript.append_message(b"ciphertext", bytes_of(&self.ciphertext)); | ||
|
||
transcript | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_zero_ciphertext_proof_instruction_correctness() { | ||
let keypair = ElGamalKeypair::new_rand(); | ||
|
||
// general case: encryption of 0 | ||
let ciphertext = keypair.pubkey().encrypt(0_u64); | ||
let zero_ciphertext_proof_data = | ||
ZeroCiphertextProofData::new(&keypair, &ciphertext).unwrap(); | ||
assert!(zero_ciphertext_proof_data.verify_proof().is_ok()); | ||
|
||
// general case: encryption of > 0 | ||
let ciphertext = keypair.pubkey().encrypt(1_u64); | ||
let zero_ciphertext_proof_data = | ||
ZeroCiphertextProofData::new(&keypair, &ciphertext).unwrap(); | ||
assert!(zero_ciphertext_proof_data.verify_proof().is_err()); | ||
} | ||
} |