Define typed domain-separated MMR peak commitments
- 主要言語
- Rust
- スター
- 772
- フォーク
- 352
- 平均マージ
- 1日 12時間
- マージ済み PR(30日)
- 93
説明
### Context
This is the architecture follow-up for `0xMiden/security-issues#18` and `0xMiden/crypto#863`.
The immediate bug is that an MMR peak commitment must bind the forest shape, not just the padded peak list. PR `0xMiden/crypto#997` and PR `0xMiden/miden-vm#3109` implement the minimal prefix fix:
```text
H([num_leaves, 0, 0, 0] || padded_peaks)
```
That fixes the ambiguity, but it is still an ad hoc commitment format. The crypto meeting outcome was that the release format should instead be a typed, domain-separated commitment shared by `miden-crypto` and the VM core library.
The useful precedent is Aptos' `CryptoHash` / `CryptoHasher` split:
- `CryptoHash` belongs to the value being committed.
- `CryptoHasher` owns the domain-specific hasher state.
- Each semantic type gets a distinct hasher/domain, so hashes for different types cannot collide by construction.
Reference: [https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-crypto/src/hash.rs](https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-crypto/src/hash.rs)
### Design Direction
Introduce the same split in Miden terms, but adapt it to field-element commitments and MASM-visible consensus formats.
For consensus-visible domains, define a canonical map from a stable textual domain tag to field elements, then let derive macros use that map. This keeps the Aptos ergonomics while preserving MASM reproducibility.
One workable rule is:
```text
domain_tag = "miden.crypto." || serde_or_explicit_type_name
domain = Felt::new(canonical_u64(domain_tag))
```
where `canonical_u64` is a documented deterministic function over ASCII bytes, rejects non-canonical tags, and is collision-checked by a small registry test. The exact function should be chosen for simplicity and reproducibility rather than secrecy. The derived value is still emitted as a Rust constant and copied into MASM for consensus-visible formats.
For hand-written first users such as MMR peaks, we can start with an explicit constant:
```rust
pub const MMR_PEAKS_DOMAIN: Felt = domain!("miden.crypto.MmrPeaks");
```
The macro can later be used by a proc macro derive, so we do not have to choose between derivation and explicit auditable constants.
Sketch:
```rust
/// A value with a typed cryptographic commitment.
pub trait CryptoHash {
type Hasher: CryptoHasher;
type Digest: From;
fn write_hash(&self, hasher: &mut Self::Hasher);
fn hash(&self) -> Self::Digest {
let mut hasher = Self::Hasher::new();
self.write_hash(&mut hasher);
hasher.finish().into()
}
}
/// A domain-specific algebraic hasher.
pub trait CryptoHasher {
const NAME: &'static str;
const DOMAIN: Felt;
const VERSION: Felt;
fn new() -> Self;
/// Default implementations should absorb through the underlying Poseidon2 state.
fn absorb_elements>(&mut self, elements: &[E]);
fn absorb_words(&mut self, words: &[Word]);
fn finish(self) -> Word;
}
```
The first implementation should be explicit, but the API should not prevent a later derive macro. The priority is a stable commitment format that can be audited and reproduced by both Rust and MASM.
### Poseidon2 Capacity Layout
For algebraic sponge commitments, use the Poseidon2 capacity word as the domain seed:
```text
C = [padding_control, type_domain, version, personalization]
```
Where:
- `padding_control` remains owned by Poseidon2's existing element/word padding rules.
- `type_domain` is a stable domain constant, for example `MMR_PEAKS_DOMAIN`.
- `version` is the commitment-format version.
- `personalization` is an optional dynamic parameter for the semantic type.
For MMR peaks, `personalization = num_leaves`.
This is cleaner than collapsing the static domain and dynamic forest size into a single field element such as `MMR_PEAKS_DOMAIN + num_leaves`.
### MMR Peaks Commitment
Define an explicit MMR peak hasher:
```rust
pub struct MmrPeaksHasher {
state: Poseidon2State,
}
impl CryptoHasher for MmrPeaksHasher {
const NAME: &'static str = "MmrPeaks";
const DOMAIN: Felt = MMR_PEAKS_DOMAIN;
const VERSION: Felt = Felt::ONE;
fn new() -> Self {
Self::with_capacity([
Felt::ZERO,
Self::DOMAIN,
Self::VERSION,
Felt::ZERO,
])
}
// The absorb methods come from CryptoHasher's default Poseidon2-backed implementation.
}
impl MmrPeaksHasher {
pub fn for_forest(forest: Forest) -> Self {
Self::with_capacity([
Felt::ZERO,
MMR_PEAKS_DOMAIN,
Felt::ONE,
Felt::new(forest.num_leaves() as u64),
])
}
}
```
Then `MmrPeaks::hash_peaks()` should commit to the padded peak elements through the forest-personalized hasher:
```rust
impl MmrPeaks {
pub fn hash_peaks(&self) -> Word {
let mut hasher = MmrPeaksHasher::for_forest(self.forest());
hasher.absorb_elements(&self.flatten_and_pad_peaks());
hasher.finish()
}
}
```
Conceptually:
```text
Poseidon2(
capacity = [0, MMR_PEAKS_DOMAIN, MMR_PEAKS_VERSION, num_leaves],
rate = padded_peaks,
)
```
This binds the same data as the prefix fix, but the binding is typed, versioned, and not another domain-zero rate encoding.
### VM / MASM Alignment
The VM core library must compute the exact same commitment for `mmr::pack` and `mmr::unpack`.
The clean reusable path is to add MASM support for hashing memory with a full capacity seed:
```text
poseidon2::hash_words_with_capacity
poseidon2::hash_elements_with_capacity
```
If that is too much for this release, an MMR-specific helper is acceptable:
```text
mmr::hash_peaks
```
Either way, the MASM constants should mirror Rust:
```text
MMR_PEAKS_DOMAIN
MMR_PEAKS_VERSION
```
and both `pack` and `unpack` should use:
```text
Poseidon2(
capacity = [0, MMR_PEAKS_DOMAIN, MMR_PEAKS_VERSION, num_leaves],
rate = padded_peaks,
)
```
The forged-`num_leaves` tests from `0xMiden/crypto#997` and `0xMiden/miden-vm#3109` remain the right regression coverage and should be carried into the replacement PRs.
### Non-Goals
- Do not redesign MMR as a partial opening of a normal Merkle tree here.
- Do not introduce BCS or byte serialization as the Miden commitment format.
- Do not block on a derive macro.
cc @krushimir @Al-Kindi-0 @bobbinth @adr1anh
コントリビューションガイド
評価
この issue はまだ評価されていません。