Transaction events: Rust types and commitment specification
- Dominant language
- Rust
- Stars
- 132
- Forks
- 167
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 110
Description
Part of #3803. This issue adds the Rust types, serialization, and commitment specification for transaction events. Kernel and transaction integration will follow in #3831.
### Rust Types
Add two types in `miden_protocol::transaction`:
- `TransactionEvent` contains `emitter: AccountId`, `topic: Word`, and `payload: Vec`.
- `TransactionEvents` holds the ordered events and their computed commitment.
Constructors will validate the size limits. Fields remain private, with no direct mutable access or unchecked constructors. Empty lists, empty payloads, and duplicate events are allowed. Events retain their insertion order, and the collection commitment is cached but not serialized.
`TransactionEvents::try_push(event)` will check the event count and total payload size, append the event, and update the commitment. Validation errors will leave the collection unchanged. Construction from a vector will use the same append logic.
### Commitment
Use the existing Poseidon2 hasher to extend the commitment as each event is added. For events numbered `1..=n`:
```text
P_i = Hasher::hash_elements(flatten(payload_i))
M_i = [emitter_suffix, emitter_prefix, payload_word_count, i]
C_0 = EMPTY_WORD
C_i = Hasher::hash_elements_in_domain(
C_(i-1) || M_i || topic_i || P_i,
TX_EVENTS_DOMAIN,
)
events_commitment = C_n
```
Each step hashes four words, binding the emitter, topic, payload, count, and order. The event position is derived from the list rather than serialized. Empty payloads hash to `EMPTY_WORD`. This PR will allocate and document the event hash domain.
The kernel can then maintain the commitment and counters between calls without retaining the full list or building a Merkle tree.
### Serialization and Limits
Use the existing serialization traits and encodings for `AccountId` and `Word`, with little endian `u16` lengths:
```text
TransactionEvent = emitter || topic || payload_word_count || payload_words
TransactionEvents = event_count || events
```
The initial limits are 64 events per transaction, 256 words per payload, and 2,048 total payload words. Deserialization will check counts before allocation and the remaining transaction budget before reading each payload, then compute the commitment from the decoded events.
### Topics
Document `topic = Hasher::hash(signature.as_bytes())` for canonical signatures such as `example::Transfer(account_id,u64)`, without whitespace or parameter names. The protocol treats topics as opaque words; application schemas and SDK tooling remain outside this issue.
### Tests and Scope
Tests will cover fixed serialization and commitment vectors, empty and duplicate events, field changes, ordering, size limits, invalid encodings, and truncated data. Include intermediate commitment vectors for each append so #3831 can verify the same results in MASM. Also verify that validation errors leave the collection unchanged and excessive lengths are rejected before payload reads.
This is Rust only. Transaction output layouts, IDs, summaries, protobuf, MASM, and finalization are covered by #3831.
Contributor guide
Assessment
This issue has not been assessed yet.