DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path protocols/ova/OVA_PROTOCOL_SPEC_V1.md

Total build days: 13.4


Scope

The Ova Protocol is a decentralized access control protocol implemented on the Kaspa blockDAG using vProgs. It provides authorization through three cryptographically indistinguishable on-chain objects (eggs): real eggs that carry live authorization state, chaff eggs that are functionally inert decoys, and nest eggs that are managed reusable decoys drawn from a shared pool. All three egg types are serialized identically so that no observer — including one with full on-chain read access — can distinguish egg type without the correct key material.

Access control is enforced through a path-branching mechanism: each authorization hop fans out to N on-chain addresses, exactly one of which is the real next-hop target. The remaining N−1 addresses contain decoy eggs that perform a lightweight decrypt-and-discard operation, producing no observable signal that distinguishes them from real authorizations.

Zero-knowledge proofs allow requesters to prove they satisfy access control list conditions without revealing which condition is satisfied or any auxiliary identity information. All authorization events are logged to the FORAY Protocol audit trail without embedding topology-revealing metadata.

This specification covers the complete vProg implementation of the Ova Protocol, including egg schema, lifecycle operations, path branching, nest egg pool management, ZK proof circuits, and FORAY integration.


Exclusions


Tech Stack


Glossary

ACL (Access Control List): The set of conditions that a requester must satisfy to obtain authorization from a given egg. Conditions include role membership, attribute value equality, and time-range constraints.

Authorization Chain: A directed sequence of eggs from an entry-point egg to a terminal egg, traversed by a requester to obtain authorization for a protected resource.

Canary: A nest egg configured as a tripwire. Any interrogation of a canary egg that does not originate from the owning authorization chain produces an alert event.

Chaff Egg: An egg containing no live authorization state. Its serialized representation, key structure, and interrogation response are indistinguishable from a real egg to an observer without the correct key material.

Decrypt-and-Discard: The operation performed by an egg at a decoy address when it receives an interrogation it is not the intended target of. The egg decrypts its state header, determines it is not the intended target, and produces a rejection response indistinguishable in timing and byte length from a real rejection.

Egg: The atomic unit of the Ova Protocol. An on-chain vProg object holding encrypted state, a version field, a creation timestamp, and an optional expiry timestamp. The three egg types — real, chaff, and nest — are serialized identically.

Egg ID: A 32-byte identifier derived deterministically from the egg seed and creation nonce. The Egg ID is the public on-chain handle for the egg.

Expiry: An optional timestamp embedded in an egg after which the egg rejects all interrogation requests and transitions to the retired state.

Fan-out Factor (N): The number of on-chain addresses receiving an authorization hop at each branching point. One address is the real next-hop; N−1 addresses receive decoy eggs.

FORAY Protocol: The tamper-evident audit infrastructure deployed at v4.1 that receives transaction records from Ova. FORAY is consumed as an external dependency; its internal implementation is outside this specification's scope.

Hop: A single step in an authorization chain. A hop consists of the current egg emitting N outbound addresses (one real, N−1 decoy) for the next step.

IND-CPA: Indistinguishability under Chosen Plaintext Attack. The security property proven for egg state-at-rest in Formal Verification Part I.

Interrogation: The operation by which a requester reads the encrypted state of an egg and submits a ZK proof to obtain authorization. Interrogation is the primary access-control operation.

Key Derivation Path: The deterministic derivation sequence from the egg master seed to the per-egg symmetric key, using HKDF-SHA256. The derivation path is identical for all three egg types.

Nest (The Nest): The managed pool of nest eggs available for allocation to authorization chains. The Nest maintains population counts and triggers replenishment when the active count falls below a configured threshold.

Nest Egg: An egg drawn from The Nest, allocated for reuse across multiple authorization chains. When retired, a nest egg returns to The Nest for reassignment.

Nullifier: A 32-byte value derived from the triple (egg ID, requester public key, block height). A nullifier is stored on-chain after a successful authorization to prevent replay attacks.

Provisioning: The operation that assigns an egg to an authorization chain, setting its ACL conditions and linking it to its upstream and downstream eggs in the chain.

Real Egg: An egg carrying live authorization state. Its ACL conditions, state payload, and chain linkage are encrypted under the egg key.

Revocation: The operation that invalidates an egg's authorization state by publishing a nullification commitment to the Kaspa blockDAG. After revocation, interrogation of the egg returns a rejection response indistinguishable from a normal rejection.

