0xMiden / 0xMiden/miden-vm

Define typed domain-separated MMR peak commitments

オープン
#3,530 コメント 6 件 リアクション 1 件 担当者 0 名 @Al-Kindi-0 が担当を希望しています GitHub で見る
corelib
主要言語
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 はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。