# PVUGC-005: Context Binding - Final Peer Review Report **Issue Code:** PVUGC-005 **Title:** Context Binding **Severity:** 🟢 Low **Status:** ⚠️ Enhanced **Report Version:** 4.0 (Final) **Review Round:** 4 (Final) **Dates:** Identified 2025-10-07; Mitigated 2025-10-07; Peer Reviewed 2025-10-24 **Reviewers:** M1; M2; Lead **Cross-References:** [`PVUGC-2025-10-20.md §1 Introduction`](../PVUGC-2025-10-20); [`report-preliminary-2025-10-07/PVUGC-005-context-binding.md`](../report-preliminary-2025-10-07/PVUGC-005-context-binding); [`report-update-2025-10-07/PVUGC-005-context-binding.md`](../report-update-2025-10-07/PVUGC-005-context-binding) --- ## Executive Summary The context binding mechanism has undergone substantial evolution from v1.0 to v2.0. Mathematician #1 (M1) correctly identified critical binding gaps in v1.0 that permitted CRS substitution and cross-context replay attacks. The v2.0 specification introduces a sophisticated layered hash structure that successfully mitigates these vulnerabilities through domain-separated, cryptographic binding chains. **Verdict:** ✅ **Validated with Enhancement Recommendation**. The v2.0 layered hash design is cryptographically sound and provides practical security against all identified attack vectors. Mathematician #2's (M2) formal analysis confirms correctness under the Random Oracle Model. The proposed enhancement—adding an explicit `epoch_nonce` to `ctx_core`—is **RECOMMENDED** for formal provability and future-proofing, but **v2.0 is deployment-ready** with appropriate testing. **Deployment Guidance:** - **v2.0 (Current):** Deployment-ready with practical security. The `txid_template` provides 2^{-256} collision probability, which is negligible in practice. - **v3.0 (Enhanced):** Recommended for conservative deployments. Adding `epoch_nonce` elevates from practical security to provable security under standard cryptographic assumptions, enabling cleaner formal verification. - **Both paths are technically sound.** The choice is an engineering decision based on deployment timeline and audit requirements. **Key Findings:** - v2.0 closes all v1.0 gaps through layered domain-separated hashing - CRS binding achieved via 3-layer hash chain (header_meta → arming_pkg_hash → ctx_hash) - Cross-context replay prevented by transaction-specific `txid_template` in ctx_core - epoch_nonce enhancement enables clean formal proof (Theorem 4) - Practical security: HIGH; Deployment readiness: ACCEPTABLE (v2.0) or RECOMMENDED (v3.0) --- ## Issue Evolution: v1.0 → v2.0 → v3.0 ### Timeline of Discovery and Resolution **v1.0 (Initial Specification - Pre-October 2025)** - **Status:** Critical binding gaps identified by M1 - **Missing bindings:** GS-CRS hash not in ctx_core, arming_pkg_hash independent of transaction context, no explicit epoch/nonce - **Vulnerabilities:** CRS substitution possible, cross-context arming replay feasible - **Severity:** HIGH (fundamental security property violated) **v2.0 (Current Specification - [`../PVUGC-2025-10-20.md §1 Introduction`](../PVUGC-2025-10-20))** - **Status:** Major improvements implemented - **Changes:** Layered hash structure with explicit domain tags, CRS bound via header_meta chain, normative SHA-256 for H_bytes - **Mitigations:** CRS substitution prevented, cross-context replay blocked by different ctx_hash - **Security:** Practical collision probability 2^{-256} (negligible) - **Deployment:** ACCEPTABLE with comprehensive testing **v3.0 (Proposed Enhancement)** - **Status:** Recommended for formal provability and conservative deployments - **Enhancement:** Add mandatory `epoch_nonce` to ctx_core - **Benefit:** Enables clean formal proof of Context Uniqueness property (Theorem 4); eliminates reliance on implicit uniqueness assumptions - **Deployment:** RECOMMENDED for production mainnet --- ## The v1.0 Binding Gaps ### Critical Missing Bindings Identified by M1 The v1.0 context binding structure had four critical gaps: #### Gap 1: GS-CRS Hash Not Bound in ctx_core **Location:** [`../report-preliminary-2025-10-07/PVUGC-005-context-binding.md`](../report-preliminary-2025-10-07/PVUGC-005-context-binding), lines 45-59 **Issue:** The Groth-Sahai CRS, which defines the bases {U_j, V_k} for the KEM, was not cryptographically bound in the core context hash. While `vk_hash` was included (representing the Groth16 verification key), this does not capture the full CRS structure. **Attack Vector:** ``` CRS Substitution Attack (v1.0) 1. Honest protocol instance initialized with GS_CRS_1 2. Armers publish masks {D₁,ⱼ = U_j^ρᵢ, D₂,ₖ = V_k^ρᵢ} 3. Attacker proposes using GS_CRS_2 (malicious, with known trapdoor) 4. Since GS_CRS hash not in ctx_core, ctx_hash remains unchanged 5. Decapper interprets {D₁, D₂} as powers of bases from GS_CRS_2 6. Attacker with CRS_2 trapdoor computes M = G_G16^ρᵢ 7. Decapsulates without valid proof, breaks No-Proof-Spend property ``` #### Gap 2: Arming Package Independence **Location:** [`../report-preliminary-2025-10-07/PVUGC-005-context-binding.md`](../report-preliminary-2025-10-07/PVUGC-005-context-binding), lines 69-91 **Issue:** The `arming_pkg_hash` was defined as: ``` arming_pkg_hash = H("PVUGC/ARM" || {D₁} || {D₂} || header_meta) ``` This hash did **not** include `ctx_core`, `txid_template`, or any transaction-specific binding. **Attack Vector:** ``` Cross-Context Arming Replay (v1.0) Context A: (vk, x, tx_template_A) Context B: (vk, x, tx_template_B) // same (vk,x), different transaction 1. Armers publish {D₁, D₂, ct_i} for context A 2. Attacker creates context B with different txid_template_B 3. ctx_core_B differs from ctx_core_A (different txid_template) 4. But arming_pkg_hash has no binding to ctx_core 5. If header_meta identical (same vk,x): arming_pkg_hash_A = arming_pkg_hash_B 6. ctx_hash_B = H("..." || ctx_core_B || arming_pkg_hash_A || presig_pkg_hash_B) 7. Potential for artifacts from A to be misused in context B ``` #### Gap 3: No Explicit Epoch or Nonce **Location:** [`../report-preliminary-2025-10-07/PVUGC-005-context-binding.md`](../report-preliminary-2025-10-07/PVUGC-005-context-binding), lines 93-103 **Issue:** No dedicated uniqueness field in ctx_core. Multiple transactions with identical (vk, x, tapleaf_hash, path_tag) could have similar ctx_core values if txid_template structures were similar. **Risk:** While SHA-256 collision resistance makes identical ctx_hash unlikely, formal proofs require explicit uniqueness guarantees, not implicit ones based on transaction structure. #### Gap 4: GS_instance_digest Placement **Location:** [`../report-preliminary-2025-10-07/PVUGC-005-context-binding.md`](../report-preliminary-2025-10-07/PVUGC-005-context-binding), lines 61-67 **Issue:** `GS_instance_digest` appeared in `AD_core` (line 242 of spec) but not in the top-level `ctx_core`. Since this digest includes critical CRS information, it should have been elevated to the highest-level binding. --- ## The v2.0 Layered Hash Structure ### Complete Specification and Analysis The v2.0 design introduces a sophisticated three-layer hash structure with explicit domain separation: #### Layer 1: Core Context (ctx_core) **Specification:** [`../PVUGC-2025-10-20.md §1 Introduction`](../PVUGC-2025-10-20), line 59 ``` ctx_core = H_bytes("PVUGC/CTX_CORE" || vk_hash || H_bytes(x) || tapleaf_hash || tapleaf_version || txid_template || path_tag) ``` **Bindings:** - `vk_hash`: Groth16 verification key (ties to specific circuit) - `H_bytes(x)`: Public input (ties to specific instance) - `tapleaf_hash`: Bitcoin script commitment (ties to specific Taproot leaf) - `tapleaf_version`: BIP-341 version byte (ensures correct script interpretation) - `txid_template`: Complete transaction template (ensures transaction uniqueness) - `path_tag`: Script path identifier ("compute" or "abort") **Purpose:** Establishes immutable foundation tying protocol to specific (circuit, input, transaction, script path). #### Layer 2: Package Hashes **Arming Package Hash** (line 60): ``` arming_pkg_hash = H_bytes("PVUGC/ARM" || {D₁} || {D₂} || header_meta) ``` **Bindings:** - `{D₁,ⱼ}`: G₂ mask elements (KEM public parameters) - `{D₂,ₖ}`: G₁ mask elements (KEM public parameters) - `header_meta`: **Critical improvement** - includes `GS_instance_digest` **header_meta Structure** (line 73): ``` header_meta = deterministic_hash( share_index_i, {D₁,ⱼ}, {D₂,ₖ}, T_i, h_i, ct_i, τ_i, ρ_link, DEM_PROFILE, GS_instance_digest // ← BINDS CRS HERE ) ``` **GS_instance_digest contents** (inferred from spec §6, §10): - Hash(GS_CRS) - Groth-Sahai CRS commitment - Hash(Groth16_CRS) - via vk and public parameters - {U_j(x), V_k(x)} - base point hashes (instance-only) - G_G16(vk,x) - target point hash **Pre-Signature Package Hash** (line 61): ``` presig_pkg_hash = H_bytes("PVUGC/PRESIG" || m || T || R || signer_set || musig_coeffs) ``` **Bindings:** - `m`: BIP-341 SIGHASH_ALL digest (commits to exact transaction) - `T`: Adaptor point (sum of all T_i) - `R`: MuSig2 nonce commitment - `signer_set`: Public keys {X₁, ..., Xₙ} - `musig_coeffs`: Key aggregation coefficients {a₁, ..., aₙ} #### Layer 3: Final Context Hash (ctx_hash) **Specification:** line 62 ``` ctx_hash = H_bytes("PVUGC/CTX" || ctx_core || arming_pkg_hash || presig_pkg_hash) ``` **Critical Property:** Any change to **any** component propagates to ctx_hash: - CRS change → GS_instance_digest changes → header_meta changes → arming_pkg_hash changes → **ctx_hash changes** - Transaction change → txid_template changes → ctx_core changes → **ctx_hash changes** - Signer change → signer_set changes → presig_pkg_hash changes → **ctx_hash changes** ### Domain Separation Analysis **Normative Domain Tags** (lines 80-84): - `"PVUGC/CTX_CORE"` - core context - `"PVUGC/ARM"` - arming package - `"PVUGC/PRESIG"` - pre-signature package - `"PVUGC/CTX"` - final context **Security Property:** Domain tags prevent cross-domain collisions. Even if Hash(data1) = Hash(data2) for raw data, the tagged hashes differ: ``` H("PVUGC/CTX_CORE" || data1) ≠ H("PVUGC/ARM" || data1) ``` This provides defense against length-extension attacks and ensures that data from one context layer cannot be interpreted as data from another. ### Normative Hash Function **Specification:** lines 75-77 ``` H_bytes = SHA-256 (for byte-level context hashes and H(x)) H_p2 = Poseidon2 (for KDF/DEM and in-circuit PoCE) H_tag = Poseidon2 (domain-tagged for ρ_link) ``` **Rationale:** SHA-256 chosen for `H_bytes` because: 1. Widely deployed and audited implementation 2. Hardware acceleration available (Bitcoin full nodes) 3. Collision resistance: 2^{128} security (sufficient for protocol lifetime) 4. Standard in Bitcoin ecosystem (BIP-340 uses tagged SHA-256) --- ## M2's Mathematical Validation ### Summary of Round 1 Findings **Source:** Appendix [A05.M201](APPENDIX-issue-debates.md#a05-m201) M2 conducted formal analysis of the v2.0 design and concluded: #### Primary Validation (lines 25-78) ✅ **Layered Hash Structure is Sound** - The three-layer construction correctly propagates binding constraints - CRS binding via header_meta → arming_pkg_hash chain is cryptographically valid - Domain tags prevent cross-layer attacks - SHA-256 as H_bytes provides adequate collision resistance ✅ **Cross-Context Replay is Prevented** - Different transactions → different txid_template → different ctx_core → different ctx_hash - KDF uses ctx_hash as salt: K_i = Poseidon2(ser(M_i) || H_bytes(ctx_hash) || GS_instance_digest) - Different ctx_hash → different K_i → decryption fails if ciphertext from wrong context ✅ **CRS Substitution is Mitigated** - GS_instance_digest includes CRS hash - Substituting CRS → different GS_instance_digest → different header_meta → different arming_pkg_hash → different ctx_hash - Attack fails at decryption due to key mismatch #### Identified Enhancement Opportunity (lines 25-30) ⚠️ **Lack of Explicit Nonce Weakens Formal Proof** While the v2.0 design is practically secure, M2 identified that formal provability requires an explicit uniqueness guarantee: **Current reliance:** Protocol assumes `txid_template` provides uniqueness **Issue:** This is an implicit property, not a formal guarantee - Two protocol instances could theoretically have identical (vk, x, txid_template) if initiated with same parameters - Formal proof must assume "inputs never collide" (weaker security model) - Best practice: Explicit nonce for provable uniqueness, not implicit uniqueness **Quote** (lines 27-30): > "Without a dedicated nonce, a formal proof of context uniqueness must make the assumption that `(vk, x, txid_template, ...)` will never be identical for two distinct instances, which is a weaker security model." --- ## Adversarial Cryptanalysis ### Attack Scenario 5.1: Cross-Context Arming Replay **Severity:** HIGH (v1.0) → LOW (v2.0) **Status:** Mitigated in v2.0 #### Attack Setup ``` Context A: (vk, x, tx_template_A) with arming artifacts {D₁, D₂, ct_i} Context B: (vk, x, tx_template_B) with different transaction template ``` #### v1.0 Attack Execution ```pseudocode FUNCTION attack_cross_context_replay_v1(): // Step 1: Honest arming for context A ctx_core_A = H("PVUGC/CTX_CORE" || vk || x || ... || tx_template_A) arming_pkg_hash_A = H("PVUGC/ARM" || {D₁} || {D₂} || header_meta_A) // Step 2: Create context B with different transaction ctx_core_B = H("PVUGC/CTX_CORE" || vk || x || ... || tx_template_B) // ctx_core_B ≠ ctx_core_A (different tx_template) // Step 3: Check if arming artifacts reusable IF header_meta_A == header_meta_B: // same (vk,x) → same header_meta arming_pkg_hash_B = arming_pkg_hash_A // REPLAY POSSIBLE ctx_hash_B = H("CTX" || ctx_core_B || arming_pkg_hash_A || presig_B) // Arming from A used in context B! // Step 4: Attempt decapsulation K_B = KDF(ctx_hash_B, M_i, ...) TRY decrypt(ct_i, K_B) // May succeed or fail depending on KDF design ``` **v1.0 Vulnerability:** arming_pkg_hash has no binding to ctx_core, allowing potential replay if header_meta identical. #### v2.0 Mitigation ```pseudocode FUNCTION verify_v2_mitigation(): // v2.0 layered hash structure ctx_core_A = H_bytes("PVUGC/CTX_CORE" || ... || tx_template_A) ctx_core_B = H_bytes("PVUGC/CTX_CORE" || ... || tx_template_B) // ctx_core_A ≠ ctx_core_B (different tx_template) arming_pkg_hash_A = H_bytes("PVUGC/ARM" || {D₁} || {D₂} || header_meta_A) // Assume arming_pkg_hash_A == arming_pkg_hash_B (same vk,x,CRS) presig_pkg_hash_A = H_bytes("PVUGC/PRESIG" || m_A || ...) presig_pkg_hash_B = H_bytes("PVUGC/PRESIG" || m_B || ...) // presig_pkg_hash_A ≠ presig_pkg_hash_B (different message m) ctx_hash_A = H_bytes("PVUGC/CTX" || ctx_core_A || arming_pkg_hash_A || presig_pkg_hash_A) ctx_hash_B = H_bytes("PVUGC/CTX" || ctx_core_B || arming_pkg_hash_A || presig_pkg_hash_B) // Key derivation K_A = Poseidon2(ser(M_i) || H_bytes(ctx_hash_A) || GS_instance_digest) K_B = Poseidon2(ser(M_i) || H_bytes(ctx_hash_B) || GS_instance_digest) // Security claim ASSERT ctx_hash_A ≠ ctx_hash_B // different ctx_core OR different presig ASSERT K_A ≠ K_B // different ctx_hash → different K ASSERT decrypt(ct_i, K_B) = FAIL // ct_i encrypted with K_A RETURN "Replay attack fails at decryption step ✅" ``` **Mitigation Mechanism:** 1. Even if arming_pkg_hash_A == arming_pkg_hash_B (same CRS, vk, x) 2. ctx_core differs (different tx_template) OR presig_pkg_hash differs (different m) 3. This propagates to ctx_hash: ctx_hash_A ≠ ctx_hash_B 4. KDF uses ctx_hash as binding salt → different keys 5. Decryption with wrong key fails with overwhelming probability **Complexity Analysis:** - Arming operations: Same as honest protocol (no additional cost) - Attack detection: Automatic (decryption failure, no explicit check needed) - Success probability: Negligible (requires SHA-256 collision: 2^{-256}) ### Attack Scenario 5.2: CRS Substitution **Severity:** CRITICAL (v1.0) → LOW (v2.0) **Status:** Mitigated in v2.0 #### Attack Description Attacker attempts to substitute the honest Groth-Sahai CRS with a malicious one for which they know the trapdoor, enabling them to compute M_i without a valid proof. #### v1.0 Attack ```pseudocode FUNCTION attack_crs_substitution_v1(): // Setup: Honest protocol using GS_CRS_1 GS_CRS_1 = honest_crs_generation() {U_j, V_k} = derive_bases(GS_CRS_1, x) // Armers publish masks D₁ⱼ = U_j^ρᵢ (for all j) D₂ₖ = V_k^ρᵢ (for all k) // Attacker generates malicious CRS with known trapdoor (GS_CRS_2, trapdoor_τ) = malicious_crs_generation() {U'_j, V'_k} = derive_bases(GS_CRS_2, x) // v1.0: GS_CRS not bound in ctx_core ctx_hash_v1 = H("CTX" || ctx_core || arming_pkg || presig_pkg) // ctx_hash unchanged by CRS substitution! // Attacker reinterprets masks as powers of malicious bases D₁ⱼ interpreted as U'_j^ρᵢ (algebraically incorrect!) D₂ₖ interpreted as V'_k^ρᵢ (algebraically incorrect!) // With trapdoor, compute M without proof M_i = compute_with_trapdoor(trapdoor_τ, {D₁}, {D₂}, G_G16) K_i = KDF(ctx_hash_v1, M_i, ...) s_i = decrypt(ct_i, K_i) RETURN "Attack succeeds - No-Proof-Spend violated ❌" ``` #### v2.0 Mitigation ```pseudocode FUNCTION verify_crs_binding_v2(): // Honest protocol GS_CRS_1 = honest_crs_generation() GS_instance_digest_1 = H(GS_CRS_1 || vk || x || {U_j} || {V_k} || G_G16) header_meta_1 = H("header" || share_i || {D₁} || {D₂} || T_i || ct_i || τ_i || ... || GS_instance_digest_1) // ← CRS BOUND HERE arming_pkg_hash_1 = H_bytes("PVUGC/ARM" || {D₁} || {D₂} || header_meta_1) ctx_hash_1 = H_bytes("PVUGC/CTX" || ctx_core || arming_pkg_hash_1 || presig_pkg_hash) // Attacker attempts CRS substitution GS_CRS_2 = malicious_crs_with_trapdoor() GS_instance_digest_2 = H(GS_CRS_2 || vk || x || {U'_j} || {V'_k} || G_G16) // Substitution detected ASSERT GS_instance_digest_1 ≠ GS_instance_digest_2 // different CRS header_meta_2 = H("header" || ... || GS_instance_digest_2) ASSERT header_meta_1 ≠ header_meta_2 // different digest arming_pkg_hash_2 = H_bytes("PVUGC/ARM" || {D₁} || {D₂} || header_meta_2) ASSERT arming_pkg_hash_1 ≠ arming_pkg_hash_2 // different header_meta ctx_hash_2 = H_bytes("PVUGC/CTX" || ctx_core || arming_pkg_hash_2 || presig_pkg_hash) ASSERT ctx_hash_1 ≠ ctx_hash_2 // propagated difference // Key derivation fails K_1 = KDF(ctx_hash_1, M_i, ...) K_2 = KDF(ctx_hash_2, M_i, ...) ASSERT K_1 ≠ K_2 // different ctx_hash → different keys // Attacker attempts decryption TRY s_i = decrypt(ct_i, K_2) // ct_i was encrypted with K_1 ASSERT decrypt_fails // wrong key RETURN "CRS substitution detected and prevented ✅" ``` **Three-Layer Binding Chain:** ``` GS_CRS_1 → GS_instance_digest_1 → header_meta_1 → arming_pkg_hash_1 → ctx_hash_1 → K_1 (KDF key) ``` Any change to the CRS propagates through all layers, changing the final KDF key and preventing decryption. **Success Condition for Attack:** Attacker must find SHA-256 collision such that: ``` H(GS_CRS_1 || ...) = H(GS_CRS_2 || ...) ``` Probability: 2^{-256} (computationally infeasible) ### Attack Scenario 5.3: Epoch Collision **Severity:** NEGLIGIBLE (v2.0) → ZERO (v3.0 with epoch_nonce) **Status:** Already low risk, eliminated by enhancement #### Scenario Description Two protocol instances with identical (vk, x) and similar transaction structures could theoretically have colliding ctx_hash values. #### Analysis ```pseudocode FUNCTION analyze_epoch_collision(): // Two transactions with same circuit and input Instance_1: (vk, x, tx_template_1) at time t=0 Instance_2: (vk, x, tx_template_2) at time t=1 // For collision, need identical ctx_core ctx_core_1 = H_bytes("PVUGC/CTX_CORE" || vk_hash || H(x) || tapleaf_hash || tapleaf_version || txid_template_1 || path_tag) ctx_core_2 = H_bytes("PVUGC/CTX_CORE" || vk_hash || H(x) || tapleaf_hash || tapleaf_version || txid_template_2 || path_tag) // For collision: need txid_template_1 == txid_template_2 // txid_template includes: // - Input UTXOs (txid:vout) // - Output scriptPubKeys and amounts // - Locktime, sequence numbers // Probability analysis P(txid_template_1 == txid_template_2) requires: - Same input UTXOs (virtually impossible - different tx history) - Same outputs (unlikely - different recipients/amounts) - Same timing parameters (possible but combined improbable) // Even if templates similar P(collision | similar_templates) = P(SHA256 collision) = 2^{-256} // Edge case: Identical parameters except txid_template // (See Edge Case Analysis section below) RETURN "Collision risk: negligible without explicit nonce" ``` **Edge Case Analysis (Identical Parameters):** Consider the limiting case where two instances have identical: - vk (same circuit) - x (same public input) - tapleaf_hash (same script) - tapleaf_version (same version) - path_tag (same path) But different: - txid_template (different transactions) **v2.0 security argument:** ``` ctx_core_1 = H_bytes("PVUGC/CTX_CORE" || vk || x || ... || txid_template_1) ctx_core_2 = H_bytes("PVUGC/CTX_CORE" || vk || x || ... || txid_template_2) Since txid_template_1 ≠ txid_template_2, the inputs differ. P(ctx_core_1 = ctx_core_2) = 2^{-256} under ROM (SHA-256 collision) This is negligible and provides practical security. The txid_template includes UTXOs (transaction IDs and output indices), outputs (scriptPubKeys and amounts), and timing parameters. For two distinct transactions to have identical txid_template values would require: - Identical input set (same UTXOs being spent) - Identical output set (same recipients and amounts) - Identical locktime and sequence numbers This is extremely unlikely in practice (probability ~2^{-256} even before considering SHA-256 collision resistance). ``` **v3.0 enhancement:** ``` Even if txid_template_1 = txid_template_2 (user error, identical tx structure), different epoch_nonce values ensure ctx_core_1 ≠ ctx_core_2 with probability 1. This provides deterministic uniqueness rather than probabilistic uniqueness, eliminating reliance on transaction structure for domain separation. ``` #### v3.0 Enhancement ```pseudocode FUNCTION epoch_nonce_enhancement(): // v3.0 ctx_core with explicit nonce ctx_core = H_bytes("PVUGC/CTX_CORE" || vk_hash || H(x) || tapleaf_hash || tapleaf_version || txid_template || path_tag || epoch_nonce) // ← NEW FIELD // epoch_nonce specification epoch_nonce = { size: 256 bits, generation: CSPRNG (OS entropy pool), uniqueness: MUST differ for every protocol instance, binding: included in NUMS key derivation (line 46) } // Collision analysis Instance_1: nonce_1 = random(256 bits) Instance_2: nonce_2 = random(256 bits) P(nonce_1 == nonce_2) = 2^{-256} (birthday bound: √(2^{256}) = 2^{128} instances) // For collision in ctx_core REQUIRE: nonce_1 == nonce_2 AND all other fields match P(collision) = 2^{-256} * P(other_fields_match) ≈ 2^{-256} (negligible) RETURN "Formal uniqueness guarantee achieved" ``` --- ## Layered Hash Security Analysis ### Domain Tag Security **Theorem (Domain Separation):** For a hash function H modeled as a random oracle and distinct domain tags T₁, T₂: ``` P(H(T₁ || data₁) = H(T₂ || data₂)) ≤ 2^{-256} ``` even if data₁ = data₂. **Notation Note:** Throughout this document, we use 2^{-256} to denote the probability 1/2^{256}, corresponding to the collision probability for a 256-bit hash function under the Random Oracle Model. **Proof Sketch:** 1. Random oracle: outputs uniformly random for any new input 2. T₁ ≠ T₂ → (T₁ || data₁) ≠ (T₂ || data₂) as bit strings 3. Different inputs → independent random outputs 4. Collision probability = 1/2^{256} = 2^{-256} (output space size) **Application to PVUGC:** - `"PVUGC/CTX_CORE"`, `"PVUGC/ARM"`, `"PVUGC/PRESIG"`, `"PVUGC/CTX"` are distinct - Data from one layer cannot collide with data from another layer - Prevents cross-domain attacks and length-extension exploits ### CRS Binding via Three-Layer Chain **Binding Path:** ``` GS_CRS (actual cryptographic parameters) ↓ (hash) GS_instance_digest (commitment to CRS + bases + target) ↓ (included in) header_meta (commitment to KEM public parameters + CRS digest) ↓ (included in) arming_pkg_hash (commitment to arming artifacts + header_meta) ↓ (included in) ctx_hash (final context commitment) ↓ (used as salt in) K_i = Poseidon2(ser(M_i) || H_bytes(ctx_hash) || GS_instance_digest) ``` **Security Property:** Each layer cryptographically commits to all previous layers. Breaking the binding requires: 1. Finding SHA-256 collision at any layer (probability: 2^{-256} per layer) 2. OR finding Poseidon2 collision in KDF (probability: 2^{-128} per Poseidon2 security level) **Total Security:** Under Random Oracle Model, breaking any binding in the chain requires breaking at least one hash function. Union bound: ``` P(break_binding) ≤ P(break_SHA256) + P(break_Poseidon2) ≤ 2^{-256} + 2^{-128} = 2^{-128} (dominated by Poseidon2 term) ``` For 128-bit security target, this is acceptable. For 256-bit security, Poseidon2 parameters should be chosen to match SHA-256 collision resistance. ### Defense-in-Depth Properties The layered structure provides multiple independent barriers: **Layer 1 (ctx_core):** Prevents transaction/circuit/input substitution - Change vk → different ctx_core → attack fails - Change x → different ctx_core → attack fails - Change tx → different ctx_core → attack fails **Layer 2 (arming_pkg_hash):** Prevents CRS/arming substitution - Change CRS → different GS_instance_digest → different header_meta → different arming_pkg_hash → attack fails - Change masks {D₁, D₂} → different arming_pkg_hash → attack fails **Layer 3 (presig_pkg_hash):** Prevents signature/signer substitution - Change message m → different presig_pkg_hash → attack fails - Change signers → different signer_set → different presig_pkg_hash → attack fails **Layer 4 (ctx_hash):** Final aggregation - Any change at any lower layer propagates to ctx_hash - ctx_hash used as binding salt in KDF - Different ctx_hash → different K_i → decryption fails **Redundancy:** Even if one layer's binding were weak, other layers provide backup security. --- ## Context Uniqueness Formalization ### Definition **Context Uniqueness Property:** A context binding scheme provides context uniqueness if for any two distinct, validly generated protocol instances I₁ and I₂, the probability that ctx_hash(I₁) = ctx_hash(I₂) is negligible. **Formal Statement:** ``` For all PPT adversaries A, all security parameters λ: P[ctx_hash(I₁) = ctx_hash(I₂) | I₁ ← A(1^λ), I₂ ← A(1^λ), I₁ ≠ I₂] ≤ negl(λ) ``` where negl(λ) = 2^{-λ} for λ = 256 (SHA-256 output size). ### Theorem 4: Provable Context Uniqueness with Nonce **Source:** M2 Round 1 analysis, lines 57-71 **Statement:** Let H_bytes be a hash function modeled as a random oracle. Let the PVUGC protocol be instantiated with ctx_core construction including a unique, randomly generated epoch_nonce for each instance. Then, for any two distinct instances I₁ and I₂, the probability that ctx_hash(I₁) = ctx_hash(I₂) is negligible. **Proof Sketch:** 1. **Random Oracle Assumption:** Model H_bytes (SHA-256) as a random oracle. For any new input, output is uniformly random in {0,1}^{256}, independent of all other outputs. *Note on Random Oracle Model:* The Random Oracle Model is a heuristic assumption: SHA-256 is not a true random oracle, but is believed to satisfy the properties needed for this proof (collision resistance, output independence for distinct inputs, and domain separation via prefixing). The ROM is standard in cryptographic security proofs and is appropriate for analyzing SHA-256 in this context. We model H_bytes (SHA-256) as a random oracle, but this does not require the entire protocol to be in the ROM—only the hash function used for ctx_hash construction. 2. **Instance Setup:** Consider two distinct protocol instances I₁ and I₂. By definition of "distinct instances," they must differ in at least one parameter. With epoch_nonce mandate, their nonces differ: n₁ ≠ n₂. 3. **Layer 1 Analysis (ctx_core):** ``` Input₁ = "PVUGC/CTX_CORE" || vk_hash || H(x) || tapleaf_hash || tapleaf_version || txid_template || path_tag || n₁ Input₂ = "PVUGC/CTX_CORE" || vk_hash || H(x) || tapleaf_hash || tapleaf_version || txid_template || path_tag || n₂ ``` Since n₁ ≠ n₂, we have Input₁ ≠ Input₂ (as bit strings). By Random Oracle property: ``` P(H_bytes(Input₁) = H_bytes(Input₂)) = 1/2^{256} = 2^{-256} ``` Therefore: ctx_core₁ ≠ ctx_core₂ with probability 1 - 2^{-256}. 4. **Layer 2 Propagation:** ``` arming_pkg_hash_i = H_bytes("PVUGC/ARM" || {D₁} || {D₂} || header_meta_i) ``` Even if arming artifacts identical, header_meta differs if protocol instances differ in any parameter (included in metadata). But we already have ctx_core₁ ≠ ctx_core₂ from step 3. 5. **Layer 3 Propagation:** ``` presig_pkg_hash_i = H_bytes("PVUGC/PRESIG" || m_i || T_i || R_i || signer_set_i || musig_coeffs_i) ``` Different instances have different messages m_i (different transactions), so presig_pkg_hash₁ ≠ presig_pkg_hash₂. **Context on birthday bound:** For reference, the birthday bound for SHA-256 collision is approximately 2^{128} queries for 50% collision probability. For epoch_nonce-based uniqueness across N protocol instances, collision probability is approximately N²/2^{257}. By the birthday paradox, for N randomly chosen nonces from a space of size 2^{256}, P(any collision) ≈ N(N-1)/(2·2^{256}) ≈ N²/2^{257} for large N. For N = 2^{40} instances (approximately 1 trillion), this is approximately (2^{40})²/2^{257} = 2^{80}/2^{257} = 2^{-177}, which is still negligible. 6. **Final Layer Analysis:** ``` ctx_hash₁ = H_bytes("PVUGC/CTX" || ctx_core₁ || arming_pkg_hash₁ || presig_pkg_hash₁) ctx_hash₂ = H_bytes("PVUGC/CTX" || ctx_core₂ || arming_pkg_hash₂ || presig_pkg_hash₂) ``` Since ctx_core₁ ≠ ctx_core₂ (from step 3), the inputs to H_bytes differ. By Random Oracle property: ``` P(ctx_hash₁ = ctx_hash₂) = 1/2^{256} = 2^{-256} ``` 7. **Security Consequence:** Different ctx_hash values lead to different KDF keys: ``` K₁ = Poseidon2(ser(M) || H_bytes(ctx_hash₁) || GS_instance_digest) K₂ = Poseidon2(ser(M) || H_bytes(ctx_hash₂) || GS_instance_digest) ``` With overwhelming probability K₁ ≠ K₂, ensuring: - Ciphertext from I₁ cannot be decrypted in I₂ - Arming artifacts cryptographically isolated between instances - No cross-context replay possible **Conclusion:** The probability of ctx_hash collision is negligible (2^{-256}), providing formal guarantee of context uniqueness and cryptographic isolation between protocol instances. ∎ ### Comparison: v2.0 (Implicit) vs v3.0 (Explicit) Uniqueness **v2.0 Security Argument (Implicit Uniqueness):** - Relies on txid_template providing instance uniqueness - Assumption: "No two protocol instances will have identical (vk, x, txid_template)" - Security holds in practice (transaction IDs are unique in Bitcoin) - Formal proof requires assumption about transaction structure (external to protocol) **v3.0 Security Argument (Explicit Uniqueness):** - epoch_nonce provides direct cryptographic uniqueness guarantee - No assumptions about transaction structure needed - Formal proof relies only on Random Oracle Model for H_bytes - Cleaner security model: uniqueness enforced by protocol, not by Bitcoin layer **Recommendation:** v3.0 enhancement preferred for: 1. Formal verification (cleaner proof) 2. Future-proofing (independent of Bitcoin transaction format changes) 3. Defense-in-depth (explicit uniqueness + implicit txid uniqueness) --- ## epoch_nonce Specification ### Normative Requirements **Proposed Specification Update** (for §3, line 59): ``` ctx_core = H_bytes("PVUGC/CTX_CORE" || vk_hash || H_bytes(x) || tapleaf_hash || tapleaf_version || txid_template || path_tag || epoch_nonce) ``` **epoch_nonce Definition:** ``` epoch_nonce (RECOMMENDED): A 256-bit value that, if implemented, MUST be unique for every protocol instance. Status: RECOMMENDED for formal provability; OPTIONAL for v2.0 practical security. If included in an implementation, all requirements below become MUST. MUST Requirements (if implemented): 1. Size: Exactly 256 bits (32 bytes) 2. Generation: Cryptographically secure pseudorandom number generator (CSPRNG) 3. Uniqueness: MUST NOT be reused across any two protocol instances 4. Binding: MUST be included in: - ctx_core (primary binding) - NUMS internal key derivation (line 46 of spec) - Transaction metadata (for verification) 5. Verification: Protocol participants MUST verify uniqueness before arming SHOULD Requirements: 1. Entropy source: OS-provided CSPRNG (/dev/urandom, BCryptGenRandom, etc.) 2. Derivation: MAY be derived from high-entropy seed + counter 3. Persistence: SHOULD be stored in transaction metadata for auditability 4. Distribution: SHOULD be transmitted in protocol setup phase ``` ### Generation Requirements **Implementation Guidance:** ```pseudocode FUNCTION generate_epoch_nonce() -> bytes[32]: // Method 1: Direct CSPRNG (RECOMMENDED) nonce = OS_CSPRNG(size=32) // /dev/urandom, getrandom(), etc. ASSERT is_high_entropy(nonce) // Check entropy pool ASSERT nonce NOT IN used_nonces // Check uniqueness database used_nonces.add(nonce) // Prevent reuse RETURN nonce FUNCTION generate_epoch_nonce_deterministic(seed, counter) -> bytes[32]: // Method 2: Deterministic derivation (ACCEPTABLE for testing) // Use when reproducibility needed (e.g., test vectors) REQUIRE length(seed) >= 32 // High-entropy seed REQUIRE counter >= 0 // Monotonic counter nonce = HKDF-Expand( prk = HKDF-Extract(salt="PVUGC/EPOCH_NONCE", ikm=seed), info = "instance" || encode_uint64(counter), L = 32 ) RETURN nonce ``` **Entropy Sources:** - Linux: `/dev/urandom`, `getrandom()` syscall - Windows: `BCryptGenRandom()`, `RtlGenRandom()` - macOS: `/dev/urandom`, `getentropy()` - Hardware: RDRAND instruction (x86), TRNG modules **Entropy Verification:** ```pseudocode FUNCTION is_high_entropy(nonce) -> bool: // Basic sanity checks (NOT cryptographic guarantees) // Check 1: Not all zeros IF nonce == bytes(32, 0): RETURN false // Check 2: Not all ones IF nonce == bytes(32, 0xFF): RETURN false // Check 3: Hamming weight (should be ~128 for random) hamming = popcount(nonce) IF hamming < 96 OR hamming > 160: LOG_WARNING("Suspicious nonce entropy") // Check 4: Not a known bad value IF nonce IN bad_nonce_database: RETURN false RETURN true ``` ### Storage and Distribution **Transaction Metadata Inclusion:** The epoch_nonce MUST be included in the transaction structure for verification. Recommended placement: ``` Option 1: OP_RETURN output - scriptPubKey: OP_RETURN - Cost: 32 bytes on-chain (permanent) - Benefit: Immutable, verifiable by all parties Option 2: Witness data (recommended) - Include in witness stack (not in txid) - Cost: 32 bytes witness data (4x weight reduction) - Benefit: Lower fees, still verifiable Option 3: Off-chain commitment - Publish epoch_nonce in protocol setup message - Include H(epoch_nonce) in ctx_core - Cost: 0 on-chain bytes - Drawback: Requires off-chain data availability ``` **Distribution Protocol:** ```pseudocode PHASE 1: Initialization 1. Protocol initiator generates epoch_nonce 2. Initiator broadcasts nonce to all participants: - Include in setup message - Signed by initiator key - Timestamp for ordering PHASE 2: Verification 3. Each participant verifies: - Nonce size == 32 bytes - Nonce not in local used_nonces database - Nonce passes is_high_entropy() checks - Signature valid PHASE 3: Commitment 4. All participants include nonce in ctx_core computation 5. Nonce included in NUMS key derivation (spec line 46) 6. Nonce stored in transaction metadata PHASE 4: Arming 7. Arming proceeds only if all participants acknowledge nonce 8. Any participant MAY abort if nonce reuse detected ``` ### Verification Requirements **Pre-Arming Verification Checklist:** ```pseudocode FUNCTION verify_epoch_nonce_before_arming(nonce, ctx) -> bool: // Check 1: Nonce size IF length(nonce) != 32: REJECT "Invalid nonce size" // Check 2: Entropy sanity IF NOT is_high_entropy(nonce): REJECT "Low entropy nonce" // Check 3: Uniqueness (local database) IF nonce IN local_used_nonces: REJECT "Nonce reused (local)" // Check 4: Uniqueness (blockchain scan - optional) IF blockchain_contains_nonce(nonce): REJECT "Nonce reused (on-chain)" // Check 5: Matches all participants' nonce FOR participant IN ctx.participants: IF participant.nonce != nonce: REJECT "Nonce mismatch across participants" // Check 6: Included in ctx_core recomputed_ctx_core = H_bytes("PVUGC/CTX_CORE" || ... || nonce) IF recomputed_ctx_core != ctx.ctx_core: REJECT "Nonce not properly bound in ctx_core" // Check 7: Included in NUMS key expected_nums = derive_nums_key(vk, x, tapleaf, version, epoch_nonce=nonce) IF expected_nums != ctx.internal_key: REJECT "Nonce not in NUMS derivation" // All checks passed local_used_nonces.add(nonce) RETURN true ``` **Uniqueness Database:** ``` Structure: - Key: epoch_nonce (32 bytes) - Value: { ctx_hash: bytes[32], timestamp: unix_time, tx_hash: bytes[32] (if broadcast), status: {pending, confirmed, aborted} } Operations: - INSERT: Add new nonce (reject if exists) - LOOKUP: Check if nonce used - EXPIRE: Remove nonces for aborted/expired instances (cleanup) Implementation: - In-memory: Hash map for active instances - Persistent: SQLite/LevelDB for historical instances - Distributed: Gossip protocol for multi-party coordination ``` ### Security Analysis of epoch_nonce **Collision Probability:** ``` Single instance: P(collision) = 0 (unique by construction) Multiple instances (birthday paradox): For N protocol instances, each with a uniformly random 256-bit epoch_nonce: P(any collision among N nonces) ≈ N(N-1)/(2·2^{256}) ≈ N²/2^{257} for large N This is the standard birthday bound for random sampling from a space of size 2^{256}. Concrete examples: For N = 2^{64} instances (18 quintillion): P(collision) ≈ (2^{64})²/2^{257} = 2^{128}/2^{257} = 2^{-129} (negligible) For N = 2^{80} instances (security parameter): P(collision) ≈ (2^{80})²/2^{257} = 2^{160}/2^{257} = 2^{-97} (negligible) The birthday bound shows that even with an astronomical number of protocol instances (2^{80} is far beyond any realistic deployment), the collision probability remains negligible. ``` **Grinding Attack Resistance:** ``` Attack: Adversary generates many nonces, selects one with special property Defense: - Nonce must be chosen before ctx_core finalized - Nonce commits to transaction parameters (included in ctx_core) - Changing nonce invalidates all downstream computations - Cost of grinding: O(2^k) for k-bit property Conclusion: Grinding provides no advantage (nonce binding prevents selective disclosure) ``` **Replay Protection:** ``` Cross-instance replay: - Different nonce → different ctx_core → different ctx_hash → different K_i - Ciphertext ct_i encrypted with K_i_old cannot decrypt with K_i_new - Attack fails at decryption step Same-instance replay: - Same nonce → same ctx_core (by construction) - This is the honest protocol execution (not an attack) - Multiple signing attempts with same instance allowed ``` --- ## Recommendations ### Priority 1: RECOMMENDED - Enhanced Normative Specification (Not Blocking) #### R1.1: Add epoch_nonce to ctx_core **Classification:** RECOMMENDED for v3.0, OPTIONAL for v2.0 deployment **Owner:** Protocol specification authors **Timeline:** 2 weeks for spec update, 1 month for implementation **Deliverable:** Updated §3 of PVUGC specification **Deployment Decision:** - **Conservative path:** Implement epoch_nonce before mainnet (v3.0) - formal provability - **Aggressive path:** Deploy v2.0 with comprehensive testing, add epoch_nonce in later version - practical security - **Both approaches are technically sound.** v2.0 provides negligible collision probability (2^{-256}) via txid_template uniqueness. v3.0 adds formal proof clarity and future-proofing. If epoch_nonce is implemented, all requirements in this section become MUST. **Action:** ```markdown Update line 59 of PVUGC-2025-10-20.md §1 Introduction: OLD: ctx_core = H_bytes("PVUGC/CTX_CORE" || vk_hash || H_bytes(x) || tapleaf_hash || tapleaf_version || txid_template || path_tag) NEW: ctx_core = H_bytes("PVUGC/CTX_CORE" || vk_hash || H_bytes(x) || tapleaf_hash || tapleaf_version || txid_template || path_tag || epoch_nonce) Add new subsection after line 77: ### epoch_nonce (RECOMMENDED) A 256-bit cryptographically random value that, if implemented, MUST be unique for every protocol instance. **Status:** RECOMMENDED for formal provability; OPTIONAL for v2.0 practical security. If included in an implementation, all requirements below become MUST. **Generation:** Use a CSPRNG seeded from OS entropy pool. Example implementations: - Linux: `getrandom(buf, 32, GRND_NONBLOCK)` - BSD/macOS: `getentropy(buf, 32)` - Windows: `BCryptGenRandom(NULL, buf, 32, BCRYPT_USE_SYSTEM_PREFERRED_RNG)` **Verification:** Implementations MUST verify that epoch_nonce: 1. Has not been used in any prior protocol instance 2. Is exactly 32 bytes 3. Passes basic entropy checks (not all-zeros, not all-ones) **Binding:** The epoch_nonce MUST be included in: - ctx_core hash (primary binding) - NUMS internal key derivation (line 46) - Transaction metadata (witness or OP_RETURN) **Uniqueness enforcement:** Maintain a database of used nonces. Reject any protocol instance that attempts to reuse a nonce. ``` **Success Criteria:** - [ ] Specification updated with normative epoch_nonce requirement - [ ] Test vectors updated with epoch_nonce field - [ ] Reference implementation includes nonce generation and verification - [ ] Uniqueness database documented #### R1.2: Mandate Comprehensive Cross-Context Replay Testing **Owner:** Security testing team **Timeline:** 2 months **Deliverable:** Test suite with 100+ negative test cases **Test Categories:** ```pseudocode // Category 1: Same (vk,x), different transaction TEST cross_context_same_circuit(): vk = generate_groth16_vk() x = [1, 2, 3, ...] tx_A = create_transaction(inputs_A, outputs_A) tx_B = create_transaction(inputs_B, outputs_B) // Honest arming for context A ctx_A = initialize_context(vk, x, tx_A) {D1_A, D2_A, ct_A} = arm_context(ctx_A) presig_A = presign_context(ctx_A) // Attempt replay in context B ctx_B = initialize_context(vk, x, tx_B) TRY: // Reuse arming from A ctx_B.arming = {D1_A, D2_A, ct_A} ctx_B.presig = presign_context(ctx_B) // Attempt decapsulation proof = generate_valid_proof(vk, x, witness) alpha = decapsulate(ctx_B, proof) ASSERT_FAIL "Replay succeeded - VULNERABILITY" EXCEPT DecryptionError: PASS "Replay blocked by different ctx_hash ✅" // Category 2: CRS substitution attempt TEST crs_substitution(): (CRS_1, bases_1) = generate_honest_crs() (CRS_2, trapdoor_2) = generate_malicious_crs() // Honest arming with CRS_1 ctx = initialize_context(vk, x, tx, CRS_1) {D1, D2, ct} = arm_context(ctx) TRY: // Substitute CRS_2 ctx_malicious = copy(ctx) ctx_malicious.crs = CRS_2 // Attacker uses trapdoor to compute M M_malicious = compute_with_trapdoor(trapdoor_2, D1, D2) K_malicious = kdf(ctx_malicious.ctx_hash, M_malicious) alpha = decrypt(ct, K_malicious) ASSERT_FAIL "CRS substitution succeeded - VULNERABILITY" EXCEPT DecryptionError: PASS "CRS substitution blocked ✅" // Category 3: Nonce reuse (if epoch_nonce implemented) TEST epoch_nonce_reuse(): nonce = generate_epoch_nonce() // First instance ctx_1 = initialize_context(vk, x, tx_1, nonce) arm_context(ctx_1) TRY: // Attempt second instance with same nonce ctx_2 = initialize_context(vk, x, tx_2, nonce) // Same nonce! arm_context(ctx_2) ASSERT_FAIL "Nonce reuse allowed - VULNERABILITY" EXCEPT NonceReuseError: PASS "Nonce reuse rejected ✅" // Category 4: Pre-signature reuse TEST presig_reuse(): // Two contexts, same everything except transaction ctx_A = initialize_context(vk, x, tx_A) ctx_B = initialize_context(vk, x, tx_B) arm_context(ctx_A) arm_context(ctx_B) presig_A = presign_context(ctx_A) TRY: // Reuse pre-signature from A in B ctx_B.presig = presig_A finalize_signature(ctx_B, presig_A, alpha) ASSERT_FAIL "Pre-sig reuse allowed - VULNERABILITY" EXCEPT VerificationError: PASS "Pre-sig reuse rejected (wrong message) ✅" // Continue with 96+ more test cases covering: // - Different CRS, same (vk,x) // - Same CRS, different vk // - Partial parameter changes // - Multi-CRS AND-ing edge cases // - Signer set changes // - Transaction structure variations // - etc. ``` **Test Matrix:** ``` Dimensions to test (combinatorial): 1. Circuit (vk): Same, Different 2. Input (x): Same, Different 3. Transaction: Same, Different structure, Different outputs 4. CRS: Same, Different, Malicious 5. Epoch nonce: Same (should fail), Different 6. Signers: Same, Different, Subset 7. Timing: Sequential, Concurrent Total test cases: 7 dimensions × ~15 variations each = ~100 tests ``` **Success Criteria:** - [ ] All replay attempts fail - [ ] Failures occur at correct layer (decryption, not signature verification) - [ ] No false positives (honest protocol succeeds) - [ ] Test coverage > 95% of code paths ### Priority 2: SHOULD - Comprehensive Testing & Documentation #### R2.1: Produce Test Vectors for ctx_hash Computation **Owner:** Protocol designers + implementation teams **Timeline:** 3 weeks **Deliverable:** Canonical test vectors (JSON format) **Test Vector Structure:** ```json { "test_vectors": [ { "id": "ctx_hash_001", "description": "Basic context hash with all standard parameters", "inputs": { "vk_hash": "0x1234567890abcdef...", "x": ["0x01", "0x02", "0x03"], "tapleaf_hash": "0xabcdef1234567890...", "tapleaf_version": "0xc0", "txid_template": "0xfedcba9876543210...", "path_tag": "0x01", "epoch_nonce": "0x0000000000000000000000000000000000000000000000000000000000000001", "D1": ["0x...", "0x...", ...], "D2": ["0x...", "0x...", ...], "header_meta": "0x...", "m": "0x...", "T": "0x...", "R": "0x...", "signer_set": ["0x...", "0x..."], "musig_coeffs": ["0x...", "0x..."] }, "expected_outputs": { "ctx_core": "0x8a7f3b2c1d5e9f4a6b3c8d2e1f7a9b4c5d6e8f1a2b3c4d5e6f7a8b9c0d1e2f3a", "arming_pkg_hash": "0x3f2a1b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a", "presig_pkg_hash": "0x7c6b5a4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b", "ctx_hash": "0xb1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2" }, "serialization": { "byte_order": "big_endian", "point_compression": "compressed", "array_encoding": "concatenation" } }, { "id": "ctx_hash_002", "description": "Multi-CRS AND-ing with two CRS", "inputs": { // ... similar structure with two CRS }, "expected_outputs": { // ... expected hashes } } // ... 10-20 more test vectors covering edge cases ] } ``` **Coverage Requirements:** - Standard parameters (happy path) - Multi-CRS AND-ing (2 CRS, 3 CRS) - Different path_tag values - Minimal and maximal input sizes - Edge case nonces (0x00...00, 0xff...ff, 0x00...01) - Different signer set sizes (2-of-2, 3-of-3, etc.) #### R2.2: Specify Canonical Serialization Rules **Owner:** Protocol designers **Timeline:** 1 week **Deliverable:** §3 specification addendum **Serialization Specification:** ```markdown ### Canonical Serialization for ctx_hash (Normative) All implementations MUST use identical serialization to ensure ctx_hash interoperability. Deviations will result in different ctx_hash values and protocol failure. #### Integer Encoding - **Byte order:** Big-endian (network byte order) - **Size encoding:** Fixed-width, no variable-length encoding - **Examples:** - uint8: 1 byte - uint16: 2 bytes, big-endian - uint32: 4 bytes, big-endian - uint256: 32 bytes, big-endian #### Field Element Encoding - **Representation:** Montgomery form or canonical representative in [0, p-1] - **Size:** Fixed to field size (32 bytes for BLS12-381 scalar field) - **Byte order:** Big-endian - **Validation:** MUST reject values ≥ p at deserialization #### Curve Point Encoding - **Format:** Compressed point encoding (x-coordinate + sign bit) - **G₁ points (BLS12-381):** 48 bytes compressed - **G₂ points (BLS12-381):** 96 bytes compressed - **Point at infinity:** For BLS12-381: - G₁ point at infinity: Compressed form with flag bits set per spec - G₂ point at infinity: Compressed form with flag bits set per spec - Implementations MUST use library-provided compressed serialization - Non-canonical encodings MUST be rejected - **Validation:** MUST check point on curve, MUST check subgroup membership #### Array Encoding - **Concatenation:** Direct concatenation, no delimiters - **Length prefix:** None (array lengths implicitly known from context) - **Length consistency (MUST):** While array lengths are "implicitly known from context," implementations MUST agree on what this context is. For example, the number of D₁ elements (m₁) and D₂ elements (m₂) MUST be determined by the GS-CRS structure, which MUST be frozen before arming. Any mismatch MUST cause arming to abort. - **Order:** As specified in data structure (e.g., j=1..m₁ for {D₁,ⱼ}) #### Hash Input Concatenation - **Tag encoding:** UTF-8 bytes of the tag string, no null terminator - **Field separator:** None (direct concatenation: tag || data) - **Example:** "PVUGC/CTX_CORE" encoded as 0x50565547432f4354585f434f5245 (15 bytes) #### Consistency Requirements (MUST) Implementations MUST ensure bit-identical serialization across all implementations. The following MUST be standardized: 1. **Field Element Padding:** For field elements < p (curve order): - MUST be encoded as 32-byte big-endian with leading zeros - MUST reject values ≥ p at deserialization 2. **Domain Tag Encoding:** UTF-8 bytes of the tag string, no null terminator. Example: "PVUGC/CTX_CORE" = 0x50565547432f4354585f434f5245 (15 bytes) 3. **Hash Function Instantiation:** H_bytes MUST be SHA-256 with standard parameters (NIST FIPS 180-4). No implementation-specific variants allowed. #### Interoperability Testing Before production deployment, all implementations MUST pass a test suite with canonical test vectors covering all data types and edge cases (point at infinity, field elements 0, 1, p-1, etc.). #### Test Vector Compliance Implementations MUST produce bit-identical outputs for all test vectors in the canonical test suite (see R2.1). ``` ### Priority 3: SHOULD - Formal Verification & Proofs #### R3.1: Formal Verification of Context Binding Properties **Owner:** Formal methods team (external consultant or academic partnership) **Timeline:** 6 months **Deliverable:** Mechanized proof in Coq/Isabelle/Tamarin **Verification Goals:** ``` Theorem 1 (Context Uniqueness): For all distinct instances I₁, I₂: P(ctx_hash(I₁) = ctx_hash(I₂)) ≤ 2^{-256} Theorem 2 (Binding Propagation): For any parameter P in {vk, x, tx, CRS, signers, ...}: P₁ ≠ P₂ → ctx_hash(I₁) ≠ ctx_hash(I₂) (w.h.p.) Theorem 3 (Replay Prevention): For all adversaries A, contexts C₁, C₂: C₁.ctx_hash ≠ C₂.ctx_hash → P(A decrypts C₂.ct with C₁.key) ≤ 2^{-128} Theorem 4 (CRS Binding): For all adversaries A: P(A substitutes CRS undetected) ≤ 2^{-256} ``` **Methodology:** - Tool: Tamarin prover (protocol verification) or CryptoVerif (cryptographic proofs) - Model: Symbolic model of hash functions (Random Oracle Model) - Adversary: Dolev-Yao attacker (controls network, cannot break crypto) - Properties: Secrecy (ctx_hash uniqueness) and authentication (binding) **Deliverable Components:** 1. Formal model of layered hash construction 2. Adversary capabilities specification 3. Security properties as lemmas 4. Mechanized proofs (or proof sketches if full proof intractable) 5. Verification report with assumptions and limitations #### R3.2: Proof-of-Concept Implementation with Formal Correctness **Owner:** Implementation team + formal methods team **Timeline:** 4 months **Deliverable:** Reference implementation with proven correctness **Approach:** - Language: Rust (for memory safety) or F* (for verification) - Verification: Use Prusti (Rust verifier) or F* type system - Properties: Pre/post-conditions for all ctx_hash functions **Example (F* pseudocode):** ```fsharp val ctx_core_hash: vk_hash:bytes{length vk_hash = 32} -> x:bytes -> tapleaf_hash:bytes{length tapleaf_hash = 32} -> tapleaf_version:byte -> txid_template:bytes{length txid_template = 32} -> path_tag:byte -> epoch_nonce:bytes{length epoch_nonce = 32} -> Pure (result:bytes{length result = 32}) (requires True) (ensures fun result -> // Property: Different nonces → different outputs (probabilistic) forall n1 n2. n1 <> n2 ==> ctx_core_hash vk_hash x tapleaf_hash tapleaf_version txid_template path_tag n1 <> ctx_core_hash vk_hash x tapleaf_hash tapleaf_version txid_template path_tag n2) ``` ### Priority 4: MAY - Long-Term Improvements #### R4.1: Blockchain-Based Nonce Registry **Owner:** Infrastructure team **Timeline:** 12 months (research + implementation) **Deliverable:** On-chain or off-chain nonce registry **Design Options:** **Option A: On-chain OP_RETURN registry** - Pro: Immutable, globally verifiable - Con: Higher cost, blockchain bloat - Implementation: Every epoch_nonce in OP_RETURN output **Option B: Merkle tree commitment** - Pro: Efficient, batching possible - Con: Requires trusted aggregator - Implementation: Periodic Merkle root published on-chain, full tree off-chain **Option C: Decentralized gossip protocol** - Pro: No blockchain dependency, scalable - Con: Eventual consistency, coordination challenges - Implementation: P2P network with nonce broadcast **Recommendation:** Option B (Merkle tree) for balance of efficiency and verifiability. #### R4.2: Enhanced Entropy Monitoring **Owner:** Security operations team **Timeline:** 6 months **Deliverable:** Entropy monitoring and alerting system **Components:** 1. **Entropy source verification:** Check OS RNG health before nonce generation 2. **Statistical testing:** Apply NIST randomness tests to generated nonces 3. **Anomaly detection:** Alert if nonces show non-random patterns 4. **Backup sources:** Fallback to hardware RNG if OS entropy low --- ## Cross-References ### Related Issues **PVUGC-002: Multi-CRS AND-ing** - **Relationship:** CRS digests now bound via header_meta → arming_pkg_hash (this issue validates that binding) - **Appendix:** [A02.CR02](APPENDIX-issue-debates.md#a02-cr02) - **Status:** ✅ Resolved - Multi-CRS mandatory, digests bound in GS_instance_digest **PVUGC-010: CRS Validation** - **Relationship:** Binding CRS in ctx_hash requires validated CRS; this issue ensures correct CRS used - **Appendix:** [A10.M101](APPENDIX-issue-debates.md#a10-m101) - **Appendix (CR):** [A10.CR01](APPENDIX-issue-debates.md#a10-cr01) - **Status:** ✅ Resolved - Pairing check for binding CRS enforced **PVUGC-003: Independence Claim** - **Relationship:** Independence of {U_j, V_k} and G_G16 ensures GT-XPDH holds; context binding isolates instances - **Appendix:** [A03.M202](APPENDIX-issue-debates.md#a03-m202) - **Status:** ⚠️ Open - Independence unproven, but context binding provides defense-in-depth ### Specification References - **Main Specification:** [`../PVUGC-2025-10-20.md §1 Introduction`](../PVUGC-2025-10-20), §3 (lines 54-77), §6 (KDF usage of ctx_hash) - **Appendix:** [A05.M203](APPENDIX-issue-debates.md#a05-m203) - [`report-preliminary-2025-10-07/PVUGC-005-context-binding.md`](../report-preliminary-2025-10-07/PVUGC-005-context-binding) - [`report-update-2025-10-07/PVUGC-005-context-binding.md`](../report-update-2025-10-07/PVUGC-005-context-binding) --- ## Attribution ### Mathematician #1 (M1) - Original Analysis **Contributions:** - Identification of critical v1.0 binding gaps (CRS not bound, arming_pkg_hash independence) - Detailed attack scenarios (CRS substitution, cross-context arming replay, epoch collision) - Comprehensive recommendations for v2.0 improvements - Recognition of layered hash structure benefits **Credit:** M1's thorough analysis in the preliminary and update reports correctly identified fundamental security issues and guided the v2.0 design toward a robust layered hash structure. The current security posture is a direct result of this work. **Files:** - v1.0: [`../report-preliminary-2025-10-07/PVUGC-005-context-binding.md`](../report-preliminary-2025-10-07/PVUGC-005-context-binding) - v2.0: [`../report-update-2025-10-07/PVUGC-005-context-binding.md`](../report-update-2025-10-07/PVUGC-005-context-binding) ### Mathematician #2 (M2) - Formal Validation **Contributions:** - Formal validation of v2.0 layered hash structure under Random Oracle Model - Identification of epoch_nonce enhancement for formal provability - **Theorem 4:** Provable Context Uniqueness with Nonce (formal statement and proof sketch) - Security analysis of binding propagation through hash chain - Specification of epoch_nonce normative requirements **Credit:** M2's formal analysis confirms the v2.0 design is cryptographically sound and provides practical security. The epoch_nonce enhancement recommendation elevates the protocol from "secure in practice" to "provably secure," enabling clean formal verification. **Files:** - Appendix [A05.M203](APPENDIX-issue-debates.md#a05-m203) - Appendix [A05.M201](APPENDIX-issue-debates.md#a05-m201) - Appendix [A05.M202](APPENDIX-issue-debates.md#a05-m202) ### Collaborative Synthesis This Final report integrates: - M1's vulnerability discovery and attack construction (validated ✅) - M2's formal analysis and enhancement recommendation (validated ✅) - Additional adversarial analysis with concrete attack algorithms - Comprehensive implementation guidance for epoch_nonce - Formal security properties and proof sketches - M2's Round 3 revisions for mathematical precision and deployment clarity **Agreement:** Full agreement between M1 and M2 on all substantive points: - v1.0 had critical gaps ✅ - v2.0 closes those gaps ✅ - v2.0 provides practical security ✅ - epoch_nonce enhancement recommended for formal provability ✅ - Both v2.0 and v3.0 are acceptable deployment paths ✅ --- ## Final Verdict ### Status: ⚠️ **Enhanced** **Severity: 🟡 LOW** (v2.0 provides practical security; epoch_nonce elevates to provable security) ### Summary The context binding mechanism has undergone a remarkable transformation from v1.0 to v2.0: **v1.0 State (Pre-October 2025):** - ❌ Critical vulnerabilities: CRS substitution, cross-context replay - ❌ Severity: HIGH - ❌ Deployment readiness: Blocked **v2.0 State (Current - ../PVUGC-2025-10-20.md §1 Introduction):** - ✅ Layered hash structure with domain separation - ✅ CRS bound via three-layer chain (header_meta → arming_pkg_hash → ctx_hash) - ✅ Cross-context replay prevented (different txid_template → different ctx_hash) - ✅ Practical security: HIGH (collision probability 2^{-256}) - ✅ Deployment readiness: ACCEPTABLE with comprehensive testing - ⚠️ Formal provability: Relies on implicit uniqueness assumption (txid_template) **v3.0 Recommendation (Enhanced):** - ✅ All v2.0 mitigations - ✅ Explicit epoch_nonce for provable uniqueness (Theorem 4) - ✅ Formal security: Provable under Random Oracle Model - ✅ Best practice compliance: Explicit uniqueness enforcement - ✅ Deployment readiness: RECOMMENDED for production mainnet ### Resolution Path **Immediate (1-2 months):** 1. Comprehensive cross-context replay testing (Priority 1) - MUST 2. Test vector production for interoperability (Priority 2) - SHOULD 3. Decision point: Deploy v2.0 or wait for v3.0 epoch_nonce **Near-term (3-6 months):** 1. Implement epoch_nonce enhancement (Priority 1 - RECOMMENDED) 2. Update specification with normative requirements and serialization details 3. Formal verification of binding properties (Priority 3) **Long-term (6-12 months):** 1. Mechanized proof in Tamarin/Coq 2. Production-grade reference implementation with formal correctness 3. Blockchain-based nonce registry (optional) ### Recommendation **For Production Deployment:** Two acceptable paths exist: **Path A: v2.0 Deployment (Aggressive)** - **Timeline:** Immediate deployment after testing - **Security:** Practical security via txid_template uniqueness (2^{-256} collision probability) - **Rationale:** v2.0 provides negligible risk in practice; txid_template includes UTXOs, outputs, and timing parameters that make collisions computationally infeasible - **Audit story:** "Secure with negligible collision probability (2^{-256})" - **Risk:** Formal proof requires assumption about transaction uniqueness (external to protocol) - **Mitigation:** Add epoch_nonce in v3.0 update post-deployment **Path B: v3.0 Deployment (Conservative)** - **Timeline:** +6 weeks for epoch_nonce implementation - **Security:** Provable security under Random Oracle Model - **Rationale:** Formal proof clarity worth modest delay; cleaner security model for audits - **Audit story:** "Provably secure under ROM with explicit uniqueness guarantee" - **Benefit:** Future-proofing, independence from Bitcoin transaction format, defense-in-depth - **Implementation cost:** LOW (32 bytes, simple CSPRNG call, standard uniqueness checks) **M2's Recommendation:** Either approach is technically sound. The choice is an engineering decision, not a security decision. **For high-value, conservative deployments:** Implement v3.0 with epoch_nonce before mainnet launch. The implementation cost is low (32 bytes, simple CSPRNG call, ~6 weeks) and the security benefit is high (formal provability, future-proofing, cleaner audit story). **For rapid deployment, pragmatic approach:** Deploy v2.0 with comprehensive testing, add epoch_nonce in v3.0 update. The v2.0 practical security is sufficient (2^{-256} collision probability via txid_template uniqueness). ### Conclusion The v2.0 layered hash structure represents excellent cryptographic engineering. It successfully mitigates all identified v1.0 vulnerabilities through defense-in-depth and domain separation. The protocol is **practically secure** and **ready for deployment** with appropriate testing. The epoch_nonce enhancement is **strongly recommended** but **not blocking** for deployment. It elevates the protocol from "secure under reasonable assumptions" (txid_template uniqueness) to "provably secure under standard assumptions" (Random Oracle Model), which is the gold standard for cryptographic protocols handling high-value transactions. **Final Assessment:** - **v2.0 alone:** ACCEPTABLE for deployment with testing (collision probability 2^{-256}) - **v3.0 (v2.0 + epoch_nonce):** RECOMMENDED for production mainnet - **Security posture:** Excellent (v2.0) → Outstanding (v3.0) **Deployment Guidance:** - Both v2.0 and v3.0 are technically sound - v2.0 provides practical security via implicit uniqueness (txid_template) - v3.0 provides formal provability via explicit uniqueness (epoch_nonce) - Conservative teams: choose v3.0 (6-week delay, formal proof clarity) - Aggressive teams: choose v2.0 (immediate deployment, add epoch_nonce post-launch) --- **Report completed:** 2025-10-24 **Next review:** After cross-context testing completion (2-3 months) **Peer review status:** Final (Round 4 complete) **Signed:** Cryptographic Peer Reviewer (M2) In collaboration with Mathematician #1 With Round 3 mathematical validation