State Commitment: A Pedersen commitment to the current encrypted state root of an egg, anchored to the Kaspa blockDAG at each state update.

vProg: A Kaspa smart-contract program executing within the Kaspa vProg execution environment.


Phase 1: Egg Foundation (Cycles 1-2)

Cycle 1: Egg Schema and Type System (3.5 build days)

| ID | Description | Days | Deps | Acceptance Criteria | |---|---|---|---|---| | REQ-001 | The vProg schema defines an egg record containing exactly the following fields: egg_id (32 bytes), version (2 bytes), created_at (8 bytes), expires_at (8 bytes, zero-filled if no expiry), state_ciphertext (variable, minimum 64 bytes), state_nonce (12 bytes), state_tag (16 bytes), commitment (32 bytes). | 0.2 | None | A schema instance constructed with all required fields serializes without error. A schema instance with any required field absent raises a schema validation error identifying the missing field by name. | | REQ-002 | The egg schema does not contain a plaintext type field. The egg type — real, chaff, or nest — is encoded in the first 2 bytes of the state_ciphertext plaintext and is not accessible without the egg key. | 0.2 | REQ-001 | Inspection of the serialized egg record at any field offset reveals no byte sequence that encodes the string "real", "chaff", or "nest" in UTF-8, ASCII, or any fixed-length encoding. | | REQ-003 | Real eggs, chaff eggs, and nest eggs all serialize to the same byte length for identical state_ciphertext lengths. | 0.2 | REQ-001, REQ-002 | A test constructing one real egg, one chaff egg, and one nest egg each with a 64-byte state_ciphertext payload produces three serialized records of equal byte length. | | REQ-004 | The egg_id is derived deterministically from the concatenation of a 32-byte egg seed and a 12-byte creation nonce using SHA-256. | 0.1 | REQ-001 | Given identical seed and nonce inputs, the egg_id derivation function returns the same 32-byte output on two independent invocations. Given distinct nonce inputs with the same seed, the function returns distinct outputs. | | REQ-005 | The egg version field encodes the Ova Protocol specification version under which the egg was created, using the format (major × 256 + minor) as a 2-byte big-endian unsigned integer. | 0.1 | REQ-001 | An egg created under protocol version 0.5 stores the value 0x0005 in the version field. The version field is readable without decryption. | | REQ-006 | The created_at field stores the Kaspa blockDAG block height at which the egg creation transaction was accepted, encoded as a 8-byte big-endian unsigned integer. | 0.1 | REQ-001 | An egg creation transaction accepted at block height H stores the value H in the created_at field, readable without decryption. | | REQ-007 | The expires_at field stores the Kaspa blockDAG block height after which the egg rejects all interrogation requests. A zero value in expires_at indicates no expiry. | 0.1 | REQ-001 | An egg with expires_at = H rejects an interrogation submitted at block height H+1 with a rejection response. An egg with expires_at = 0 does not reject interrogation on the basis of block height alone. | | REQ-008 | The state_nonce field is generated by a CSPRNG seeded from Kaspa blockDAG entropy at the time of the state write operation. The same nonce value must not appear twice for the same egg_id across the egg's lifetime. | 0.2 | REQ-001 | A sequence of 10,000 successive state write operations on the same egg produces 10,000 distinct nonce values. A nonce collision within this sequence causes a test failure. | | REQ-009 | The commitment field stores a Pedersen commitment to the current state_ciphertext value, computed at the time of each state write. | 0.2 | REQ-001 | Given a known state_ciphertext input, the commitment field value equals the Pedersen commitment of that ciphertext computed with the protocol's defined commitment parameters. The commitment field updates on every state write. | | REQ-010 | The egg schema version is forward-compatible: a vProg compiled against schema version V accepts a serialized egg record produced under schema version V+1 without raising a parsing error, treating unknown fields as ignored extensions. | 0.2 | REQ-001, REQ-005 | A serialized egg record produced under schema version 0.6 is parsed by a vProg compiled against schema version 0.5 without error. The parsed record contains all version 0.5 fields with their correct values. |

Cycle 1 total: 1.6 build days | 10 requirements (REQ-001 – REQ-010)


Cycle 2: Egg Lifecycle Operations (4.0 build days)

