feat!: add MessageOrigin::Delegate variant for inter-delegate caller attestation#65
Merged
feat!: add MessageOrigin::Delegate variant for inter-delegate caller attestation#65
Conversation
…attestation Adds a `Delegate(DelegateKey)` variant to `MessageOrigin` so the runtime can attest the caller's identity when one delegate sends a message to another via `OutboundDelegateMsg::SendDelegateMessage`. Previously the receiver was passed `origin = None` and could not learn which delegate invoked it, blocking any authorization scheme that depends on caller identity (freenet/freenet-core#3860). The enum is now `#[non_exhaustive]` so future variants can be added without a source-level breaking change. This is a one-time source break for downstream code that currently matches `MessageOrigin` exhaustively — they must add a wildcard arm. Wire-format compat: bincode discriminant for the existing `WebApp(..)` variant is unchanged. A new wire-format pin test (`webapp_origin_wire_format_is_stable`) locks the byte layout so this guarantee can't drift in future releases. Deployed delegate WASM compiled against 0.4.x continues to deserialize `WebApp(..)` and `None` origins identically. Only delegates that start receiving inter-delegate calls carrying the new `Delegate(..)` variant need rebuilding against 0.5.x — no production delegate exercises that path today. Drive-by: tighten a `read()`/`read_exact()` test in memory::buf so `cargo clippy --all-targets` is clean on stable (the bug surfaces under newer clippy versions; CI's wasm-only clippy didn't catch it). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merged
8 tasks
…emantics Addresses skeptical-review feedback on freenet-stdlib#65: 1. Wire format pin: assert the FULL byte layout for `MessageOrigin::Delegate` (variant tag + DelegateKey serde repr), not just the variant tag. If `DelegateKey`'s serde representation ever changes, deployed delegates compiled against a previous stdlib will silently fail to deserialize inter-delegate origins — exactly the failure mode this test exists to prevent. 2. Document the inherited-origin revocation semantics in the `MessageOrigin::Delegate` rustdoc: an inter-delegate message replaces rather than composes with any inherited WebApp origin the calling delegate may itself hold. The receiver sees only Delegate(caller_key) and does not gain contract access on behalf of any web app the caller was acting for. Authorization should be made on the calling delegate's identity alone. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When delegate A sends a message to delegate B via
OutboundDelegateMsg::SendDelegateMessage, B'sprocess()is invoked withorigin = None. B has no way to learn which delegate (if any) called it,because
MessageOriginonly had aWebApp(ContractInstanceId)variant. Thisblocks any trust policy that depends on caller delegate identity, and prevents
permission prompts from showing the actual caller for delegate-to-delegate
RequestUserInputrequests (see freenet/freenet-core#3857).Approach
Add
MessageOrigin::Delegate(DelegateKey)so the runtime can attest thecalling delegate's identity. The companion freenet-core PR plumbs this
through the executor at
contract.rs:479so inter-delegate dispatch passesSome(MessageOrigin::Delegate(caller_key))instead ofNone.Mark
MessageOriginas#[non_exhaustive]in the same change so futurevariants can be added without a source-level break. This is a one-time source
break for downstream code that currently matches the enum exhaustively — they
must add a wildcard arm.
Compatibility
This is a source-level breaking change (variant added +
non_exhaustive),hence the major-bump 0.4.0 → 0.5.0. It is not a wire-format break:
MessageOrigin::WebApp(..)is unchanged(variant index 0). Deployed delegate WASM compiled against 0.4.x will
continue to deserialize
WebApp(..)andNoneorigins identically. A newwire-format pin test (
webapp_origin_wire_format_is_stable) locks thebyte layout.
MessageOrigin::Delegate(..). That requires another delegate to call itvia
SendDelegateMessage, which no production delegate exercises today(river chat delegate, ghostkey, etc. do not participate in inter-delegate
messaging).
#[non_exhaustive]does not affect runtime/wire behavior — it onlychanges how source code may match on the enum.
So in practice: contracts are unaffected entirely (
MessageOriginisdelegate-only); deployed delegates are unaffected unless they begin
participating in inter-delegate messaging; downstream code that builds
against 0.5.x and pattern-matches on
MessageOriginmust add a wildcard arm.Testing
webapp_origin_wire_format_is_stable— pins the bincode bytes forWebApp(..)so any future refactor that changes the wire format will failloudly. Wire-format breaks at this layer would break every deployed
delegate, so this test is the canary.
delegate_origin_round_trips_and_is_distinct— round-trips the newDelegate(..)variant and asserts its discriminant tag is 1, distinctfrom
WebApp(..)so the two cannot be conflated.delegate_interfacetests continue to pass.Drive-by: tightened a
read()→read_exact()test inmemory::bufsocargo clippy --all-targetsis clean under newer stable clippy. CI onlyclippies the wasm32 target so this didn't surface in CI; I tripped over it
running local
--all-targetschecks.Release coordination
This crate must publish to crates.io as 0.5.0 before the freenet-core PR
that consumes the new variant can merge. The freenet-core PR currently
points at this branch via a
[patch.crates-io]override.Fixes
Companion to freenet/freenet-core#3860.
[AI-assisted - Claude]