Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions sdk/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
/**

/** Thrown by `assertStructuralValidity` for shape violations. */
export class StructuralError extends Error {
constructor(message: string) {
super(message);
this.name = 'StructuralError';
}
}
* Thrown when withdrawal witness or proof inputs fail structural validation
* (lengths, encodings) before a proving backend is invoked. Use
* `code` to distinguish from honest proving/verification errors.
Expand Down
55 changes: 55 additions & 0 deletions sdk/src/offline_verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,45 @@ import {
ArtifactManifestError,
} from './types';
import { assertManifestMatchesNoirArtifacts } from './backends/noir';
import { StructuralError } from './errors';

// ZK-075: Structural guards for proof, VK, and public-input shapes
// ---------------------------------------------------------------------------



/**
* ZK-075: Structural-validity checks for proofs, VKs, and public inputs.
* A lightweight pre-filter that rejects misshapen objects before they
* can trigger a panic in the parsing or verification backend.
*/
function assertStructuralValidity(
proof: Uint8Array,
publicInputs: WithdrawalPublicInputs | PreparedWitness | string[],
artifacts: NoirArtifacts,
): void {
// Proof shape: Non-empty Uint8Array, at least 192 bytes for Groth16.
if (!(proof instanceof Uint8Array) || proof.length < 192) {
throw new StructuralError(
'Proof must be a Uint8Array of at least 192 bytes',
);
}

// VK shape: Non-empty Uint8Array, at least 256 bytes for a BN254 VK.
const { vkey } = artifacts;
if (!(vkey instanceof Uint8Array) || vkey.length < 256) {
throw new StructuralError(
'Verification key must be a Uint8Array of at least 256 bytes',
);
}

// Public inputs: Must be a non-empty array-like structure.
if (!publicInputs || typeof publicInputs.length !== 'number' || publicInputs.length === 0) {
throw new StructuralError(
'Public inputs must be a non-empty array or array-like object',
);
}
}

// ---------------------------------------------------------------------------
// Failure taxonomy
Expand Down Expand Up @@ -205,6 +244,22 @@ export async function verifyWithManifest(
);
}

// ------------------------------------------------------------------
// Step 1.5: Structural guards for proof, VK, and public-input shapes
// ------------------------------------------------------------------
try {
assertStructuralValidity(proof, publicInputs, artifacts);
} catch (err) {
// Re-wrap structural-validity failures as BAD_PROOF so callers
// only have to handle the three primary categories.
throw new OfflineVerificationError(
`Structural validity check failed: ${err instanceof Error ? err.message : String(err)}`,
'BAD_PROOF',
err,
);
}


// ------------------------------------------------------------------
// Step 2: Public-input schema check
// ------------------------------------------------------------------
Expand Down