| ID | Description | Days | Deps | Acceptance Criteria | |---|---|---|---|---| | REQ-011 | The egg creation operation accepts a seed (32 bytes), a creation nonce (12 bytes), an egg type (real, chaff, or nest), an optional expiry block height, and an optional ACL payload, and produces a valid egg record committed to the Kaspa blockDAG. | 0.3 | REQ-001, REQ-004 | An egg creation call with valid inputs produces a committed egg record retrievable by egg_id within 2 Kaspa blockDAG confirmation blocks. An egg creation call with a duplicate egg_id returns an error identifying the conflict. | | REQ-012 | The egg provisioning operation accepts an egg_id and an ACL payload (serialized conditions), encrypts the ACL payload under the egg key using AES-256-GCM, writes the ciphertext to the state_ciphertext field, and updates the commitment field. | 0.3 | REQ-011, REQ-015 | A provisioned egg's state_ciphertext decrypts to the original ACL payload under the correct egg key. Decryption under an incorrect key produces an AES-GCM authentication tag failure. | | REQ-013 | The egg interrogation operation accepts an egg_id, a ZK authorization proof, and a nullifier, and returns an authorization result (granted or rejected). The operation does not expose the egg's ACL conditions or type in its return value. | 0.3 | REQ-012, REQ-043 | An interrogation call with a valid ZK proof and a non-spent nullifier returns a granted result. An interrogation call with an invalid ZK proof returns a rejected result. An interrogation call with a spent nullifier returns a rejected result. The return value in all three cases contains no field encoding the egg type or ACL condition details. | | REQ-014 | The egg revocation operation accepts an egg_id and a revocation proof (ZK proof of ownership), publishes a nullification commitment to the Kaspa blockDAG, and transitions the egg to the revoked state. | 0.3 | REQ-011, REQ-043 | After a successful revocation call, all subsequent interrogation calls on the same egg_id return a rejected result. The rejected result from a revoked egg is byte-length-equal to the rejected result from a non-revoked egg with a valid-but-failing proof. | | REQ-015 | Every state write operation on an egg — including creation, provisioning, and revocation — encrypts the state payload using AES-256-GCM with a freshly derived per-operation key. Re-use of a (key, nonce) pair for two distinct state writes is prohibited. | 0.3 | REQ-001, REQ-008 | A test capturing the AES-256-GCM key and nonce for 1000 successive state writes on the same egg finds no repeated (key, nonce) pair. | | REQ-016 | The egg key is re-derived on every state write using HKDF-SHA256 with the egg master seed, the current state write counter, and the operation type as input key material. The key derivation path is identical for real eggs, chaff eggs, and nest eggs. | 0.3 | REQ-015 | Given the same master seed and write counter, the key derivation function produces the same output for a real egg and a chaff egg. Given the same master seed and distinct write counters, the function produces distinct outputs. | | REQ-017 | The HKDF-SHA256 info string for egg key derivation uses the domain separation prefix ova-egg-key-v1: followed by the write counter encoded as a decimal string. No other info string format is accepted. | 0.1 | REQ-016 | A key derivation call using the info string ova-egg-key-v1:42 and a second call using ova-egg-key-v1:043 produce distinct output key material. A call using the info string egg-key:42 returns an error. | | REQ-018 | The state write counter is initialized to 1 at egg creation and incremented by 1 on each subsequent state write. The counter is stored in the egg record as a 4-byte big-endian unsigned integer and is readable without decryption. | 0.1 | REQ-016 | An egg undergoes three state writes. The state write counter fields read 1, 2, and 3 respectively in the order of the writes. | | REQ-019 | The egg state commitment is anchored to the Kaspa blockDAG by including the state commitment in the transaction payload of the state write transaction. The anchoring block height is stored in the egg record as a 8-byte big-endian unsigned integer field named last_anchored_at. | 0.2 | REQ-009 | After a state write transaction accepted at block height H, the egg record's last_anchored_at field equals H, readable without decryption. | | REQ-020 | The egg interrogation operation checks that the current on-chain state commitment matches the commitment included in the submitted ZK proof before evaluating the proof. A mismatch between the on-chain commitment and the proof's committed value causes the interrogation to return a rejected result without evaluating the proof's ACL conditions. | 0.2 | REQ-013, REQ-019 | An interrogation call containing a ZK proof committed to a stale state commitment (one write behind the current on-chain state) returns a rejected result. The rejection does not leak whether the commitment mismatch or an ACL condition failure was the cause. |

