hca-rs crate design guide
constants --> error --> hash --> merkle --> address
| \
leaf_version witness
|
rlp <---------+
|
builder
|
bin/hca (CLI)
|
wasm (feature-gated)
Arrows mean "depends on". No cycles.
Foundation of the entire crate. Provides:
keccak256(data) -> [u8; 32]-- standard Keccak-256.tagged_hash(tag_hash, data) -> [u8; 32]-- BIP-340 style domain-separated hash. Accepts precomputed&[u8; 32]tag hash (zero SHA-256 overhead per call).tagged_hash_str(tag, data) -> [u8; 32]-- runtime tag string variant (computes SHA-256 of tag on each call).tag_hashesmodule -- compile-timeconst [u8; 32]for all five HCA domain tags.tagsmodule -- tag string constants for documentation and cross-impl compatibility.
Design decision: SHA-256 of each tag string is precomputed. This eliminates one SHA-256 call per tagged_hash invocation -- significant in tree construction where thousands of hashes occur.
Merkle tree construction, proof generation, and verification.
Leaf-- spending condition (version byte + EVM script + description).MerkleTree-- built from leaves, stores all node levels.MerkleProof-- leaf index + sibling hashes.CompactProofSet-- deduplicated sibling table for multi-leaf proofs.MerkleTree::verify()-- static method, no tree instance required.
Key patterns:
- Trees pad to next power-of-two by repeating the last leaf.
- Duplicate leaves are rejected at construction time.
#[cfg(feature = "parallel")]gates rayon-based parallel hashing.- Serde serializes only
leaves; nodes are recomputed on deserialization.
Single function: derive_address(auth_root) -> [u8; 20].
Formula: keccak256(tagged_hash("HCAAddr", auth_root))[12:].
Transaction primitives:
TxMessage-- all transaction fields (chain_id, nonce, from, to, value, data, gas fields).HCAWitness-- leaf + proof + optional signature.build()creates unsigned,attach_signature()completes it.RotationRequest-- rotation-specific signing hash withHCARotatedomain tag.
signing_hash() commits to chain_id + nonce + from + to + value + gas + leaf_hash.
Minimal RLP encoder/decoder for HCA typed transactions (EIP-2718, type 0x05).
- Encoder:
encode_hca_tx(tx, witness) -> Vec<u8>. - Decoder:
decode_hca_tx(bytes) -> (TxMessage, HCAWitness)with canonical encoding checks. - Security: rejects non-canonical length prefixes, trailing bytes, invalid leaf versions, oversized scripts.
Version registry and validation dispatch:
LeafVersionenum --V1(active, EVM),V2(reserved, EIP-7932).validate_for_version()-- routes to EVM opcode validation for V1, rejects V2.from_byte()rejects0x00and unknown versions.
EVM opcode validator and gas counter:
opcode::validate_leaf_script()-- scans bytecode, rejects banned opcodes, enforces MAX_LEAF_GAS.gas::GasCounter-- tracks cumulative gas, returns error on overflow.- PUSH1-PUSH32 data bytes are skipped during scanning.
Ergonomic builder pattern:
TreeBuilder-- fluent API for constructing trees.TxBuilder-- fluent API for constructing transactions.
CLI tool with clap subcommands:
| Command | Description |
|---|---|
create-account |
Build tree, derive address |
derive-address |
Address from auth_root |
generate-proof |
Merkle proof for a leaf |
verify-proof |
Check proof against auth_root |
encode-tx |
RLP-encode signed HCA transaction |
signing-hash |
Compute transaction signing hash |
rotation-hash |
Compute rotation signing hash |
generate-vectors |
Output cross-impl test vectors |
verify-vectors |
Verify a test vector file |
All output is JSON to stdout. Errors go to stderr with exit code 1.
JavaScript bindings via wasm-bindgen. Feature-gated behind --features wasm. All I/O as JSON strings.
| Feature | Default | Description |
|---|---|---|
std |
yes | Links against std, enables serde_json, hex formatting, clap |
serde |
yes | Derives Serialize/Deserialize on core types |
parallel |
no | Rayon-based parallel tree construction |
wasm |
no | WASM bindings via wasm-bindgen |
no_std + alloc supported with default-features = false.
- No panics in crypto code. All fallible operations return
HcaResult. Panics only in tests. - Domain separation everywhere. Every hash uses a distinct tag. Cross-context collisions are impossible by construction.
- Static verification.
MerkleTree::verify()needs only leaf_hash + proof + auth_root. No tree instance, no global state. - Feature-gated dependencies. rayon, serde, wasm-bindgen are all optional. Core crypto is dependency-light.
- Precomputed constants. Tag hashes and protocol constants are compile-time values.
| Layer | Location | Count | Purpose |
|---|---|---|---|
| Unit tests | src/*.rs |
~145 | Edge cases per module |
| Property tests | tests/property_tests.rs |
~40 | Determinism, collision resistance, avalanche |
| Vector tests | tests/vector_tests.rs |
~10 | Cross-impl compatibility (JSON vectors) |
| Integration tests | src/lib.rs |
1 | Full HCA flow end-to-end |
| Fuzz targets | fuzz/ |
6 | libfuzzer targets for merkle, proof, witness, RLP, EVM |
GitHub Actions enforces: cargo fmt, cargo clippy -D warnings, tests on Linux/macOS/Windows, WASM build, MSRV 1.71, and no_std build for thumbv7em-none-eabihf.