This implementation adds structural validation guards that reject malformed byte lengths, wrong IC counts, and impossible payload shapes BEFORE deserializing elliptic-curve points or touching pairing logic. These guards ensure malformed data fails early in both contract and SDK environments.
Added granular error codes for structural validation:
VkAlphaG1WrongLength(52) - VK alpha_g1 has wrong byte length (expected 64)VkBetaG2WrongLength(53) - VK beta_g2 has wrong byte length (expected 128)VkGammaG2WrongLength(54) - VK gamma_g2 has wrong byte length (expected 128)VkDeltaG2WrongLength(55) - VK delta_g2 has wrong byte length (expected 128)VkIcVectorWrongLength(56) - VK gamma_abc_g1 vector has wrong length (expected 9)VkIcPointWrongLength(57) - VK gamma_abc_g1 contains a point with wrong byte lengthPublicInputWrongLength(63) - Public input field has wrong byte length (expected 32)
Added three structural validation functions that run BEFORE deserialization:
-
validate_proof_structure()- Validates G1 point A is 64 bytes
- Validates G2 point B is 128 bytes
- Validates G1 point C is 64 bytes
- Returns specific error for each malformed component
-
validate_vk_structure()- Validates alpha_g1 is 64 bytes
- Validates beta_g2, gamma_g2, delta_g2 are 128 bytes each
- Validates gamma_abc_g1 has exactly 9 elements (IC[0] + 8 public inputs)
- Validates each IC point is 64 bytes
- Returns specific error for each malformed component
-
validate_public_inputs_structure()- Validates all 8 public input fields are exactly 32 bytes
- Returns error if any field has wrong length
Updated verify_proof() to call all three validation functions before any cryptographic operations.
Comprehensive test suite with 20+ tests covering:
- Proof structure validation (wrong A, B, C lengths)
- VK structure validation (wrong alpha, beta, gamma, delta lengths)
- IC vector validation (too short, too long, empty, wrong point lengths)
- Public inputs validation (wrong field lengths)
- Multiple structural errors (first error reported)
- Valid structures passing guards
Created comprehensive structural validation module mirroring contract-side guards:
Constants:
G1_POINT_BYTE_LENGTH = 64G2_POINT_BYTE_LENGTH = 128FIELD_ELEMENT_BYTE_LENGTH = 32EXPECTED_PUBLIC_INPUT_COUNT = 8EXPECTED_IC_VECTOR_LENGTH = 9GROTH16_PROOF_TOTAL_LENGTH = 256
Functions:
-
validateProofStructure(proof: Uint8Array)- Validates proof is exactly 256 bytes (64 + 128 + 64)
- Throws
WitnessValidationErroron malformed proof
-
validateVkStructure(vk: VerifyingKeyStructure)- Validates all VK curve points have correct byte lengths
- Validates IC vector has exactly 9 points
- Validates each IC point is 64 bytes
- Throws
WitnessValidationErrorwith specific error messages
-
validatePublicInputsStructure(publicInputs: Uint8Array[])- Validates exactly 8 public inputs
- Validates each input is 32 bytes
- Throws
WitnessValidationErroron malformed inputs
-
validatePublicInputsHexStructure(publicInputs: string[])- Validates hex string format (64 hex chars = 32 bytes)
- Validates hex characters are valid
- Throws
WitnessValidationErroron malformed inputs
-
extractProofComponents(proof: Uint8Array)- Extracts A, B, C components from raw proof bytes
- Validates structure before extraction
Comprehensive test suite with 30+ tests covering:
- Proof structure validation (correct length, too short, too long, empty)
- VK structure validation (all point lengths, IC vector lengths)
- Public inputs validation (bytes and hex formats)
- Component extraction
- Multiple structural errors
- Constants validation
Updated assertValidGroth16ProofBytes() to use validateProofStructure() from structural guards module, providing consistent validation across SDK.
verify_proof() → deserialize points → pairing check
↑ Malformed data discovered here
verify_proof() → structural guards → deserialize points → pairing check
↑ Malformed data discovered here (FAST)
✅ Early Failure: Malformed payloads fail before expensive cryptographic operations ✅ Explicit Errors: Specific error codes identify exactly which component is malformed ✅ Consistent Validation: Same invariants enforced in both contract and SDK ✅ Performance: Structural checks are O(1) vs. O(n) for curve operations ✅ Security: Prevents malformed data from reaching cryptographic code ✅ Debuggability: Clear error messages help developers identify issues quickly
| Malformed Component | Contract Error | SDK Error |
|---|---|---|
| Proof A wrong length | MalformedProofA |
PROOF_FORMAT |
| Proof B wrong length | MalformedProofB |
PROOF_FORMAT |
| Proof C wrong length | MalformedProofC |
PROOF_FORMAT |
| VK alpha_g1 wrong length | VkAlphaG1WrongLength |
VK_FORMAT |
| VK beta_g2 wrong length | VkBetaG2WrongLength |
VK_FORMAT |
| VK gamma_g2 wrong length | VkGammaG2WrongLength |
VK_FORMAT |
| VK delta_g2 wrong length | VkDeltaG2WrongLength |
VK_FORMAT |
| VK IC vector wrong length | VkIcVectorWrongLength |
VK_FORMAT |
| VK IC point wrong length | VkIcPointWrongLength |
VK_FORMAT |
| Public input wrong length | PublicInputWrongLength |
PUBLIC_INPUT_FORMAT |
cd contracts/privacy_pool
cargo test structural_guards # Run structural guard tests
cargo test # Run all testscd sdk
npm test structural_guards # Run structural guard tests
npm test # Run all tests| Component | Type | Bytes | Notes |
|---|---|---|---|
| G1 Point | Curve point | 64 | Two 32-byte field elements (x, y) |
| G2 Point | Curve point | 128 | Two pairs of 32-byte field elements |
| Field Element | Scalar | 32 | BN254 field element |
| Proof A | G1 Point | 64 | First proof component |
| Proof B | G2 Point | 128 | Second proof component |
| Proof C | G1 Point | 64 | Third proof component |
| Total Proof | A + B + C | 256 | Complete Groth16 proof |
| VK alpha_g1 | G1 Point | 64 | VK component |
| VK beta_g2 | G2 Point | 128 | VK component |
| VK gamma_g2 | G2 Point | 128 | VK component |
| VK delta_g2 | G2 Point | 128 | VK component |
| VK IC point | G1 Point | 64 | Each IC vector element |
| VK IC vector | 9 × G1 | 576 | IC[0] + 8 public inputs |
| Public Input | Field Element | 32 | Each public input field |
✅ Malformed proof or VK structures fail with explicit pre-verification errors ✅ Contract and SDK tests cover short, long, and count-mismatch payloads ✅ Verifier code is no longer the first place malformed data is discovered ✅ Structural guards run before any elliptic curve deserialization ✅ Error messages clearly identify which component is malformed
- SDK validates payloads before sending to contract
- Same byte length expectations in both environments
- Consistent error semantics
- Clients receive clear error messages about malformed payloads
- Structural errors fail fast before expensive operations
- Debugging is easier with specific error codes
contracts/privacy_pool/src/types/errors.rs- Added 8 new error codescontracts/privacy_pool/src/crypto/verifier.rs- Added 3 validation functionscontracts/privacy_pool/src/test/structural_guards.rs- NEW: 20+ testscontracts/privacy_pool/src/test/mod.rs- Added structural_guards module
sdk/src/structural_guards.ts- NEW: Validation functions and constantssdk/src/structural_guards.test.ts- NEW: 30+ testssdk/src/witness.ts- Updated to use structural guardsZK-075_IMPLEMENTATION_SUMMARY.md- NEW: This document
Structural guards add minimal overhead:
- Contract: ~3 length checks + 1 vector iteration = O(n) where n=9 (IC vector length)
- SDK: ~3 length checks + 1 array iteration = O(n) where n=9
- Benefit: Avoids expensive elliptic curve deserialization on malformed data
Estimated savings on malformed payload:
- Without guards: Full deserialization attempt + potential panic/error
- With guards: Simple length check (< 1% of deserialization cost)
No breaking changes - this is purely additive validation. Existing valid payloads continue to work unchanged.
Potential future improvements:
- Add range checks for field elements (< BN254 modulus)
- Validate curve point encoding (compressed vs uncompressed)
- Add structural guards for commitment and merkle circuits
- Create malformed payload corpus for fuzzing
- ZK-044: (Dependency)
- ZK-114: Verifier hardening tests (malformed corpora)
- ZK-087: Verifier schema parity
Wave Issue Key: ZK-075