Cycle 2 total: 2.4 build days | 10 requirements (REQ-011 – REQ-020)


Phase 2: Path Branching and Chaff Infrastructure (Cycles 3-4)

Cycle 3: Path Branching Engine (4.0 build days)

| ID | Description | Days | Deps | Acceptance Criteria | |---|---|---|---|---| | REQ-021 | The path branching engine accepts a fan-out factor N (integer, minimum 2, maximum 32) as a deployment configuration parameter. The fan-out factor applies uniformly to all hops in all authorization chains managed by a given deployment. | 0.1 | None | A deployment configured with N=5 produces exactly 5 outbound addresses at each hop. A configuration with N=1 is rejected with an error identifying the minimum of 2. A configuration with N=33 is rejected with an error identifying the maximum of 32. | | REQ-022 | At each hop, the path branching engine selects exactly one real next-hop address and generates N−1 decoy addresses. The real address is selected deterministically from the authorization chain seed and the current hop index using HKDF-SHA256 with the info string ova-hop-real-v1:{hop_index}. | 0.2 | REQ-021, REQ-017 | Given an authorization chain seed S and hop index 3, the real address selection function returns the same address on two independent invocations. Given the same seed and hop index 4, the function returns a different address from hop index 3. | | REQ-023 | Each decoy address is generated by the path branching engine using HKDF-SHA256 with the info string ova-hop-decoy-v1:{hop_index}:{decoy_index} where decoy_index runs from 0 to N−2. Decoy addresses are distinct from the real address and from each other at each hop. | 0.2 | REQ-021, REQ-022 | For N=5 at hop index 2, the engine generates 4 decoy addresses. All 5 output addresses (1 real + 4 decoy) are distinct. The real address for hop 2 does not appear in the decoy set for hop 2. | | REQ-024 | The byte length of a decoy address equals the byte length of a real address. The two are statistically indistinguishable: a sample of 10,000 real addresses and 10,000 decoy addresses from the same authorization chain family passes a chi-squared uniformity test at the 0.05 significance level. | 0.3 | REQ-022, REQ-023 | A test drawing 10,000 real addresses and 10,000 decoy addresses from distinct chains with the same fan-out factor N applies a chi-squared test to the distribution of address byte values. The test does not reject the null hypothesis of uniform distribution at p < 0.05 for either sample. | | REQ-025 | The decrypt-and-discard operation is performed by an egg at a decoy address when it receives an interrogation. The operation decrypts the state header, confirms the interrogation is not addressed to this egg, and returns a rejection response. | 0.2 | REQ-013, REQ-016 | An interrogation sent to a decoy egg address returns a rejection response. The response byte length equals the byte length of a rejection response from a real egg with a valid-but-failing ZK proof submitted to the same vProg version. | | REQ-026 | The decrypt-and-discard rejection response is produced within a time window that does not distinguish it from a real rejection. The p95 latency of a decrypt-and-discard rejection is within 50ms of the p95 latency of a real-egg rejection under the same Kaspa blockDAG load. | 0.3 | REQ-025 | A load test submitting 1000 interrogations to decoy eggs and 1000 interrogations to real eggs with invalid proofs, under identical blockDAG load conditions, produces p95 latency values within 50ms of each other. | | REQ-027 | The path branching engine does not include the real next-hop address in the current hop's on-chain transaction payload in plaintext. The real next-hop address is derivable only by a party holding the authorization chain seed. | 0.2 | REQ-022 | Inspection of the on-chain transaction payload for any hop reveals no byte sequence that matches the real next-hop address in plaintext. | | REQ-028 | The N outbound addresses at each hop are published to the chain in pseudorandom order, not in a fixed order that places the real address at a predictable position. The ordering permutation is derived from HKDF-SHA256 with info string ova-hop-order-v1:{hop_index}. | 0.2 | REQ-022, REQ-023 | A test examining the position of the real address across 1000 hops of the same authorization chain finds the real address at each position 0 through N−1 with a frequency within ±5 percentage points of 1/N. | | REQ-029 | The path branching engine enforces a minimum hop depth of 1 and a maximum hop depth of 64 per authorization chain. A chain construction call specifying a depth outside this range returns an error identifying the violated bound. | 0.1 | REQ-021 | A chain construction call with depth 0 returns an error. A chain construction call with depth 65 returns an error. A chain construction call with depth 1 succeeds. A chain construction call with depth 64 succeeds. | | REQ-030 | The authorization chain seed is a 32-byte value generated by a CSPRNG at chain creation time and stored encrypted under the chain owner's public key. The seed is not stored in plaintext on-chain at any point in the chain lifecycle. | 0.2 | REQ-022 | Inspection of all on-chain records associated with a given authorization chain finds no 32-byte sequence that matches the chain seed in plaintext. |

