Skip to content

Endpoint identity: a resource with per-path constructor interfaces - #31

Merged
lann merged 3 commits into
mainfrom
endpoint-identity
Aug 8, 2026
Merged

Endpoint identity: a resource with per-path constructor interfaces#31
lann merged 3 commits into
mainfrom
endpoint-identity

Conversation

@lann

@lann lann commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #29.

The ruling

Identity is an opaque resource, and each provisioning path is its own constructor interface:

  • identity — the resource (an endpoint-id getter; valid by construction; reusable).
  • identity-generate — fresh mint through the endpoint's crypto import. A deployment that requires provisioned identities withholds it.
  • identity-from-keys — construction from polymorph:webcrypto/signature handles: the embedder owns provisioning and persistence (platform-held, non-extractable keys included) and injects the pair. The only interface that names webcrypto types.
  • future paths (platform storage per polymorph-webcrypto#97, …) are additive peer interfaces; a storage-backed constructor can even be a separate component layered on from-keys, with no new endpoint surface.

Worlds import exactly the construction paths they need; deployments export exactly the paths they serve; a composition needing an unserved path fails at wac plug/instantiation time. Consumers that never inject see no webcrypto types at all — the record-in-options shape this PR originally carried leaked signature types into every endpoint consumer's bindings and transpile maps.

endpoint-options becomes a resource in the sibling options-resource style. The identity is the one design-mandatory input, expressed structurally as the constructor argument — borrowed, so one identity configures any number of endpoints (e.g. two binds with different ALPNs sharing an identity). The consumed-record shape made that literally impossible: webcrypto key handles are not cloneable, so one pair could only ever reach one bind. Everything else is a setter defaulting to nothing granted (add-alpn accumulates; relay, UDP socket, webrtc all opt-in). bind consumes the options; its relay-url and one-ALPN requirements stay bind-time invalid-arguments, because both are recorded v0 narrowings, not design rules — a constructor argument would freeze that latitude into the interface. Record→resource also moves options growth onto the semver-minor path (a new knob is a new method, not a breaking record change).

Constructors validate — from-keys checks algorithm, sign permission, public-key shape, and runs a sign/verify possession probe (fixed message, discarded) — so an identity in hand is a witness and bind cannot fail for identity reasons. The private key stays behind its webcrypto handle on every path.

Coverage

The demo constructs its identity explicitly on both paths — from-keys under --inject-identity (webcrypto handles crossing the wac composition into the endpoint component), generate otherwise — and always asserts endpoint.id() equals the constructed identity's public key. Matrix row endpoint-identity-wasmtime-wasmtime exercises the injection path on both roles over a relay echo; every other endpoint row now exercises identity-generate. The jco driver (run-endpoint.mjs) constructs through identity-generate the same way.

Gates

fmt-check, clippy, validate-wit, test, probes, full matrix, bench — all green; the crypto boundary call-count budget is unmoved (generate performs exactly the keygen + public-key export bind used to perform; the possession probe's two extra calls exist only on the from-keys path).

Boundary

Persistence mechanics stay out, per #29's scoping: a browser embedder structured-clones the non-extractable CryptoKey into IndexedDB and rehydrates it host-side; the guest-visible storage surface remains polymorph-webcrypto#97, which composes here as just another constructor when it lands.

lann added 2 commits August 7, 2026 20:09
endpoint-options grows an optional identity — an Ed25519
signing/verifying pair carried as polymorph:webcrypto handles — so an
embedder can provision and persist the endpoint's identity instead of
bind minting a fresh one per call. Absent identity, bind behaves as
before. The private key stays behind its webcrypto handle either way;
the signature interface now appears on the world surface because the
options record carries its handles, so the webcrypto package is
vendored into wit/deps.

bind validates a supplied pair — algorithm, sign permission,
public-key shape, and a sign/verify possession probe of the halves
against each other — and fails invalid-argument at bind, rather than
letting a mismatched pair surface as handshake failures against every
peer.

The demo grows inject-identity: it mints the pair through its own
webcrypto imports, hands the handles across the composition to bind,
and requires the endpoint to report the injected public key as its
endpoint-id. The matrix gains endpoint-identity-wasmtime-wasmtime,
exercising injection on both roles over a relay echo.

Fixes #29.
…mandatory

Reshaped from the record form on review. `identity` is now an opaque
resource owned by the package, with each provisioning path a separate
constructor interface — `identity-generate` (fresh mint) and
`identity-from-keys` (webcrypto handles) — so a world imports exactly
the paths it needs, a deployment serves exactly the paths it
implements, and a composition needing an unserved path fails at
composition time. Consumers that never inject no longer see webcrypto
types at all. Constructors validate, so an identity in hand is valid
by construction and bind cannot fail for identity reasons.

endpoint-options becomes a resource in the sibling options-resource
style: the identity is the one mandatory input, expressed structurally
as the constructor argument (borrowed — one identity configures any
number of endpoints, which the consumed-record shape made impossible:
key handles are not cloneable, so one pair could only ever reach one
bind). Everything else is a setter defaulting to nothing granted;
bind's relay-url and one-alpn requirements stay bind-time
invalid-arguments, since both are recorded v0 narrowings rather than
design rules, and a constructor argument would freeze that latitude
into the interface.

The demo constructs its identity explicitly on both paths — from-keys
under --inject-identity, generate otherwise — and always asserts the
endpoint reports the constructed identity's public key. The jco driver
does the same through identity-generate.
@lann lann changed the title Endpoint identity: embedder-injected key pairs on bind Endpoint identity: a resource with per-path constructor interfaces Aug 8, 2026
# Conflicts:
#	endpoint-demo/wit/world.wit
#	host-wasmtime/src/bin/endpoint-demo.rs
@lann
lann merged commit bcaed0f into main Aug 8, 2026
1 check passed
@lann
lann deleted the endpoint-identity branch August 8, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Endpoint identity: embedder-injected, persistable identity

1 participant