Skip to content

Commit 7bb0f92

Browse files
authored
Merge pull request #46 from eth-hca/fix/pre-release-v030
fix: pre-release corrections for v0.3.0
2 parents a528c5a + 15b33c7 commit 7bb0f92

4 files changed

Lines changed: 35 additions & 11 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ See [SECURITY.md](SECURITY.md) for the full security policy.
166166

167167
- **Not audited** — this is research-grade software
168168
- Constant-time comparison (`subtle`) in proof verification
169-
- 4 libfuzzer fuzz targets: merkle, proof, witness, RLP
169+
- 6 libfuzzer fuzz targets: merkle, proof, witness, RLP encode, RLP decode, EVM opcode
170170
- EVM opcode validator with Berlin gas schedule enforcement
171171
- `#![warn(missing_docs)]` — all public items documented
172172

SPECIFICATION.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,13 @@ EIP-2718 typed transaction, type `0x05`:
117117
```
118118
signing_hash = tagged_hash("HCAWitness",
119119
chain_id[8] || nonce[8] || from[20] || to[20] || value[16] ||
120+
data_len[8] || data[variable] ||
120121
gas_limit[8] || max_fee_per_gas[16] || max_priority_fee_per_gas[16] ||
121122
leaf_hash[32]
122123
)
123124
```
124125

125-
Includes `chain_id` (cross-chain replay), `nonce` (same-chain replay), `from` (cross-account replay), and `leaf_hash` (binds to specific spending condition).
126+
Includes `chain_id` (cross-chain replay), `nonce` (same-chain replay), `from` (cross-account replay), `data` (binds signature to exact calldata), and `leaf_hash` (binds to specific spending condition). The explicit `data_len` prefix is required so that a trailing-zero calldata cannot be confused with an appended `gas_limit` field.
126127

127128
## 8. authRoot rotation
128129

src/merkle.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use std::collections::HashSet;
1414

1515
use crate::constants::{MAX_LEAF_SCRIPT_SIZE, MAX_TREE_DEPTH};
1616
use crate::error::{HcaError, HcaResult};
17-
use crate::evm::opcode::validate_leaf_script;
1817
use crate::hash::{tag_hashes, tagged_hash};
18+
use crate::leaf_version::{validate_for_version, LeafVersion};
1919
#[cfg(feature = "parallel")]
2020
use rayon::prelude::*;
2121
#[cfg(feature = "serde")]
@@ -41,19 +41,21 @@ pub struct Leaf {
4141
impl Leaf {
4242
/// Create a new leaf, validating version and script size.
4343
///
44-
/// Returns `HcaError::InvalidLeafVersion` if version is `0x00`.
44+
/// Routes through the `LeafVersion` registry: unknown or reserved versions
45+
/// (including `0x00` and `0x02`+) are rejected via `LeafVersion::from_byte`,
46+
/// and script validation is dispatched per-version by `validate_for_version`.
47+
///
48+
/// Returns `HcaError::InvalidLeafVersion` if version is not active.
4549
/// Returns `HcaError::LeafScriptTooLarge` if script exceeds `MAX_LEAF_SCRIPT_SIZE`.
4650
pub fn new(version: u8, script: Vec<u8>, description: &str) -> HcaResult<Self> {
47-
if version == 0x00 {
48-
return Err(HcaError::InvalidLeafVersion { version });
49-
}
51+
let lv = LeafVersion::from_byte(version)?;
5052
if script.is_empty() {
5153
return Err(HcaError::EmptyLeafScript);
5254
}
5355
if script.len() > MAX_LEAF_SCRIPT_SIZE {
5456
return Err(HcaError::LeafScriptTooLarge { size: script.len() });
5557
}
56-
validate_leaf_script(&script)?;
58+
validate_for_version(lv, &script)?;
5759
Ok(Self {
5860
version,
5961
script,
@@ -482,9 +484,30 @@ mod tests {
482484

483485
#[test]
484486
fn test_valid_leaf_versions() {
487+
// Only 0x01 is active — 0x02+ route through LeafVersion registry and are rejected.
485488
assert!(Leaf::new(0x01, b"script".to_vec(), "v1").is_ok());
486-
assert!(Leaf::new(0x02, b"script".to_vec(), "v2").is_ok());
487-
assert!(Leaf::new(0xFF, b"script".to_vec(), "vFF").is_ok());
489+
}
490+
491+
#[test]
492+
fn test_leaf_version_0x02_rejected() {
493+
// 0x02 is reserved (EIP-7932 dispatch) and MUST be rejected at construction.
494+
let result = Leaf::new(0x02, b"script".to_vec(), "v2");
495+
assert!(result.is_err());
496+
assert_eq!(
497+
result.unwrap_err(),
498+
HcaError::InvalidLeafVersion { version: 0x02 }
499+
);
500+
}
501+
502+
#[test]
503+
fn test_leaf_version_0xff_rejected() {
504+
// Unknown versions MUST be rejected by LeafVersion::from_byte.
505+
let result = Leaf::new(0xFF, b"script".to_vec(), "vFF");
506+
assert!(result.is_err());
507+
assert_eq!(
508+
result.unwrap_err(),
509+
HcaError::InvalidLeafVersion { version: 0xFF }
510+
);
488511
}
489512

490513
#[test]

tests/vectors/witness_signing.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"description": "HCA witness signing hash test vectors",
33
"version": "0.2.0",
4-
"formula": "signing_hash = tagged_hash('HCAWitness', chain_id || nonce || from || to || value || gas_limit || max_fee || max_priority_fee || leaf_hash)",
4+
"formula": "signing_hash = tagged_hash('HCAWitness', chain_id || nonce || from || to || value || data_len || data || gas_limit || max_fee || max_priority_fee || leaf_hash)",
55
"vectors": [
66
{
77
"description": "Minimal transaction on Sepolia",

0 commit comments

Comments
 (0)