Cycle 3 total: 2.0 build days | 10 requirements (REQ-021 – REQ-030)


Cycle 4: Nest Egg Pool (3.5 build days)

| ID | Description | Days | Deps | Acceptance Criteria | |---|---|---|---|---| | REQ-031 | The Nest is a managed on-chain pool of nest eggs. The Nest tracks, for each nest egg, one of three states: available (unallocated), active (allocated to one or more authorization chains), or retired (expiry reached or explicitly decommissioned). | 0.2 | REQ-011 | A query to The Nest's state returns a count of eggs in each of the three states. The sum of available, active, and retired counts equals the total number of eggs ever committed to The Nest. | | REQ-032 | The Nest enforces a minimum available population threshold T, configured at deployment time (minimum value 1000). When the available count falls below T, The Nest emits a replenishment event containing the current available count and the threshold value. | 0.2 | REQ-031 | A test depleting The Nest's available count to T−1 causes a replenishment event to be emitted. The event contains the available count T−1 and the threshold T. No replenishment event is emitted when the available count equals T. | | REQ-033 | Nest egg allocation assigns an available nest egg to a requesting authorization chain and transitions the egg from available to active. A single nest egg may be allocated to multiple authorization chains simultaneously. | 0.2 | REQ-031 | A nest egg allocated to chain A and then allocated to chain B is in the active state and appears in the active set of both chain A and chain B. | | REQ-034 | The serialized byte representation of an active nest egg on the Kaspa blockDAG is byte-length-equal to a real egg with the same state_ciphertext length. A nest egg in the available state and a nest egg in the active state produce serialized records of the same byte length. | 0.2 | REQ-003, REQ-031 | A test comparing the serialized byte lengths of one available nest egg, one active nest egg, and one real egg — all with 64-byte state_ciphertext payloads — finds all three lengths equal. | | REQ-035 | An interrogation of a nest egg that is not addressed to the interrogating chain's current hop returns a rejection response with the same byte length and within the same latency window as a decrypt-and-discard rejection from a chaff egg, as specified in REQ-025 and REQ-026. | 0.2 | REQ-025, REQ-033 | A test submitting 500 interrogations to available nest eggs and 500 interrogations to chaff eggs finds the response byte lengths identical and the p95 latency values within 50ms of each other. | | REQ-036 | A nest egg may be designated as a canary at allocation time by setting a canary flag in the allocation request. A canary nest egg emits an alert event when interrogated by any address not in the allocation chain's authorized interrogator set. | 0.2 | REQ-033 | A canary nest egg interrogated by an address not in its authorized set emits exactly one alert event. The alert event contains the nest egg's egg_id, the block height of the interrogation, and the interrogating address. A non-canary nest egg interrogated by the same address emits no alert event. | | REQ-037 | The canary alert event does not reveal the allocation chain's identity, the number of chains the canary is allocated to, or any field of the authorization chain seed. | 0.2 | REQ-036 | The canary alert event payload contains only: the egg_id (32 bytes), the interrogation block height (8 bytes), and the interrogating address (32 bytes). No other fields are present in the event payload. | | REQ-038 | Nest egg retirement transitions an active nest egg to the retired state and removes it from all allocation chains. A retired nest egg returns a retired-status response to interrogation, distinct in content from a rejection but equal in byte length. | 0.2 | REQ-031, REQ-033 | After retirement, an interrogation of the egg returns a retired-status response. The retired-status response byte length equals the rejection response byte length for the same vProg version. The retired-status response does not contain the word "retired" in any plaintext field. | | REQ-039 | The Nest tracks the total number of interrogations each nest egg has received across all its allocation chains. This count is stored in the egg record as a 4-byte big-endian unsigned integer field named interrogation_count, readable without decryption. | 0.1 | REQ-031, REQ-013 | After three interrogations of a nest egg (from any combination of allocation chains), the interrogation_count field reads 3. | | REQ-040 | The Nest exposes a population query operation returning the current available, active, and retired counts as a triple of 4-byte unsigned integers. The query does not reveal individual egg identities. | 0.1 | REQ-031 | A population query returns a three-field response (available, active, retired). The sum of the three fields equals the total number of eggs registered to The Nest. The response contains no egg_id values. |

