Signed Messages for Contract Accounts (draft SEP) #2045
sayweer
started this conversation in
Stellar Ecosystem Proposals
Replies: 1 comment 1 reply
|
Nice write-up. Eunomia (agent treasury on Soroban) would likely be an early user: the treasury owner is a passkey smart account, and today there's no way for them to sign in to a dashboard or prove they control a treasury without sending a transaction. This fills that gap. One question on OZ accounts with scoped context rules: if an account only has CallContract rules (say, a session rule limited to the USDC contract) and no Default rule, |
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This is the discussion for a new SEP that does for contract accounts what SEP-53 does for G accounts: a C address signs a human-readable message, and anyone can check that signature against the account's current rules without submitting a transaction. It came out of #1928 and #2027, where @JakeUrban suggested writing it up as its own SEP next to SEP-53, with the SEP-43 change as a separate follow-up.
Why
A contract account doesn't have a single key. Who can act, with what threshold and under which policy is decided in its
__check_auth. Checking a message signature against one public key ignores all of that, so a signer that was removed yesterday still produces a "valid" signature today. Right now wallets either throw onsignMessagefor C addresses or return their own WebAuthn envelope (Stellar-Wallets-Kit #93, #95, #112), and relying parties have no consistent way to verify those.How it works
SorobanAuthorizationEntryin which the account authorizes one call,verify_message(account, msg), on a small reference verifier contract.msgis a readable map with the domain, a statement, a nonce,issued_atand a few optional fields.account.require_auth()and then always fails with a reserved error. Submitting the entry as a transaction does nothing, and the host rolls the nonce back. It still works like a bearer credential though: anyone who holds it can verify it, including another contract on-chain, somsgshouldn't carry anything secret.__check_authdecides, against current ledger state. Nothing is submitted and there's no fee.domainfrom the requesting origin, the way WebAuthn handlesorigin, so one site can't get a signature addressed to another.Where it stands
The whole flow runs on testnet, with transaction links in the results:
Okinstead, the nonce gets burned, which is why the verifier always fails.require_authgets through without the hash check and is rejected with it.Draft: https://github.com/sayweer/c-sign/blob/main/docs/SPEC.md
Testnet results: https://github.com/sayweer/c-sign/blob/main/docs/EXPERIMENTS.md
Test vectors: https://github.com/sayweer/c-sign/blob/main/vectors/c-sign-v1.json
The reference verifier, a TypeScript library and a passkey sign-in demo are in the same repo (Apache-2.0).
Open questions
domainfrom the request origin workable for you across extensions, WalletConnect and mobile? This is the part I'm least sure about.verify_message_for(account, rp, msg), both sides authorize and nothing reverts, like SEP-45). It drops the always-fail pattern but loses third-party verification. If people prefer it, I can add it.Feedback welcome. Once things settle I'll open the draft PR.
All reactions