|
| 1 | +# Structural Guards Quick Reference (ZK-075) |
| 2 | + |
| 3 | +## What Are Structural Guards? |
| 4 | + |
| 5 | +Structural guards validate byte lengths and vector counts BEFORE deserializing elliptic curve points or touching cryptographic operations. They ensure malformed payloads fail fast with explicit errors. |
| 6 | + |
| 7 | +## Expected Byte Lengths |
| 8 | + |
| 9 | +``` |
| 10 | +G1 Point: 64 bytes |
| 11 | +G2 Point: 128 bytes |
| 12 | +Field Element: 32 bytes |
| 13 | +
|
| 14 | +Proof: |
| 15 | + A (G1): 64 bytes |
| 16 | + B (G2): 128 bytes |
| 17 | + C (G1): 64 bytes |
| 18 | + Total: 256 bytes |
| 19 | +
|
| 20 | +VK: |
| 21 | + alpha_g1: 64 bytes |
| 22 | + beta_g2: 128 bytes |
| 23 | + gamma_g2: 128 bytes |
| 24 | + delta_g2: 128 bytes |
| 25 | + IC vector: 9 points × 64 bytes = 576 bytes |
| 26 | +
|
| 27 | +Public Inputs: 8 fields × 32 bytes = 256 bytes |
| 28 | +``` |
| 29 | + |
| 30 | +## Contract Usage (Rust) |
| 31 | + |
| 32 | +### Automatic Validation |
| 33 | + |
| 34 | +```rust |
| 35 | +use crate::crypto::verifier::verify_proof; |
| 36 | + |
| 37 | +// Structural guards run automatically in verify_proof() |
| 38 | +let result = verify_proof(&env, &vk, &proof, &pub_inputs); |
| 39 | + |
| 40 | +match result { |
| 41 | + Err(Error::MalformedProofA) => { |
| 42 | + // Proof A has wrong length |
| 43 | + } |
| 44 | + Err(Error::VkIcVectorWrongLength) => { |
| 45 | + // VK IC vector doesn't have 9 points |
| 46 | + } |
| 47 | + Err(Error::PublicInputWrongLength) => { |
| 48 | + // A public input field has wrong length |
| 49 | + } |
| 50 | + Ok(valid) => { |
| 51 | + // Structural validation passed, check pairing result |
| 52 | + } |
| 53 | + _ => {} |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +### Error Codes |
| 58 | + |
| 59 | +```rust |
| 60 | +// Proof errors |
| 61 | +Error::MalformedProofA // A is not 64 bytes |
| 62 | +Error::MalformedProofB // B is not 128 bytes |
| 63 | +Error::MalformedProofC // C is not 64 bytes |
| 64 | + |
| 65 | +// VK errors |
| 66 | +Error::VkAlphaG1WrongLength // alpha_g1 is not 64 bytes |
| 67 | +Error::VkBetaG2WrongLength // beta_g2 is not 128 bytes |
| 68 | +Error::VkGammaG2WrongLength // gamma_g2 is not 128 bytes |
| 69 | +Error::VkDeltaG2WrongLength // delta_g2 is not 128 bytes |
| 70 | +Error::VkIcVectorWrongLength // IC vector doesn't have 9 points |
| 71 | +Error::VkIcPointWrongLength // An IC point is not 64 bytes |
| 72 | + |
| 73 | +// Public input errors |
| 74 | +Error::PublicInputWrongLength // A field is not 32 bytes |
| 75 | +``` |
| 76 | + |
| 77 | +## SDK Usage (TypeScript) |
| 78 | + |
| 79 | +### Validate Proof |
| 80 | + |
| 81 | +```typescript |
| 82 | +import { validateProofStructure } from "./structural_guards"; |
| 83 | + |
| 84 | +try { |
| 85 | + validateProofStructure(proofBytes); |
| 86 | + // Proof structure is valid |
| 87 | +} catch (error) { |
| 88 | + // Proof has wrong length |
| 89 | + console.error("Invalid proof structure:", error.message); |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +### Validate VK |
| 94 | + |
| 95 | +```typescript |
| 96 | +import { validateVkStructure } from "./structural_guards"; |
| 97 | + |
| 98 | +const vk = { |
| 99 | + alpha_g1: new Uint8Array(64), |
| 100 | + beta_g2: new Uint8Array(128), |
| 101 | + gamma_g2: new Uint8Array(128), |
| 102 | + delta_g2: new Uint8Array(128), |
| 103 | + gamma_abc_g1: [ |
| 104 | + /* 9 points of 64 bytes each */ |
| 105 | + ], |
| 106 | +}; |
| 107 | + |
| 108 | +try { |
| 109 | + validateVkStructure(vk); |
| 110 | + // VK structure is valid |
| 111 | +} catch (error) { |
| 112 | + // VK has structural issues |
| 113 | + console.error("Invalid VK structure:", error.message); |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +### Validate Public Inputs |
| 118 | + |
| 119 | +```typescript |
| 120 | +import { |
| 121 | + validatePublicInputsStructure, |
| 122 | + validatePublicInputsHexStructure, |
| 123 | +} from "./structural_guards"; |
| 124 | + |
| 125 | +// Validate byte arrays |
| 126 | +const inputs = [ |
| 127 | + /* 8 Uint8Array of 32 bytes each */ |
| 128 | +]; |
| 129 | +validatePublicInputsStructure(inputs); |
| 130 | + |
| 131 | +// Validate hex strings |
| 132 | +const hexInputs = [ |
| 133 | + /* 8 strings of 64 hex chars each */ |
| 134 | +]; |
| 135 | +validatePublicInputsHexStructure(hexInputs); |
| 136 | +``` |
| 137 | + |
| 138 | +### Extract Proof Components |
| 139 | + |
| 140 | +```typescript |
| 141 | +import { extractProofComponents } from "./structural_guards"; |
| 142 | + |
| 143 | +const proof = new Uint8Array(256); |
| 144 | +const { a, b, c } = extractProofComponents(proof); |
| 145 | + |
| 146 | +console.log("A:", a.length); // 64 |
| 147 | +console.log("B:", b.length); // 128 |
| 148 | +console.log("C:", c.length); // 64 |
| 149 | +``` |
| 150 | + |
| 151 | +## Common Errors and Fixes |
| 152 | + |
| 153 | +### Proof Too Short/Long |
| 154 | + |
| 155 | +``` |
| 156 | +Error: Proof must be 256 bytes (64 + 128 + 64), got 255 |
| 157 | +Fix: Ensure proof contains all three components (A, B, C) |
| 158 | +``` |
| 159 | + |
| 160 | +### VK IC Vector Wrong Length |
| 161 | + |
| 162 | +``` |
| 163 | +Error: VK gamma_abc_g1 must have 9 points (IC[0] + 8 inputs), got 8 |
| 164 | +Fix: IC vector needs IC[0] plus one point per public input |
| 165 | +``` |
| 166 | + |
| 167 | +### Public Input Wrong Length |
| 168 | + |
| 169 | +``` |
| 170 | +Error: Public input[3] must be 32 bytes, got 16 |
| 171 | +Fix: All public inputs must be 32-byte field elements |
| 172 | +``` |
| 173 | + |
| 174 | +### VK Point Wrong Length |
| 175 | + |
| 176 | +``` |
| 177 | +Error: VK alpha_g1 must be 64 bytes, got 32 |
| 178 | +Fix: G1 points are 64 bytes, G2 points are 128 bytes |
| 179 | +``` |
| 180 | + |
| 181 | +## Testing |
| 182 | + |
| 183 | +### Contract Tests |
| 184 | + |
| 185 | +```bash |
| 186 | +# Run structural guard tests |
| 187 | +cargo test structural_guards |
| 188 | + |
| 189 | +# Run specific test |
| 190 | +cargo test test_proof_a_wrong_length_rejected |
| 191 | +``` |
| 192 | + |
| 193 | +### SDK Tests |
| 194 | + |
| 195 | +```bash |
| 196 | +# Run structural guard tests |
| 197 | +npm test structural_guards |
| 198 | + |
| 199 | +# Run specific test |
| 200 | +npm test -- -t "should reject proof that is too short" |
| 201 | +``` |
| 202 | + |
| 203 | +## Constants |
| 204 | + |
| 205 | +### Contract (Rust) |
| 206 | + |
| 207 | +```rust |
| 208 | +const G1_POINT_BYTE_LENGTH: u32 = 64; |
| 209 | +const G2_POINT_BYTE_LENGTH: u32 = 128; |
| 210 | +const FIELD_ELEMENT_BYTE_LENGTH: u32 = 32; |
| 211 | +const EXPECTED_PUBLIC_INPUT_COUNT: u32 = 8; |
| 212 | +const EXPECTED_IC_VECTOR_LENGTH: u32 = 9; |
| 213 | +``` |
| 214 | + |
| 215 | +### SDK (TypeScript) |
| 216 | + |
| 217 | +```typescript |
| 218 | +export const G1_POINT_BYTE_LENGTH = 64; |
| 219 | +export const G2_POINT_BYTE_LENGTH = 128; |
| 220 | +export const FIELD_ELEMENT_BYTE_LENGTH = 32; |
| 221 | +export const EXPECTED_PUBLIC_INPUT_COUNT = 8; |
| 222 | +export const EXPECTED_IC_VECTOR_LENGTH = 9; |
| 223 | +export const GROTH16_PROOF_TOTAL_LENGTH = 256; |
| 224 | +``` |
| 225 | + |
| 226 | +## Validation Order |
| 227 | + |
| 228 | +Structural guards run in this order: |
| 229 | + |
| 230 | +1. **Proof Structure** |
| 231 | + - Check A length (64 bytes) |
| 232 | + - Check B length (128 bytes) |
| 233 | + - Check C length (64 bytes) |
| 234 | + |
| 235 | +2. **VK Structure** |
| 236 | + - Check alpha_g1 length (64 bytes) |
| 237 | + - Check beta_g2 length (128 bytes) |
| 238 | + - Check gamma_g2 length (128 bytes) |
| 239 | + - Check delta_g2 length (128 bytes) |
| 240 | + - Check IC vector length (9 points) |
| 241 | + - Check each IC point length (64 bytes) |
| 242 | + |
| 243 | +3. **Public Inputs Structure** |
| 244 | + - Check input count (8 fields) |
| 245 | + - Check each field length (32 bytes) |
| 246 | + |
| 247 | +4. **Cryptographic Operations** |
| 248 | + - Deserialize curve points |
| 249 | + - Compute linear combination |
| 250 | + - Perform pairing check |
| 251 | + |
| 252 | +## Best Practices |
| 253 | + |
| 254 | +1. **Always validate before deserialization** |
| 255 | + - Structural guards should run first |
| 256 | + - Prevents expensive operations on bad data |
| 257 | + |
| 258 | +2. **Use specific error codes** |
| 259 | + - Don't catch all errors generically |
| 260 | + - Handle structural errors differently from crypto errors |
| 261 | + |
| 262 | +3. **Validate early in the pipeline** |
| 263 | + - SDK should validate before sending to contract |
| 264 | + - Contract validates again for defense in depth |
| 265 | + |
| 266 | +4. **Test malformed payloads** |
| 267 | + - Include structural guard tests in your test suite |
| 268 | + - Test all error paths |
| 269 | + |
| 270 | +5. **Log structural errors** |
| 271 | + - Structural errors indicate bugs or attacks |
| 272 | + - Log them for debugging and security monitoring |
| 273 | + |
| 274 | +## See Also |
| 275 | + |
| 276 | +- [ZK-075_IMPLEMENTATION_SUMMARY.md](./ZK-075_IMPLEMENTATION_SUMMARY.md) - Full implementation details |
| 277 | +- [contracts/privacy_pool/src/test/structural_guards.rs](./contracts/privacy_pool/src/test/structural_guards.rs) - Contract tests |
| 278 | +- [sdk/src/structural_guards.test.ts](./sdk/src/structural_guards.test.ts) - SDK tests |
0 commit comments