Cycle 4 total: 1.8 build days | 10 requirements (REQ-031 – REQ-040)


Phase 3: ZK Proof and Audit Integration (Cycles 5-6)

Cycle 5: ZK Circuits and Proof System (4.5 build days)

| ID | Description | Days | Deps | Acceptance Criteria | |---|---|---|---|---| | REQ-041 | The least-privilege ZK circuit accepts as private inputs the requester's credential set and the egg's ACL conditions, and as public inputs the egg's current state commitment and a nullifier. The circuit produces a proof that the credential set satisfies at least one ACL condition without revealing which condition is satisfied. | 0.4 | REQ-009, REQ-018 | A proof generated with a credential set satisfying condition C2 of a three-condition ACL verifies successfully. The verifier's inputs do not include C2 or any information about which of the three conditions was satisfied. | | REQ-042 | The ZK circuit supports three ACL condition types: (1) role membership — the credential set contains a role identifier matching a role in the ACL's role list; (2) attribute value — the credential set contains an attribute key-value pair matching an entry in the ACL's attribute map; (3) time range — the current block height falls within the range [start_height, end_height] inclusive. | 0.3 | REQ-041 | A proof is generated and verified successfully for each of the three condition types in isolation. A proof is generated and verified for a mixed ACL containing one condition of each type. A proof generation attempt with a credential set satisfying none of the ACL conditions fails at the circuit constraint level before producing a proof. | | REQ-043 | The nullifier is computed as SHA-256(egg_id ‖ requester_pubkey ‖ block_height) where block_height is the Kaspa blockDAG block height at the time of the interrogation transaction submission. | 0.2 | REQ-041 | Given known values for egg_id, requester_pubkey, and block_height, the nullifier function returns the expected SHA-256 output. The nullifier for the same egg_id and requester_pubkey at block heights H and H+1 are distinct. | | REQ-044 | A nullifier is stored on-chain in The Nest's nullifier registry upon successful authorization grant. Subsequent interrogation calls submitting the same nullifier return a rejected result without evaluating the ZK proof. | 0.2 | REQ-043, REQ-013 | An interrogation with nullifier N succeeds. A second interrogation with the same nullifier N on the same egg returns a rejected result. The rejection does not reveal that a nullifier collision was the cause of rejection. | | REQ-045 | The ZK proof includes a circuit version identifier as a public input. The vProg verifier rejects proofs produced under a circuit version older than the current deployment's minimum accepted circuit version. | 0.1 | REQ-041 | A proof produced under circuit version 1 is rejected by a verifier with minimum version 2. A proof produced under circuit version 2 is accepted by the same verifier. The rejection response for a version mismatch is byte-length-equal to the rejection response for a constraint failure. | | REQ-046 | Proof generation executes entirely on the requester's local device. No intermediate circuit state, private input, or credential data is transmitted to any on-chain or off-chain service during proof generation. | 0.2 | REQ-041 | A network capture during proof generation shows zero outbound data packets between proof generation start and proof generation complete events. The only network activity is the submission of the completed proof to the Kaspa vProg. | | REQ-047 | The ZK proof size is bounded at 32,768 bytes (32 KiB). A proof exceeding this bound is rejected by the vProg verifier with an error identifying the actual proof size. | 0.1 | REQ-041 | The proof generated by the least-privilege circuit for a three-condition ACL under standard inputs is ≤ 32,768 bytes. The verifier rejects a synthetically padded proof of 32,769 bytes with an error containing the value 32769. | | REQ-048 | The vProg verifier for the least-privilege circuit completes proof verification within 200ms at p95 under standard Kaspa blockDAG load. | 0.2 | REQ-041 | A load test submitting 1000 sequential verification calls to the vProg verifier records p95 verification time ≤ 200ms. | | REQ-049 | The ZK circuit does not accept a state commitment as a private input. The state commitment is always a public input, binding the proof to the egg's current on-chain state. | 0.1 | REQ-041, REQ-020 | A proof generated with state commitment C1 fails verification against an egg whose current on-chain commitment is C2 ≠ C1. | | REQ-050 | The ZK proving system uses a universal trusted setup. The setup parameters are published and pinned to a known hash in the vProg deployment configuration. A verifier initialized with a different setup parameter hash rejects all proofs. | 0.3 | REQ-041 | The deployment configuration contains a 32-byte SHA-256 hash of the setup parameters. A verifier initialized from these parameters accepts a valid proof. A verifier initialized from setup parameters producing a different hash rejects the same proof. |

