Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions extensions/tn_utils/maa.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ func computeRulesHashHandler(ctx *common.EngineContext, app *common.App, inputs

// computeRulesHash builds the canonical RULES_PREIMAGE (doc 5 §1) and returns keccak256(preimage).
func computeRulesHash(feeMode string, feeBps int64, feeFlatStr, bridge string, namespaces, actions []string, bodyHashes [][]byte) ([]byte, error) {
// Defensive: the three allow-list slices are indexed in lockstep below. The on-chain handler
// already equalizes them, but guard the pure function so a direct caller gets an error instead
// of an index-out-of-range panic or a silently-truncated hash.
if len(namespaces) != len(actions) || len(namespaces) != len(bodyHashes) {
return nil, fmt.Errorf("namespaces/actions/body_hashes must be equal length (%d/%d/%d)",
len(namespaces), len(actions), len(bodyHashes))
}

var b bytes.Buffer

b.WriteByte(maaRulesVersion)
Expand Down
23 changes: 23 additions & 0 deletions extensions/tn_utils/maa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ func TestComputeRulesHash_OrderIndependentAndDedup(t *testing.T) {
if !bytes.Equal(base, deduped) {
t.Fatalf("duplicate entry changed the hash:\n base %x\n dedup %x", base, deduped)
}

// Conflicting body_hash for a duplicate (namespace, action): the LAST occurrence wins
// (5RulesHash-Preimage-Spec.md §1 canonicalization rule 1: "last write wins for its body_hash").
// The earlier 0xdd pin on ob_place_order is dropped in favor of the trailing 0xcc, so the result
// must equal `base` (which pins ob_place_order to 0xcc).
lastWins, err := computeRulesHash("bps", 250, "0", "eth_truf",
[]string{"main", "main", "main"},
[]string{"ob_place_order", "ob_cancel_order", "ob_place_order"},
[][]byte{repeatByte(0xdd, 32), nil, repeatByte(0xcc, 32)})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(base, lastWins) {
t.Fatalf("last-write-wins not honored for a conflicting body_hash:\n base %x\n lastWins %x", base, lastWins)
}
}

func TestDeriveMAAAddress_GoldenVectors(t *testing.T) {
Expand Down Expand Up @@ -164,6 +179,9 @@ func TestDeriveMAAAddress_RejectsBadLengths(t *testing.T) {
if _, err := deriveMAAAddress(repeatByte(0x11, 19), good20, good32, nil); err == nil {
t.Fatal("expected error for 19-byte restricted")
}
if _, err := deriveMAAAddress(good20, repeatByte(0x22, 21), good32, nil); err == nil {
t.Fatal("expected error for 21-byte unrestricted")
}
if _, err := deriveMAAAddress(good20, good20, repeatByte(0x33, 31), nil); err == nil {
t.Fatal("expected error for 31-byte rules_hash")
}
Expand All @@ -180,4 +198,9 @@ func TestComputeRulesHash_Validation(t *testing.T) {
[]string{"main"}, []string{"a"}, [][]byte{repeatByte(0x00, 31)}); err == nil {
t.Fatal("expected error for 31-byte body_hash")
}
// Mismatched parallel-slice lengths must error, not panic (index-out-of-range) or silently truncate.
if _, err := computeRulesHash("bps", 0, "0", "eth_truf",
[]string{"main"}, []string{"a", "b"}, [][]byte{nil}); err == nil {
t.Fatal("expected error for mismatched namespaces/actions/body_hashes lengths")
}
}
Loading
Loading