VDI Billing v2 Cryptographic Binding Model
This note clarifies the current trust surface for api-billing-v2, including why logs often show publicSignals: 0 and how security-critical facts are still bound.
Why publicSignals: 0 appears
zk-cp currently uses a private-witness-heavy Groth16 circuit configuration for causalProof and returns zero public outputs for this proof path. This is intentional in the present design, not a runtime accident.
That means proof validity alone is not the full story. Security depends on layered binding:
- proof verification (
verification.valid) - attestation canonicalization and signature binding
- receipt attestation-hash/signature binding (
receipt_verification.valid) - billing v2 fingerprint equality (
billing_check.valid) - public billing commitments digest (
billing.public_commitments_digestat attest time) - transparency inclusion proof verification (
log_proof_verification.valid) - durable replay guard (
vdi_billing_replay_guard:reserved→committed, TTL reclaim after crashes) - signed attestation commitment binding (
attestation.billing_commitments,proof.public_signalsfield split) - signed transparency log entries (PostgreSQL
vdi_billing_transparency_log, operator Ed25519 SLE)
In-circuit vs off-circuit binding
| Billing v2 fact | Bound in current public proof output | Bound off-circuit |
|---|---|---|
usage_units | No (not exposed as public signal) | Included in billing fingerprint compare and attestation private-input pipeline |
tariff_version | No | Included in billing fingerprint compare |
expected_charge_micros | No | Included in billing fingerprint compare |
meter_event_digest | No | Included in billing fingerprint compare + envelope signature verification |
meter_signing_kid | No | Included in billing fingerprint compare + trust-anchor lookup |
| Receipt integrity | No | verifyReceipt over signed receipt + attestation binding |
| Transparency inclusion | No | log_proof_verification (canonical receipt hash + Merkle proof + operator SLE) |
| Public commitments bundle | No | billing.public_commitments + public_commitments_digest (canonical hash of finance-facing fields) |
| Replay / double-spend | No | vdi_billing_replay_guard unique (tenant_id, billing_window_id, event_id) |
Verification signals and expected failure modes
POST /api/vdi/billing/verify returns independent checks:
verification, attestation/circuit-profile verificationreceipt_verification, receipt integrity and attestation bindingbilling_check, v1 arithmetic or v2 fingerprint equalitylog_proof_verification, optional inclusion-proof validation against receipt hash
Expected negative behaviors:
- Altered usage/tariff values:
billing_check.valid = false(others remain true) - Modified receipt fields:
receipt_verification.valid = false - Broken inclusion proof node/hash:
log_proof_verification.valid = false
Maturity posture
This architecture can be secure and auditable when the off-circuit bindings are treated as protocol-critical and tested as first-class invariants. The E2E/API suite should continue asserting which signal fails per tamper class, not just pass/fail globally.
For future hardening, consider exposing selected commitments as public outputs (or a committed public root) to move more semantics onto the proof surface while keeping privacy goals.