Cycle 5 total: 2.1 build days | 10 requirements (REQ-041 – REQ-050)


Cycle 6: FORAY Audit Integration (2.5 build days)

| ID | Description | Days | Deps | Acceptance Criteria | |---|---|---|---|---| | REQ-051 | The Ova vProg emits a FORAY transaction on egg creation. The transaction payload contains: the SHA-256 hash of the egg_id (not the egg_id itself), the string literal CREATE, and the block height. | 0.2 | REQ-011 | An egg creation call at block height H produces exactly one FORAY transaction. The transaction payload contains a 32-byte SHA-256 hash, the string CREATE, and the value H. The payload does not contain the raw egg_id or any field of the egg's state. | | REQ-052 | The Ova vProg emits a FORAY transaction on egg interrogation regardless of whether the interrogation result is granted or rejected. The transaction payload contains: the SHA-256 hash of the egg_id, the string literal INTERROGATE, and the block height. | 0.2 | REQ-013 | An interrogation call at block height H produces exactly one FORAY transaction with the string INTERROGATE and the value H. The transaction does not contain the interrogation result (granted or rejected), the requester address, or the nullifier. | | REQ-053 | The Ova vProg emits a FORAY transaction on egg revocation. The transaction payload contains: the SHA-256 hash of the egg_id, the string literal REVOKE, and the block height. | 0.1 | REQ-014 | A revocation call at block height H produces exactly one FORAY transaction with the string REVOKE and the value H. No additional FORAY transaction is emitted for the revocation. | | REQ-054 | FORAY transaction payloads emitted by Ova contain no field that identifies the egg type (real, chaff, or nest), the authorization chain the egg belongs to, the fan-out factor N, or the hop index of the egg within the chain. | 0.2 | REQ-051, REQ-052, REQ-053 | Inspection of the FORAY transaction payloads for 100 distinct egg operations finds no field encoding an egg type string, a chain identifier, an N value, or a hop index in any plaintext or fixed-offset position. | | REQ-055 | FORAY transactions for operations on the same egg are emitted with a causal ordering field: an 8-byte big-endian unsigned integer initialized to 1 at creation and incremented by 1 for each subsequent operation on the same egg. | 0.2 | REQ-051 | The FORAY transactions for create, interrogate, and revoke operations on the same egg carry causal ordering values 1, 2, and 3 respectively. Two interrogation transactions on the same egg carry consecutive causal ordering values. | | REQ-056 | If the FORAY endpoint is unreachable at the time of an Ova operation, the Ova vProg completes the local operation and queues the FORAY transaction in an on-chain retry queue. The retry queue is processed on the next successful FORAY connection. | 0.3 | REQ-051 | A test simulating FORAY unavailability during 10 egg operations finds all 10 operations complete successfully and all 10 FORAY transactions appear in the retry queue. After FORAY availability is restored, all 10 transactions are delivered to FORAY within 5 subsequent block confirmations. | | REQ-057 | The retry queue is bounded at 10,000 entries. When the queue reaches capacity, new FORAY transactions are dropped and an overflow event is emitted containing the current queue size and the total number of dropped transactions since the last successful flush. | 0.1 | REQ-056 | A test filling the retry queue to 10,000 entries and then triggering one additional operation finds the queue at 10,000 entries and an overflow event containing queue size 10,000 and dropped count 1. | | REQ-058 | The SHA-256 hash used in FORAY transaction payloads is computed as SHA-256(egg_id ‖ deployment_id) where deployment_id is a 16-byte identifier unique to the Ova deployment instance. This prevents cross-deployment egg_id correlation. | 0.2 | REQ-051 | Two deployments with different deployment_ids but the same egg_id produce different SHA-256 hashes in their FORAY payloads for the same egg. |

Cycle 6 total: 1.5 build days | 8 requirements (REQ-051 – REQ-058)


Phase 4: Non-Functional Requirements and Hardening (Cycle 7)

