From b1c598263c6459d5f6206e1e4a29f128c57d3122 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sun, 4 Jan 2026 17:21:19 +0900 Subject: [PATCH 1/4] derive `Debug`, `PartialEq`, `Eq` for proof types --- zk-sdk/src/range_proof/pod.rs | 6 +++--- zk-sdk/src/sigma_proofs/pod.rs | 18 +++++++++--------- .../handles_2.rs | 4 ++-- .../handles_3.rs | 4 ++-- .../batched_range_proof_u128.rs | 2 +- .../batched_range_proof_u256.rs | 2 +- .../batched_range_proof_u64.rs | 2 +- .../proof_data/batched_range_proof/mod.rs | 2 +- .../ciphertext_ciphertext_equality.rs | 4 ++-- .../ciphertext_commitment_equality.rs | 4 ++-- .../grouped_ciphertext_validity/handles_2.rs | 4 ++-- .../grouped_ciphertext_validity/handles_3.rs | 4 ++-- .../proof_data/percentage_with_cap.rs | 4 ++-- .../proof_data/pubkey_validity.rs | 4 ++-- .../proof_data/zero_ciphertext.rs | 4 ++-- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/zk-sdk/src/range_proof/pod.rs b/zk-sdk/src/range_proof/pod.rs index 28604080..9b95524c 100644 --- a/zk-sdk/src/range_proof/pod.rs +++ b/zk-sdk/src/range_proof/pod.rs @@ -16,7 +16,7 @@ use { }; /// The `RangeProof` type as a `Pod` restricted to proofs on 64-bit numbers. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodRangeProofU64(pub(crate) [u8; RANGE_PROOF_U64_LEN]); @@ -63,7 +63,7 @@ impl_from_str!( impl_from_bytes!(TYPE = PodRangeProofU64, BYTES_LEN = RANGE_PROOF_U64_LEN); /// The `RangeProof` type as a `Pod` restricted to proofs on 128-bit numbers. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodRangeProofU128(pub(crate) [u8; RANGE_PROOF_U128_LEN]); @@ -110,7 +110,7 @@ impl_from_str!( impl_from_bytes!(TYPE = PodRangeProofU128, BYTES_LEN = RANGE_PROOF_U128_LEN); /// The `RangeProof` type as a `Pod` restricted to proofs on 256-bit numbers. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodRangeProofU256(pub(crate) [u8; RANGE_PROOF_U256_LEN]); diff --git a/zk-sdk/src/sigma_proofs/pod.rs b/zk-sdk/src/sigma_proofs/pod.rs index 3ce3f38f..1bc6c14a 100644 --- a/zk-sdk/src/sigma_proofs/pod.rs +++ b/zk-sdk/src/sigma_proofs/pod.rs @@ -26,7 +26,7 @@ use { }; /// The `CiphertextCommitmentEqualityProof` type as a `Pod`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodCiphertextCommitmentEqualityProof( pub(crate) [u8; CIPHERTEXT_COMMITMENT_EQUALITY_PROOF_LEN], @@ -68,7 +68,7 @@ impl_from_bytes!( ); /// The `CiphertextCiphertextEqualityProof` type as a `Pod`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodCiphertextCiphertextEqualityProof( pub(crate) [u8; CIPHERTEXT_CIPHERTEXT_EQUALITY_PROOF_LEN], @@ -110,7 +110,7 @@ impl_from_bytes!( ); /// The `GroupedCiphertext2HandlesValidityProof` type as a `Pod`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodGroupedCiphertext2HandlesValidityProof( pub(crate) [u8; GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN], @@ -152,7 +152,7 @@ impl_from_bytes!( ); /// The `GroupedCiphertext3HandlesValidityProof` type as a `Pod`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodGroupedCiphertext3HandlesValidityProof( pub(crate) [u8; GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN], @@ -194,7 +194,7 @@ impl_from_bytes!( ); /// The `BatchedGroupedCiphertext2HandlesValidityProof` type as a `Pod`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodBatchedGroupedCiphertext2HandlesValidityProof( pub(crate) [u8; BATCHED_GROUPED_CIPHERTEXT_2_HANDLES_VALIDITY_PROOF_LEN], @@ -242,7 +242,7 @@ impl_from_bytes!( ); /// The `BatchedGroupedCiphertext3HandlesValidityProof` type as a `Pod`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodBatchedGroupedCiphertext3HandlesValidityProof( pub(crate) [u8; BATCHED_GROUPED_CIPHERTEXT_3_HANDLES_VALIDITY_PROOF_LEN], @@ -290,7 +290,7 @@ impl_from_bytes!( ); /// The `ZeroCiphertextProof` type as a `Pod`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(transparent)] pub struct PodZeroCiphertextProof(pub(crate) [u8; ZERO_CIPHERTEXT_PROOF_LEN]); @@ -330,7 +330,7 @@ impl_from_bytes!( ); /// The `PercentageWithCapProof` type as a `Pod`. -#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(transparent)] pub struct PodPercentageWithCapProof(pub(crate) [u8; PERCENTAGE_WITH_CAP_PROOF_LEN]); @@ -370,7 +370,7 @@ impl_from_bytes!( ); /// The `PubkeyValidityProof` type as a `Pod`. -#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(transparent)] pub struct PodPubkeyValidityProof(pub(crate) [u8; PUBKEY_VALIDITY_PROOF_LEN]); diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs index 9e2f5cf4..3d65af1a 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs @@ -34,7 +34,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct BatchedGroupedCiphertext2HandlesValidityProofData { pub context: BatchedGroupedCiphertext2HandlesValidityProofContext, @@ -42,7 +42,7 @@ pub struct BatchedGroupedCiphertext2HandlesValidityProofData { pub proof: PodBatchedGroupedCiphertext2HandlesValidityProof, } -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct BatchedGroupedCiphertext2HandlesValidityProofContext { pub first_pubkey: PodElGamalPubkey, // 32 bytes diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs index 7f72e77e..bf5e0a6a 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs @@ -34,7 +34,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct BatchedGroupedCiphertext3HandlesValidityProofData { pub context: BatchedGroupedCiphertext3HandlesValidityProofContext, @@ -42,7 +42,7 @@ pub struct BatchedGroupedCiphertext3HandlesValidityProofData { pub proof: PodBatchedGroupedCiphertext3HandlesValidityProof, } -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct BatchedGroupedCiphertext3HandlesValidityProofContext { pub first_pubkey: PodElGamalPubkey, // 32 bytes diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs index df292c1a..52c87ae8 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u128.rs @@ -27,7 +27,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct BatchedRangeProofU128Data { /// The context data for a batched range proof diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs index 42ace83b..ddc399f4 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u256.rs @@ -30,7 +30,7 @@ const BATCHED_RANGE_PROOF_U256_BIT_LENGTH: usize = 256; /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct BatchedRangeProofU256Data { /// The context data for a batched range proof diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs index 3104fcc3..382f8a59 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs @@ -27,7 +27,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct BatchedRangeProofU64Data { /// The context data for a batched range proof diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs index 18e3fd33..21d8cbb8 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs @@ -50,7 +50,7 @@ const MAX_SINGLE_BIT_LENGTH: usize = 64; /// This struct holds the public information that a batched range proof certifies. It includes the /// Pedersen commitments and their corresponding bit lengths. This context is shared by all /// `VerifyBatchedRangeProof{N}` instructions. -#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)] #[repr(C)] pub struct BatchedRangeProofContext { pub commitments: [PodPedersenCommitment; MAX_COMMITMENTS], diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs index b9e5484f..6c1ed191 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs @@ -33,7 +33,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct CiphertextCiphertextEqualityProofData { pub context: CiphertextCiphertextEqualityProofContext, @@ -42,7 +42,7 @@ pub struct CiphertextCiphertextEqualityProofData { } /// The context data needed to verify a ciphertext-ciphertext equality proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct CiphertextCiphertextEqualityProofContext { pub first_pubkey: PodElGamalPubkey, // 32 bytes diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs index c02fce6a..b240d6b1 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs @@ -35,7 +35,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct CiphertextCommitmentEqualityProofData { pub context: CiphertextCommitmentEqualityProofContext, @@ -43,7 +43,7 @@ pub struct CiphertextCommitmentEqualityProofData { } /// The context data needed to verify a ciphertext-commitment equality proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct CiphertextCommitmentEqualityProofContext { /// The ElGamal pubkey diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs index bfbc3b8f..dde874b4 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs @@ -34,7 +34,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct GroupedCiphertext2HandlesValidityProofData { pub context: GroupedCiphertext2HandlesValidityProofContext, @@ -42,7 +42,7 @@ pub struct GroupedCiphertext2HandlesValidityProofData { pub proof: PodGroupedCiphertext2HandlesValidityProof, } -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct GroupedCiphertext2HandlesValidityProofContext { pub first_pubkey: PodElGamalPubkey, // 32 bytes diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs index 17ffaba1..f7218b43 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs @@ -34,7 +34,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct GroupedCiphertext3HandlesValidityProofData { pub context: GroupedCiphertext3HandlesValidityProofContext, @@ -42,7 +42,7 @@ pub struct GroupedCiphertext3HandlesValidityProofData { pub proof: PodGroupedCiphertext3HandlesValidityProof, } -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct GroupedCiphertext3HandlesValidityProofContext { pub first_pubkey: PodElGamalPubkey, // 32 bytes diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs index fb3760ce..71833a98 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs @@ -32,7 +32,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct PercentageWithCapProofData { pub context: PercentageWithCapProofContext, @@ -46,7 +46,7 @@ pub struct PercentageWithCapProofData { /// computed. /// /// [`ZK ElGamal proof`]: https://docs.solanalabs.com/runtime/zk-token-proof -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct PercentageWithCapProofContext { /// The Pedersen commitment to the percentage amount. diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs index ac2b8e31..5582e794 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/pubkey_validity.rs @@ -30,7 +30,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct PubkeyValidityProofData { /// The context data for the public key validity proof @@ -41,7 +41,7 @@ pub struct PubkeyValidityProofData { } /// The context data needed to verify a pubkey validity proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct PubkeyValidityProofContext { /// The public key to be proved diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs index 253623c0..46bc1acf 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs @@ -28,7 +28,7 @@ use { /// /// It includes the cryptographic proof as well as the context data information needed to verify /// the proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct ZeroCiphertextProofData { /// The context data for the zero-ciphertext proof @@ -39,7 +39,7 @@ pub struct ZeroCiphertextProofData { } /// The context data needed to verify a zero-ciphertext proof. -#[derive(Clone, Copy, Pod, Zeroable)] +#[derive(Clone, Copy, Pod, Zeroable, Debug, PartialEq, Eq)] #[repr(C)] pub struct ZeroCiphertextProofContext { /// The ElGamal pubkey associated with the ElGamal ciphertext From 33c662a362cabe7b277a4f2cecca88a5d0e9574e Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sun, 4 Jan 2026 17:46:10 +0900 Subject: [PATCH 2/4] add input check to proof generation logic --- zk-sdk/src/zk_elgamal_proof_program/errors.rs | 2 + .../handles_2.rs | 27 +++++++++- .../handles_3.rs | 35 ++++++++++++- .../ciphertext_ciphertext_equality.rs | 35 ++++++++++++- .../ciphertext_commitment_equality.rs | 29 ++++++++++- .../grouped_ciphertext_validity/handles_2.rs | 19 ++++++- .../grouped_ciphertext_validity/handles_3.rs | 23 ++++++++- .../proof_data/percentage_with_cap.rs | 51 +++++++++++++------ .../proof_data/zero_ciphertext.rs | 12 +++-- 9 files changed, 208 insertions(+), 25 deletions(-) diff --git a/zk-sdk/src/zk_elgamal_proof_program/errors.rs b/zk-sdk/src/zk_elgamal_proof_program/errors.rs index c134015c..5dc15555 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/errors.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/errors.rs @@ -21,6 +21,8 @@ pub enum ProofGenerationError { RangeProof(#[from] RangeProofGenerationError), #[error("unexpected proof length")] ProofLength, + #[error("cryptographic component mismatch")] + InconsistentInput, } #[derive(Error, Clone, Debug, Eq, PartialEq)] diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs index 3d65af1a..936a3cda 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_2.rs @@ -19,7 +19,8 @@ use { use { crate::{ encryption::{ - elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext, + elgamal::ElGamalPubkey, + grouped_elgamal::{GroupedElGamal, GroupedElGamalCiphertext}, pedersen::PedersenOpening, }, sigma_proofs::batched_grouped_ciphertext_validity::BatchedGroupedCiphertext2HandlesValidityProof, @@ -67,6 +68,18 @@ impl BatchedGroupedCiphertext2HandlesValidityProofData { opening_lo: &PedersenOpening, opening_hi: &PedersenOpening, ) -> Result { + let expected_lo = + GroupedElGamal::encrypt_with([first_pubkey, second_pubkey], amount_lo, opening_lo); + if *grouped_ciphertext_lo != expected_lo { + return Err(ProofGenerationError::InconsistentInput); + } + + let expected_hi = + GroupedElGamal::encrypt_with([first_pubkey, second_pubkey], amount_hi, opening_hi); + if *grouped_ciphertext_hi != expected_hi { + return Err(ProofGenerationError::InconsistentInput); + } + let pod_first_pubkey = PodElGamalPubkey(first_pubkey.into()); let pod_second_pubkey = PodElGamalPubkey(second_pubkey.into()); let pod_grouped_ciphertext_lo = (*grouped_ciphertext_lo).into(); @@ -176,5 +189,17 @@ mod test { .unwrap(); assert!(proof_data.verify_proof().is_ok()); + + let result = BatchedGroupedCiphertext2HandlesValidityProofData::new( + first_keypair.pubkey(), + second_keypair.pubkey(), + &grouped_ciphertext_hi, // Swapped: Passed Hi ciphertext + &grouped_ciphertext_lo, // Swapped: Passed Lo ciphertext + amount_lo, // Claiming Lo amount + amount_hi, // Claiming Hi amount + &opening_lo, // Claiming Lo opening + &opening_hi, // Claiming Hi opening + ); + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs index bf5e0a6a..e277a8b4 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_grouped_ciphertext_validity/handles_3.rs @@ -19,7 +19,8 @@ use { use { crate::{ encryption::{ - elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext, + elgamal::ElGamalPubkey, + grouped_elgamal::{GroupedElGamal, GroupedElGamalCiphertext}, pedersen::PedersenOpening, }, sigma_proofs::batched_grouped_ciphertext_validity::BatchedGroupedCiphertext3HandlesValidityProof, @@ -70,6 +71,24 @@ impl BatchedGroupedCiphertext3HandlesValidityProofData { opening_lo: &PedersenOpening, opening_hi: &PedersenOpening, ) -> Result { + let expected_lo = GroupedElGamal::encrypt_with( + [first_pubkey, second_pubkey, third_pubkey], + amount_lo, + opening_lo, + ); + if *grouped_ciphertext_lo != expected_lo { + return Err(ProofGenerationError::InconsistentInput); + } + + let expected_hi = GroupedElGamal::encrypt_with( + [first_pubkey, second_pubkey, third_pubkey], + amount_hi, + opening_hi, + ); + if *grouped_ciphertext_hi != expected_hi { + return Err(ProofGenerationError::InconsistentInput); + } + let pod_first_pubkey = PodElGamalPubkey(first_pubkey.into()); let pod_second_pubkey = PodElGamalPubkey(second_pubkey.into()); let pod_third_pubkey = PodElGamalPubkey(third_pubkey.into()); @@ -196,5 +215,19 @@ mod test { .unwrap(); assert!(proof_data.verify_proof().is_ok()); + + let other_keypair = ElGamalKeypair::new_rand(); + let result = BatchedGroupedCiphertext3HandlesValidityProofData::new( + first_keypair.pubkey(), + second_keypair.pubkey(), + other_keypair.pubkey(), // Mismatch: Ciphertext was encrypted with `third_keypair` + &grouped_ciphertext_lo, + &grouped_ciphertext_hi, + amount_lo, + amount_hi, + &opening_lo, + &opening_hi, + ); + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs index 6c1ed191..8fe9e59f 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_ciphertext_equality.rs @@ -18,12 +18,13 @@ use { crate::{ encryption::{ elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, - pedersen::PedersenOpening, + pedersen::{PedersenOpening, G}, }, sigma_proofs::ciphertext_ciphertext_equality::CiphertextCiphertextEqualityProof, transcript::TranscriptProtocol, zk_elgamal_proof_program::errors::{ProofGenerationError, ProofVerificationError}, }, + curve25519_dalek::scalar::Scalar, merlin::Transcript, std::convert::TryInto, }; @@ -64,6 +65,20 @@ impl CiphertextCiphertextEqualityProofData { second_opening: &PedersenOpening, amount: u64, ) -> Result { + // First ciphertext should decrypt to the expected amount + // D_first = C_first - s * H_first. Should equal amount * G. + let decrypted_point = first_ciphertext.decrypt(first_keypair.secret()).target; + let expected_point = Scalar::from(amount) * G; + if decrypted_point != expected_point { + return Err(ProofGenerationError::InconsistentInput); + } + + // Second ciphertext should match encryption of amount with second_opening + let expected_second_ciphertext = second_pubkey.encrypt_with(amount, second_opening); + if *second_ciphertext != expected_second_ciphertext { + return Err(ProofGenerationError::InconsistentInput); + } + let pod_first_pubkey = PodElGamalPubkey(first_keypair.pubkey().into()); let pod_second_pubkey = PodElGamalPubkey(second_pubkey.into()); let pod_first_ciphertext = PodElGamalCiphertext(first_ciphertext.to_bytes()); @@ -194,5 +209,23 @@ mod test { .unwrap(); assert!(proof_data.verify_proof().is_ok()); + + let amount_2 = 77_u64; + let second_opening_2 = PedersenOpening::new_rand(); + let second_ciphertext_2 = second_keypair + .pubkey() + .encrypt_with(amount_2, &second_opening_2); + + // We try to prove equality between encryption of 55 and encryption of 77 + let result = CiphertextCiphertextEqualityProofData::new( + &first_keypair, + second_keypair.pubkey(), + &first_ciphertext, + &second_ciphertext_2, + &second_opening_2, + amount, + ); + + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs index b240d6b1..64dd68dd 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/ciphertext_commitment_equality.rs @@ -21,12 +21,13 @@ use { crate::{ encryption::{ elgamal::{ElGamalCiphertext, ElGamalKeypair}, - pedersen::{PedersenCommitment, PedersenOpening}, + pedersen::{Pedersen, PedersenCommitment, PedersenOpening, G}, }, sigma_proofs::ciphertext_commitment_equality::CiphertextCommitmentEqualityProof, transcript::TranscriptProtocol, zk_elgamal_proof_program::errors::{ProofGenerationError, ProofVerificationError}, }, + curve25519_dalek::scalar::Scalar, merlin::Transcript, std::convert::TryInto, }; @@ -65,6 +66,19 @@ impl CiphertextCommitmentEqualityProofData { opening: &PedersenOpening, amount: u64, ) -> Result { + // Ciphertext should decrypt to amount + let decrypted_point = ciphertext.decrypt(keypair.secret()).target; + let expected_point = Scalar::from(amount) * G; + if decrypted_point != expected_point { + return Err(ProofGenerationError::InconsistentInput); + } + + // Commitment should match amount and opening + let expected_commitment = Pedersen::with(amount, opening); + if *commitment != expected_commitment { + return Err(ProofGenerationError::InconsistentInput); + } + let context = CiphertextCommitmentEqualityProofContext { pubkey: PodElGamalPubkey(keypair.pubkey().into()), ciphertext: PodElGamalCiphertext(ciphertext.to_bytes()), @@ -136,5 +150,18 @@ mod test { .unwrap(); assert!(proof_data.verify_proof().is_ok()); + + let amount_2 = 77_u64; + let (commitment_2, opening_2) = Pedersen::new(amount_2); + + let result = CiphertextCommitmentEqualityProofData::new( + &keypair, + &ciphertext, + &commitment_2, + &opening_2, + amount, + ); + + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs index dde874b4..2804ecb3 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_2.rs @@ -19,7 +19,8 @@ use { use { crate::{ encryption::{ - elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext, + elgamal::ElGamalPubkey, + grouped_elgamal::{GroupedElGamal, GroupedElGamalCiphertext}, pedersen::PedersenOpening, }, sigma_proofs::grouped_ciphertext_validity::GroupedCiphertext2HandlesValidityProof, @@ -61,6 +62,12 @@ impl GroupedCiphertext2HandlesValidityProofData { amount: u64, opening: &PedersenOpening, ) -> Result { + let expected_ciphertext = + GroupedElGamal::encrypt_with([first_pubkey, second_pubkey], amount, opening); + if *grouped_ciphertext != expected_ciphertext { + return Err(ProofGenerationError::InconsistentInput); + } + let pod_first_pubkey = PodElGamalPubkey(first_pubkey.into()); let pod_second_pubkey = PodElGamalPubkey(second_pubkey.into()); let pod_grouped_ciphertext = (*grouped_ciphertext).into(); @@ -155,5 +162,15 @@ mod test { .unwrap(); assert!(proof_data.verify_proof().is_ok()); + + let wrong_amount = 99_u64; + let result = GroupedCiphertext2HandlesValidityProofData::new( + first_pubkey, + second_pubkey, + &grouped_ciphertext, + wrong_amount, + &opening, + ); + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs index f7218b43..2465c270 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/grouped_ciphertext_validity/handles_3.rs @@ -19,7 +19,8 @@ use { use { crate::{ encryption::{ - elgamal::ElGamalPubkey, grouped_elgamal::GroupedElGamalCiphertext, + elgamal::ElGamalPubkey, + grouped_elgamal::{GroupedElGamal, GroupedElGamalCiphertext}, pedersen::PedersenOpening, }, sigma_proofs::grouped_ciphertext_validity::GroupedCiphertext3HandlesValidityProof, @@ -64,6 +65,15 @@ impl GroupedCiphertext3HandlesValidityProofData { amount: u64, opening: &PedersenOpening, ) -> Result { + let expected_ciphertext = GroupedElGamal::encrypt_with( + [first_pubkey, second_pubkey, third_pubkey], + amount, + opening, + ); + if *grouped_ciphertext != expected_ciphertext { + return Err(ProofGenerationError::InconsistentInput); + } + let pod_first_pubkey = PodElGamalPubkey(first_pubkey.into()); let pod_second_pubkey = PodElGamalPubkey(second_pubkey.into()); let pod_third_pubkey = PodElGamalPubkey(third_pubkey.into()); @@ -169,5 +179,16 @@ mod test { .unwrap(); assert!(proof_data.verify_proof().is_ok()); + + let wrong_opening = PedersenOpening::new_rand(); + let result = GroupedCiphertext3HandlesValidityProofData::new( + first_keypair.pubkey(), + second_keypair.pubkey(), + third_keypair.pubkey(), + &grouped_ciphertext, + amount, + &wrong_opening, + ); + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs index 71833a98..8279914b 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/percentage_with_cap.rs @@ -9,7 +9,7 @@ #[cfg(not(target_os = "solana"))] use { crate::{ - encryption::pedersen::{PedersenCommitment, PedersenOpening}, + encryption::pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, sigma_proofs::percentage_with_cap::PercentageWithCapProof, transcript::TranscriptProtocol, zk_elgamal_proof_program::errors::{ProofGenerationError, ProofVerificationError}, @@ -76,6 +76,19 @@ impl PercentageWithCapProofData { claimed_opening: &PedersenOpening, max_value: u64, ) -> Result { + // Verify percentage commitment + if *percentage_commitment != Pedersen::with(percentage_amount, percentage_opening) { + return Err(ProofGenerationError::InconsistentInput); + } + // Verify delta commitment + if *delta_commitment != Pedersen::with(delta_amount, delta_opening) { + return Err(ProofGenerationError::InconsistentInput); + } + // Verify claimed commitment + if *claimed_commitment != Pedersen::with(delta_amount, claimed_opening) { + return Err(ProofGenerationError::InconsistentInput); + } + let pod_percentage_commitment = PodPedersenCommitment(percentage_commitment.to_bytes()); let pod_delta_commitment = PodPedersenCommitment(delta_commitment.to_bytes()); let pod_claimed_commitment = PodPedersenCommitment(claimed_commitment.to_bytes()); @@ -180,22 +193,12 @@ mod test { assert!(proof_data.verify_proof().is_ok()); // base amount is equal to max value - let base_amount: u64 = 55; let max_value: u64 = 3; - - let percentage_rate: u16 = 555; - let percentage_amount: u64 = 4; - - let (transfer_commitment, transfer_opening) = Pedersen::new(base_amount); - let (percentage_commitment, percentage_opening) = Pedersen::new(max_value); - - let scalar_rate = Scalar::from(percentage_rate); - let delta_commitment = - &percentage_commitment * &Scalar::from(10000_u64) - &transfer_commitment * &scalar_rate; - let delta_opening = - &percentage_opening * &Scalar::from(10000_u64) - &transfer_opening * &scalar_rate; - - let (claimed_commitment, claimed_opening) = Pedersen::new(0_u64); + let percentage_amount: u64 = 3; + let (percentage_commitment, percentage_opening) = Pedersen::new(percentage_amount); + let delta_amount: u64 = 100; + let (delta_commitment, delta_opening) = Pedersen::new(delta_amount); + let (claimed_commitment, claimed_opening) = Pedersen::new(delta_amount); let proof_data = PercentageWithCapProofData::new( &percentage_commitment, @@ -211,5 +214,21 @@ mod test { .unwrap(); assert!(proof_data.verify_proof().is_ok()); + + let (fake_commitment, _) = Pedersen::new(999_u64); + + let result = PercentageWithCapProofData::new( + &percentage_commitment, + &percentage_opening, + percentage_amount, + &delta_commitment, + &delta_opening, + delta_amount, + &fake_commitment, + &claimed_opening, + max_value, + ); + + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } diff --git a/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs b/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs index 46bc1acf..42c4067b 100644 --- a/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs +++ b/zk-sdk/src/zk_elgamal_proof_program/proof_data/zero_ciphertext.rs @@ -12,6 +12,7 @@ use { transcript::TranscriptProtocol, zk_elgamal_proof_program::errors::{ProofGenerationError, ProofVerificationError}, }, + curve25519_dalek::traits::IsIdentity, merlin::Transcript, std::convert::TryInto, }; @@ -55,6 +56,12 @@ impl ZeroCiphertextProofData { keypair: &ElGamalKeypair, ciphertext: &ElGamalCiphertext, ) -> Result { + // Ciphertext should decrypt to Identity + let decrypted_point = ciphertext.decrypt(keypair.secret()).target; + if !decrypted_point.is_identity() { + return Err(ProofGenerationError::InconsistentInput); + } + let pod_pubkey = PodElGamalPubkey(keypair.pubkey().into()); let pod_ciphertext = PodElGamalCiphertext(ciphertext.to_bytes()); @@ -105,8 +112,7 @@ mod test { // 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()); + let result = ZeroCiphertextProofData::new(&keypair, &ciphertext); + assert_eq!(result, Err(ProofGenerationError::InconsistentInput)); } } From 89b5b87cf8e1c3279f70de414df23c6f1ac23ce4 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Mon, 5 Jan 2026 09:13:08 +0900 Subject: [PATCH 3/4] update wasm crate test --- zk-sdk-wasm-js/src/proof_data/zero_ciphertext.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zk-sdk-wasm-js/src/proof_data/zero_ciphertext.rs b/zk-sdk-wasm-js/src/proof_data/zero_ciphertext.rs index 1556c344..71c29fc7 100644 --- a/zk-sdk-wasm-js/src/proof_data/zero_ciphertext.rs +++ b/zk-sdk-wasm-js/src/proof_data/zero_ciphertext.rs @@ -128,8 +128,8 @@ mod tests { // Proof for an invalid encryption of 1 let one_ciphertext = keypair.pubkey().encrypt_u64(1); - let proof_invalid = ZeroCiphertextProofData::new(&keypair, &one_ciphertext).unwrap(); - assert!(proof_invalid.verify().is_err()); + let result = ZeroCiphertextProofData::new(&keypair, &one_ciphertext); + assert!(result.is_err()); } #[wasm_bindgen_test] From 89e198a8500a445f27e1f61d2365613f58f0bbc8 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Mon, 5 Jan 2026 10:30:08 +0900 Subject: [PATCH 4/4] reject identity inputs to verification --- .../handles_2.rs | 14 ++- .../handles_3.rs | 15 ++- .../ciphertext_ciphertext_equality.rs | 8 ++ .../ciphertext_commitment_equality.rs | 97 ++++++++----------- zk-sdk/src/sigma_proofs/errors.rs | 4 +- .../grouped_ciphertext_validity/handles_2.rs | 89 ++++++----------- .../grouped_ciphertext_validity/handles_3.rs | 97 ++++++------------- .../src/sigma_proofs/percentage_with_cap.rs | 7 ++ zk-sdk/src/sigma_proofs/pubkey_validity.rs | 6 +- zk-sdk/src/sigma_proofs/zero_ciphertext.rs | 58 +++++------ 10 files changed, 162 insertions(+), 233 deletions(-) diff --git a/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_2.rs index f9c36e31..e009d4a5 100644 --- a/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_2.rs @@ -29,12 +29,12 @@ use { use { crate::{ sigma_proofs::{ - errors::ValidityProofVerificationError, + errors::{SigmaProofVerificationError, ValidityProofVerificationError}, grouped_ciphertext_validity::GroupedCiphertext2HandlesValidityProof, }, transcript::TranscriptProtocol, }, - curve25519_dalek::scalar::Scalar, + curve25519_dalek::{scalar::Scalar, traits::IsIdentity}, merlin::Transcript, }; @@ -112,6 +112,16 @@ impl BatchedGroupedCiphertext2HandlesValidityProof { grouped_ciphertext_hi: &GroupedElGamalCiphertext<2>, transcript: &mut Transcript, ) -> Result<(), ValidityProofVerificationError> { + // We reject if the first public key or the commitments are the identity point. + // We allow the second public key to be an identity point as it is often the auditor's + // public key in the token-2022 program that can be the identity. + if first_pubkey.get_point().is_identity() + || grouped_ciphertext_lo.commitment.get_point().is_identity() + || grouped_ciphertext_hi.commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript( first_pubkey, second_pubkey, diff --git a/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_3.rs index 7e154b8d..56dbe643 100644 --- a/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_3.rs @@ -33,13 +33,13 @@ use { use { crate::{ sigma_proofs::{ - errors::ValidityProofVerificationError, + errors::{SigmaProofVerificationError, ValidityProofVerificationError}, grouped_ciphertext_validity::GroupedCiphertext3HandlesValidityProof, }, transcript::TranscriptProtocol, UNIT_LEN, }, - curve25519_dalek::scalar::Scalar, + curve25519_dalek::{scalar::Scalar, traits::IsIdentity}, merlin::Transcript, }; @@ -118,6 +118,17 @@ impl BatchedGroupedCiphertext3HandlesValidityProof { grouped_ciphertext_hi: &GroupedElGamalCiphertext<3>, transcript: &mut Transcript, ) -> Result<(), ValidityProofVerificationError> { + // We reject if the public keys or the commitments are the identity point. + // The exception is the third public key, which is often the auditor's + // public key in the tokne-2022 program that can be the identity. + if first_pubkey.get_point().is_identity() + || second_pubkey.get_point().is_identity() + || grouped_ciphertext_lo.commitment.get_point().is_identity() + || grouped_ciphertext_hi.commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript( first_pubkey, second_pubkey, diff --git a/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs b/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs index 839fd2d8..77ec2679 100644 --- a/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs +++ b/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs @@ -152,6 +152,14 @@ impl CiphertextCiphertextEqualityProof { second_ciphertext: &ElGamalCiphertext, transcript: &mut Transcript, ) -> Result<(), EqualityProofVerificationError> { + if first_pubkey.get_point().is_identity() + || second_pubkey.get_point().is_identity() + || first_ciphertext.commitment.get_point().is_identity() + || second_ciphertext.commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript( first_pubkey, second_pubkey, diff --git a/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs b/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs index 4fa3a145..027173ec 100644 --- a/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs +++ b/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs @@ -143,6 +143,13 @@ impl CiphertextCommitmentEqualityProof { commitment: &PedersenCommitment, transcript: &mut Transcript, ) -> Result<(), EqualityProofVerificationError> { + if pubkey.get_point().is_identity() + || ciphertext.commitment.get_point().is_identity() + || commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript(pubkey, ciphertext, commitment, transcript); transcript.ciphertext_commitment_equality_proof_domain_separator(); @@ -345,11 +352,10 @@ mod test { } #[test] - fn test_ciphertext_commitment_equality_proof_edge_cases() { - // if ElGamal public key zero (public key is invalid), then the proof should always reject + fn test_ciphertext_commitment_equality_proof_identity_inputs() { + // ElGamal public key zero (already invalid, but now checks IdentityPoint) let public = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); let secret = ElGamalSecretKey::new_rand(); - let elgamal_keypair = ElGamalKeypair::new_for_tests(public, secret); let message: u64 = 55; @@ -368,19 +374,19 @@ mod test { &mut prover_transcript, ); - assert!(proof - .verify( - elgamal_keypair.pubkey(), - &ciphertext, - &commitment, - &mut verifier_transcript - ) - .is_err()); + let result = proof.verify( + elgamal_keypair.pubkey(), + &ciphertext, + &commitment, + &mut verifier_transcript, + ); + assert_eq!( + result.unwrap_err(), + EqualityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); - // if ciphertext is all-zero (valid commitment of 0) and commitment is also all-zero, then - // the proof should still accept + // Ciphertext and Commitment are all-zero let elgamal_keypair = ElGamalKeypair::new_rand(); - let message: u64 = 0; let ciphertext = ElGamalCiphertext::from_bytes(&[0u8; 64]).unwrap(); let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap(); @@ -398,19 +404,19 @@ mod test { &mut prover_transcript, ); - proof - .verify( - elgamal_keypair.pubkey(), - &ciphertext, - &commitment, - &mut verifier_transcript, - ) - .unwrap(); + let result = proof.verify( + elgamal_keypair.pubkey(), + &ciphertext, + &commitment, + &mut verifier_transcript, + ); + assert_eq!( + result.unwrap_err(), + EqualityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); - // if commitment is all-zero and the ciphertext is a correct encryption of 0, then the - // proof should still accept + // Only Commitment is zero let elgamal_keypair = ElGamalKeypair::new_rand(); - let message: u64 = 0; let ciphertext = elgamal_keypair.pubkey().encrypt(message); let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap(); @@ -428,43 +434,16 @@ mod test { &mut prover_transcript, ); - proof - .verify( - elgamal_keypair.pubkey(), - &ciphertext, - &commitment, - &mut verifier_transcript, - ) - .unwrap(); - - // if ciphertext is all zero and commitment correctly encodes 0, then the proof should - // still accept - let elgamal_keypair = ElGamalKeypair::new_rand(); - - let message: u64 = 0; - let ciphertext = ElGamalCiphertext::from_bytes(&[0u8; 64]).unwrap(); - let (commitment, opening) = Pedersen::new(message); - - let mut prover_transcript = Transcript::new_zk_elgamal_transcript(b"Test"); - let mut verifier_transcript = Transcript::new_zk_elgamal_transcript(b"Test"); - - let proof = CiphertextCommitmentEqualityProof::new( - &elgamal_keypair, + let result = proof.verify( + elgamal_keypair.pubkey(), &ciphertext, &commitment, - &opening, - message, - &mut prover_transcript, + &mut verifier_transcript, + ); + assert_eq!( + result.unwrap_err(), + EqualityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) ); - - proof - .verify( - elgamal_keypair.pubkey(), - &ciphertext, - &commitment, - &mut verifier_transcript, - ) - .unwrap(); } #[test] diff --git a/zk-sdk/src/sigma_proofs/errors.rs b/zk-sdk/src/sigma_proofs/errors.rs index 87ef84e4..d58fe2e6 100644 --- a/zk-sdk/src/sigma_proofs/errors.rs +++ b/zk-sdk/src/sigma_proofs/errors.rs @@ -11,8 +11,8 @@ pub enum SigmaProofVerificationError { MultiscalarMul, #[error("transcript failed to produce a challenge")] Transcript(#[from] TranscriptError), - #[error("public key is the identity")] - PubkeyIsIdentity, + #[error("input point is the identity")] + IdentityPoint, } macro_rules! impl_from_transcript_error { diff --git a/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs b/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs index 39887387..fdb43867 100644 --- a/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs +++ b/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs @@ -149,6 +149,15 @@ impl GroupedCiphertext2HandlesValidityProof { grouped_ciphertext: &GroupedElGamalCiphertext<2>, transcript: &mut Transcript, ) -> Result<(), ValidityProofVerificationError> { + // We reject if the first public key or the commitment is the identity point. + // We allow the second public key to be an identity point as it is often the auditor's + // public key in the token-2022 program that can be the identity. + if first_pubkey.get_point().is_identity() + || grouped_ciphertext.commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript( first_pubkey, second_pubkey, @@ -342,16 +351,14 @@ mod test { } #[test] - fn test_grouped_ciphertext_validity_proof_edge_cases() { - // if the first public key zeroed, then the proof should always reject + fn test_grouped_ciphertext_validity_proof_identity_inputs() { + // First public key zeroed let first_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); - let second_keypair = ElGamalKeypair::new_rand(); let second_pubkey = second_keypair.pubkey(); let amount: u64 = 55; let (commitment, opening) = Pedersen::new(amount); - let first_handle = first_pubkey.decrypt_handle(&opening); let second_handle = second_pubkey.decrypt_handle(&opening); @@ -372,26 +379,26 @@ mod test { &mut prover_transcript, ); - assert!(proof - .verify( - &first_pubkey, - second_pubkey, - &grouped_ciphertext, - &mut verifier_transcript, - ) - .is_err()); + let result = proof.verify( + &first_pubkey, + second_pubkey, + &grouped_ciphertext, + &mut verifier_transcript, + ); + assert_eq!( + result.unwrap_err(), + ValidityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); - // all zeroed ciphertext should still be valid + // All zeroed ciphertext (Previously valid, now invalid) let first_keypair = ElGamalKeypair::new_rand(); let first_pubkey = first_keypair.pubkey(); - let second_keypair = ElGamalKeypair::new_rand(); let second_pubkey = second_keypair.pubkey(); let amount: u64 = 0; let commitment = PedersenCommitment::from_bytes(&[0u8; 32]).unwrap(); let opening = PedersenOpening::from_bytes(&[0u8; 32]).unwrap(); - let first_handle = first_pubkey.decrypt_handle(&opening); let second_handle = second_pubkey.decrypt_handle(&opening); @@ -412,58 +419,16 @@ mod test { &mut prover_transcript, ); - proof - .verify( - first_pubkey, - second_pubkey, - &grouped_ciphertext, - &mut verifier_transcript, - ) - .unwrap(); - - // decryption handles can be zero as long as the Pedersen commitment is valid - let first_keypair = ElGamalKeypair::new_rand(); - let first_pubkey = first_keypair.pubkey(); - - let second_keypair = ElGamalKeypair::new_rand(); - let second_pubkey = second_keypair.pubkey(); - - let amount: u64 = 55; - let (commitment, opening) = Pedersen::new(amount); - - let first_handle = first_pubkey.decrypt_handle(&opening); - let second_handle = second_pubkey.decrypt_handle(&opening); - - let grouped_ciphertext = GroupedElGamalCiphertext { - commitment, - handles: [first_handle, second_handle], - }; - - let mut prover_transcript = Transcript::new_zk_elgamal_transcript(b"Test"); - let mut verifier_transcript = Transcript::new_zk_elgamal_transcript(b"Test"); - - let proof = GroupedCiphertext2HandlesValidityProof::new( + let result = proof.verify( first_pubkey, second_pubkey, &grouped_ciphertext, - amount, - &opening, - &mut prover_transcript, + &mut verifier_transcript, ); - - proof - .verify( - first_pubkey, - second_pubkey, - &grouped_ciphertext, - &mut verifier_transcript, - ) - .unwrap(); - assert_eq!( - prover_transcript.challenge_scalar(b"test"), - verifier_transcript.challenge_scalar(b"test"), - ) + result.unwrap_err(), + ValidityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); } #[test] diff --git a/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs b/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs index 25595ad7..c46e91a9 100644 --- a/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs +++ b/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs @@ -168,6 +168,16 @@ impl GroupedCiphertext3HandlesValidityProof { grouped_ciphertext: &GroupedElGamalCiphertext<3>, transcript: &mut Transcript, ) -> Result<(), ValidityProofVerificationError> { + // We reject if the public keys or the commitment is the identity point. + // The exception is the third public key, which is often the auditor's + // public key in the tokne-2022 program that can be the identity. + if first_pubkey.get_point().is_identity() + || second_pubkey.get_point().is_identity() + || grouped_ciphertext.commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript( first_pubkey, second_pubkey, @@ -399,10 +409,9 @@ mod test { #[test] fn test_grouped_ciphertext_3_handles_validity_proof_edge_cases() { - // if first or second public key zeroed, then the proof should always reject + // First public key zeroed let first_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); let second_pubkey = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); - let third_keypair = ElGamalKeypair::new_rand(); let third_pubkey = third_keypair.pubkey(); @@ -431,23 +440,23 @@ mod test { &mut prover_transcript, ); - assert!(proof - .verify( - &first_pubkey, - &second_pubkey, - third_pubkey, - &grouped_ciphertext, - &mut verifier_transcript, - ) - .is_err()); + let result = proof.verify( + &first_pubkey, + &second_pubkey, + third_pubkey, + &grouped_ciphertext, + &mut verifier_transcript, + ); + assert_eq!( + result.unwrap_err(), + ValidityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); - // all zeroed ciphertext should still be valid + // All zeroed ciphertext (Previously valid, now invalid) let first_keypair = ElGamalKeypair::new_rand(); let first_pubkey = first_keypair.pubkey(); - let second_keypair = ElGamalKeypair::new_rand(); let second_pubkey = second_keypair.pubkey(); - let third_keypair = ElGamalKeypair::new_rand(); let third_pubkey = third_keypair.pubkey(); @@ -477,67 +486,17 @@ mod test { &mut prover_transcript, ); - proof - .verify( - first_pubkey, - second_pubkey, - third_pubkey, - &grouped_ciphertext, - &mut verifier_transcript, - ) - .unwrap(); - - // decryption handles can be zero as long as the Pedersen commitment is valid - let first_keypair = ElGamalKeypair::new_rand(); - let first_pubkey = first_keypair.pubkey(); - - let second_keypair = ElGamalKeypair::new_rand(); - let second_pubkey = second_keypair.pubkey(); - - let third_keypair = ElGamalKeypair::new_rand(); - let third_pubkey = third_keypair.pubkey(); - - let amount: u64 = 55; - let zeroed_opening = PedersenOpening::default(); - - let commitment = Pedersen::with(amount, &zeroed_opening); - - let first_handle = first_pubkey.decrypt_handle(&zeroed_opening); - let second_handle = second_pubkey.decrypt_handle(&zeroed_opening); - let third_handle = third_pubkey.decrypt_handle(&zeroed_opening); - - let grouped_ciphertext = GroupedElGamalCiphertext { - commitment, - handles: [first_handle, second_handle, third_handle], - }; - - let mut prover_transcript = Transcript::new_zk_elgamal_transcript(b"Test"); - let mut verifier_transcript = Transcript::new_zk_elgamal_transcript(b"Test"); - - let proof = GroupedCiphertext3HandlesValidityProof::new( + let result = proof.verify( first_pubkey, second_pubkey, third_pubkey, &grouped_ciphertext, - amount, - &opening, - &mut prover_transcript, + &mut verifier_transcript, ); - - proof - .verify( - first_pubkey, - second_pubkey, - third_pubkey, - &grouped_ciphertext, - &mut verifier_transcript, - ) - .unwrap(); - assert_eq!( - prover_transcript.challenge_scalar(b"test"), - verifier_transcript.challenge_scalar(b"test"), - ) + result.unwrap_err(), + ValidityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); } #[test] diff --git a/zk-sdk/src/sigma_proofs/percentage_with_cap.rs b/zk-sdk/src/sigma_proofs/percentage_with_cap.rs index 01e30f2d..be17c63a 100644 --- a/zk-sdk/src/sigma_proofs/percentage_with_cap.rs +++ b/zk-sdk/src/sigma_proofs/percentage_with_cap.rs @@ -418,6 +418,13 @@ impl PercentageWithCapProof { max_value: u64, transcript: &mut Transcript, ) -> Result<(), PercentageWithCapProofVerificationError> { + if percentage_commitment.get_point().is_identity() + || delta_commitment.get_point().is_identity() + || claimed_commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript( percentage_commitment, delta_commitment, diff --git a/zk-sdk/src/sigma_proofs/pubkey_validity.rs b/zk-sdk/src/sigma_proofs/pubkey_validity.rs index 0316d76b..b508a454 100644 --- a/zk-sdk/src/sigma_proofs/pubkey_validity.rs +++ b/zk-sdk/src/sigma_proofs/pubkey_validity.rs @@ -100,7 +100,7 @@ impl PubkeyValidityProof { let P = elgamal_pubkey.get_point(); if P.is_identity() { - return Err(SigmaProofVerificationError::PubkeyIsIdentity.into()); + return Err(SigmaProofVerificationError::IdentityPoint.into()); } // include Y to transcript and extract challenge @@ -223,9 +223,7 @@ mod test { assert_eq!( err, - PubkeyValidityProofVerificationError::from( - SigmaProofVerificationError::PubkeyIsIdentity - ) + PubkeyValidityProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) ); } } diff --git a/zk-sdk/src/sigma_proofs/zero_ciphertext.rs b/zk-sdk/src/sigma_proofs/zero_ciphertext.rs index c79e4575..7d543630 100644 --- a/zk-sdk/src/sigma_proofs/zero_ciphertext.rs +++ b/zk-sdk/src/sigma_proofs/zero_ciphertext.rs @@ -101,6 +101,12 @@ impl ZeroCiphertextProof { ciphertext: &ElGamalCiphertext, transcript: &mut Transcript, ) -> Result<(), ZeroCiphertextProofVerificationError> { + if elgamal_pubkey.get_point().is_identity() + || ciphertext.commitment.get_point().is_identity() + { + return Err(SigmaProofVerificationError::IdentityPoint.into()); + } + Self::hash_context_into_transcript(elgamal_pubkey, ciphertext, transcript); transcript.zero_ciphertext_proof_domain_separator(); @@ -235,23 +241,23 @@ mod test { } #[test] - fn test_zero_ciphertext_proof_edge_cases() { + fn test_zero_ciphertext_proof_identity_inputs() { let keypair = ElGamalKeypair::new_rand(); let mut prover_transcript = Transcript::new_zk_elgamal_transcript(b"test"); let mut verifier_transcript = Transcript::new_zk_elgamal_transcript(b"test"); - // all zero ciphertext should always be a valid encryption of 0 + // All zero ciphertext let ciphertext = ElGamalCiphertext::from_bytes(&[0u8; 64]).unwrap(); - let proof = ZeroCiphertextProof::new(&keypair, &ciphertext, &mut prover_transcript); - proof - .verify(keypair.pubkey(), &ciphertext, &mut verifier_transcript) - .unwrap(); + let result = proof.verify(keypair.pubkey(), &ciphertext, &mut verifier_transcript); + assert_eq!( + result.unwrap_err(), + ZeroCiphertextProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); - // if only either commitment or handle is zero, the ciphertext is always invalid and proof - // verification should always reject + // Only commitment is zero let mut prover_transcript = Transcript::new_zk_elgamal_transcript(b"test"); let mut verifier_transcript = Transcript::new_zk_elgamal_transcript(b"test"); @@ -259,45 +265,31 @@ mod test { let handle = keypair .pubkey() .decrypt_handle(&PedersenOpening::new_rand()); - let ciphertext = ElGamalCiphertext { commitment: zeroed_commitment, handle, }; - - let proof = ZeroCiphertextProof::new(&keypair, &ciphertext, &mut prover_transcript); - - assert!(proof - .verify(keypair.pubkey(), &ciphertext, &mut verifier_transcript) - .is_err()); - - let mut prover_transcript = Transcript::new_zk_elgamal_transcript(b"test"); - let mut verifier_transcript = Transcript::new_zk_elgamal_transcript(b"test"); - - let (zeroed_commitment, _) = Pedersen::new(0_u64); - let ciphertext = ElGamalCiphertext { - commitment: zeroed_commitment, - handle: DecryptHandle::from_bytes(&[0u8; 32]).unwrap(), - }; - let proof = ZeroCiphertextProof::new(&keypair, &ciphertext, &mut prover_transcript); - assert!(proof - .verify(keypair.pubkey(), &ciphertext, &mut verifier_transcript) - .is_err()); + let result = proof.verify(keypair.pubkey(), &ciphertext, &mut verifier_transcript); + assert_eq!( + result.unwrap_err(), + ZeroCiphertextProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); - // if public key is always zero, then the proof should always reject + // Public key is zero let mut prover_transcript = Transcript::new_zk_elgamal_transcript(b"test"); let mut verifier_transcript = Transcript::new_zk_elgamal_transcript(b"test"); let public = ElGamalPubkey::try_from([0u8; 32].as_slice()).unwrap(); let ciphertext = public.encrypt(0_u64); - let proof = ZeroCiphertextProof::new(&keypair, &ciphertext, &mut prover_transcript); - assert!(proof - .verify(keypair.pubkey(), &ciphertext, &mut verifier_transcript) - .is_err()); + let result = proof.verify(&public, &ciphertext, &mut verifier_transcript); + assert_eq!( + result.unwrap_err(), + ZeroCiphertextProofVerificationError::from(SigmaProofVerificationError::IdentityPoint) + ); } #[test]