fix: non-malleable child op_id derivation and namespaced OpSeen#95
fix: non-malleable child op_id derivation and namespaced OpSeen#95salazarsebas wants to merge 3 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughThe change hardens cross-contract idempotency by hashing events child operation IDs with a profile-contract domain separator and by scoping profile replay markers to domains. New tests cover collision resistance, front-running resistance, and true replay rejection. ChangesIdempotency hardening
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Attacker
participant EventsContract
participant ProfileContract
participant ProfileStorage
EventsContract->>EventsContract: derive hashed child op_ids
Attacker->>ProfileContract: bootstrap_self(attacker, child op_id)
ProfileContract->>ProfileStorage: store under attacker domain
EventsContract->>ProfileContract: claim_prize with child op_ids
ProfileContract->>ProfileStorage: check events domain
ProfileContract-->>EventsContract: apply legitimate profile updates
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
Fixes a HIGH severity payout DoS: XOR-derived child
op_ids were malleable, and profileOpSeenlived in a single global namespace that unprivilegedbootstrap_selfcould squat.Closes #68
Problem
derive_child/derive_child_indexedinboundless-eventsXORed a 1-byte tag (and optional sub-index) into the parentop_id. XOR is reversible and collision-friendly.OpSeenwas keyed only byop_id(DataKey::OpSeen(BytesN<32>)), shared by privileged events-originated mutations and unprivilegedbootstrap_self.op_id, recompute deterministic children (BOOTSTRAP/BUMP_REP/REGISTER_EARNINGS), front-runbootstrap_self(attacker, child_id), then the events→profile call hitsOpAlreadySeenand blocks reputation / earnings (and hard-fails grant paths that do not usetry_).Scout
incorrect_exponentiationflags on^=were false positives (intentional XOR). Removing XOR clears those flags by deleting the malleable construction.Fix
Events — domain-separated SHA-256 child ids
callee_contract_idis the configured profile contract address (XDR-encoded domain separator).derive_childusessub_idx = 0;derive_child_indexedkeeps the explicit index.env.crypto().sha256keeps WASM well under the 64 KB CI ceiling.Profile — namespaced
OpSeenbootstrap/bump_reputation/slash_reputation/register_earningsbootstrap_selfadmin_slashUnprivileged
bootstrap_selfcan no longer mark the events domain, so front-running child ids is useless against legitimate orchestration.OpSeenremainstemporary()storage; no persistent migration required.Tests
New module
contracts/events/src/tests/op_id_security.rs:sha256_child_ids_differ_for_xor_colliding_parentsbootstrap_self_cannot_front_run_events_child_op_idsbootstrap_self;claim_prizestill pays and updates reputation + earningsevents_domain_child_op_id_replay_still_rejectedLocal verification
Risk notes
op_idvalues change for any in-flight parent (testnet-acceptable; not a client API break).OpSeenkey shape changes; existing temporary entries simply expire under the old layout.Checklist
Summary by CodeRabbit
Security Improvements
Bug Fixes