Cycle 7: Performance, Security, and Availability (3.0 build days)

| ID | Description | Days | Deps | Acceptance Criteria | |---|---|---|---|---| | REQ-059 | Egg interrogation — including ZK proof verification, state commitment check, nullifier check, and FORAY emission — completes within 500ms at p95 under a Kaspa blockDAG transaction throughput of 100 transactions per second. | 0.2 | REQ-013, REQ-048 | A load test submitting 1000 interrogation calls at a sustained rate of 100 tps records p95 end-to-end latency ≤ 500ms. | | REQ-060 | The Nest supports a minimum active population of 1,000,000 nest eggs without causing Kaspa blockDAG throughput degradation exceeding 5% compared to a baseline with an empty Nest. | 0.3 | REQ-031 | A benchmark with The Nest holding 1,000,000 active eggs records Kaspa blockDAG transaction throughput at no less than 95% of the throughput recorded with an empty Nest under identical load. | | REQ-061 | A path branching operation with fan-out factor N=10 — including real address selection, decoy address generation, ordering permutation, and on-chain transaction submission — completes within 2000ms at p95. | 0.2 | REQ-021, REQ-028 | A load test submitting 500 path branching operations with N=10 records p95 completion time ≤ 2000ms. | | REQ-062 | All communication between Ova vProgs and the FORAY endpoint uses TLS 1.3 or higher. Connections using TLS 1.2 or lower are rejected at the Ova vProg level with an error identifying the negotiated TLS version. | 0.2 | REQ-056 | An Ova vProg configured to connect to a FORAY endpoint presenting a TLS 1.2 certificate rejects the connection and emits an error containing the string "TLS 1.2". An Ova vProg connecting to a FORAY endpoint presenting a TLS 1.3 certificate establishes the connection successfully. | | REQ-063 | Egg master seeds, per-operation keys, and chain seeds are never written to any persistent storage medium in plaintext. All seed and key material is held in memory only for the duration of the operation that requires it. | 0.2 | REQ-016, REQ-030 | A forensic capture of all Kaspa vProg storage writes during 1000 egg operations finds no byte sequence matching any of the known test seed or key values in plaintext. | | REQ-064 | The test suite validates all three egg types (real, chaff, nest) across all lifecycle operations: creation, provisioning, interrogation, and revocation. A test run with all egg types and all operations produces zero failures. | 0.2 | REQ-020, REQ-038 | Executing the full test suite against a deployed Ova vProg instance produces a result of 0 failures and ≥ 120 passing test cases. | | REQ-065 | The test suite includes a path reconstruction adversarial test: an adversary with full on-chain read access and 1000 observed interrogation sequences attempts to identify the real next-hop address at each hop by statistical analysis. The adversary's identification accuracy must not exceed 1/N + 0.02 where N is the fan-out factor. | 0.3 | REQ-028, REQ-024 | A path reconstruction test using 1000 observed hop sequences with N=5 finds the adversary identifying the correct real address with frequency ≤ 0.22 (= 1/5 + 0.02). | | REQ-066 | FORAY audit entries for Ova operations are retained on the FORAY endpoint for a minimum of 90 days from the block height at which the operation was recorded. | 0.1 | REQ-051 | A FORAY query for an audit entry created at block height H returns the entry at block height H + (90 × average blocks per day). A query for an entry at block height H − 1 day before the 90-day boundary returns the entry. | | REQ-067 | The vProg schema supports forward-compatible minor version increments: a vProg at version V.x accepts serialized egg records produced by a vProg at version V.(x+1) without data loss for all fields defined in version V.x. | 0.2 | REQ-010 | A vProg at version 0.5 parses a serialized egg produced by a vProg at version 0.6. All fields present in the 0.5 schema are populated with their correct values. No parsing error is raised for unknown fields introduced in 0.6. | | REQ-068 | The vProg deployment configuration exposes a liveness check endpoint that returns HTTP 200 with the body {"status":"live","version":"<N.M>"} when the vProg is operational, and returns no response within 5 seconds when the vProg is non-operational. | 0.1 | None | A GET request to the liveness endpoint of a running vProg returns HTTP 200 with a body matching the pattern {"status":"live","version":"<semver>"} within 1 second. A GET request to the endpoint of a stopped vProg times out after 5 seconds with no response received. |

Cycle 7 total: 2.0 build days | 10 requirements (REQ-059 – REQ-068)