Filed on the theahaco/stellar-cli fork. Issue/PR numbers below like stellar/stellar-cli#NNNN reference the upstream repo.
Summary
stellar cannot sign an operation whose require_auth() resolves to a smart-contract account (a C… address with a custom __check_auth). This blocks the whole class of smart-account-authored operations from the CLI alone — most visibly stellar registry publish with a smart-account --author, but equally any contract invoke whose auth is a smart account.
This issue proposes completing the stellar#2026 signing-plugin exploration with a concrete design, so that every smart-account-authored action can be done with stellar alone — no project-specific signer binary.
Where it stands today (v28, cmd/soroban-cli/src/signer/mod.rs)
sign_soroban_authorizations has two hard stops:
- Contract-address authors are rejected. A
SorobanCredentials::Address / AddressV2 credential is only signed when its address is an ed25519 G… account with a matching local key, emitting the host builtin AccountEd25519Signature. A contract (C…) address returns Error::MissingSignerForAddress — "This address is for a contract. This means we're using a custom smart-contract account. Currently the CLI doesn't support that yet."
- CAP-71 delegates are skipped.
SorobanCredentials::AddressWithDelegates is skipped with "Skipping auth entry with delegated signers: not supported yet; entry left unsigned."
So a smart-account author either errors (C… address) or goes on-chain unsigned and is rejected by __check_auth. The stellar registry plugin inherits this exactly — publish / publish_hash delegate build+sign+send to contract invoke, so --author <C…> cannot be signed.
The good news: the v1/v2 preimage split is already done (Address → SorobanAuthorization, AddressV2 → SorobanAuthorizationWithAddress, cf. stellar#2701). Only the smart-account credential shape and the CAP-71 delegate assembly are missing.
Why the CLI can't just do it generically
A smart account's signature is application-defined — its __check_auth dictates the shape, so the core CLI can't synthesize it blindly. Concretely, OpenZeppelin stellar-accounts accounts expect:
AuthPayload {
signers: Map<Signer, Bytes>, // Signer::External(verifier, key) | Signer::Delegated(addr)
context_rule_ids: Vec<u32>, // which context rule authorizes this invocation
}
signed over a rule-id-bound digest sha256(signature_payload || XDR(ScVec[ScU32(rule_id), …])), and — for delegated signers — delivered via a CAP-71 AddressWithDelegates credential whose delegate signs the SorobanAuthorizationWithAddress preimage. Passkey/WebAuthn accounts have yet another shape. So the app-specific part must be pluggable, while two pieces are generic and belong in core.
Proposal
1. First-class CAP-71 AddressWithDelegates signing (generic, protocol-level).
Implement the skipped arm: given a delegate key, sign the SorobanAuthorizationWithAddress preimage (already computed for AddressV2) and assemble AddressWithDelegates { address_credentials, delegates: [SorobanDelegateSignature { address, signature, nested_delegates }] }. This is CAP-71 mechanics, not app-specific.
2. A pluggable auth-entry signer — the concrete shape stellar#2026 is looking for.
Per @dmkozh's steer in stellar#2026, a plugin should receive the signing context, symmetric with __check_auth — the payload hash(es) to sign plus the invocation, credential address, nonce, and signature-expiration — and return the credential signature ScVal (and, when delegated, the delegate signatures). Support both transports:
- the existing envelope-in / envelope-out CLI plugins (the soroban-examples signer), and
- a "primitive" protocol so plugins don't re-parse the transaction:
{ payloads, invocation, address, nonce, expiration, credential_kind } → signature ScVal.
This should sit on the Sign trait / transaction pipeline being extracted in stellar#2632 and stellar#2453 so plugins don't import the whole CLI.
3. Command surface. Expose per-entry signing via stellar auth (stellar#2630: ls / sign / set) and/or tx auth (stellar#1493): sign an individual SorobanAuthorizationEntry (optionally via a signer plugin) and splice it back into the envelope, so contract invoke / registry publish compose it. stellar tx send already submits a supplied envelope untouched, so the transport half is done.
4. A reference OZ smart-account plugin (in-tree example or documented): builds the AuthPayload + context-rule selection for both External and Delegated signers. This subsumes OpenZeppelin's brozorec/smart-account-sign (which today displays but skips Delegated signers), and doubles as the acceptance demo.
Acceptance
Related
Summary
stellarcannot sign an operation whoserequire_auth()resolves to a smart-contract account (aC…address with a custom__check_auth). This blocks the whole class of smart-account-authored operations from the CLI alone — most visiblystellar registry publishwith a smart-account--author, but equally anycontract invokewhose auth is a smart account.This issue proposes completing the stellar#2026 signing-plugin exploration with a concrete design, so that every smart-account-authored action can be done with
stellaralone — no project-specific signer binary.Where it stands today (v28,
cmd/soroban-cli/src/signer/mod.rs)sign_soroban_authorizationshas two hard stops:SorobanCredentials::Address/AddressV2credential is only signed when its address is an ed25519G…account with a matching local key, emitting the host builtinAccountEd25519Signature. A contract (C…) address returnsError::MissingSignerForAddress— "This address is for a contract. This means we're using a custom smart-contract account. Currently the CLI doesn't support that yet."SorobanCredentials::AddressWithDelegatesis skipped with "Skipping auth entry with delegated signers: not supported yet; entry left unsigned."So a smart-account author either errors (
C…address) or goes on-chain unsigned and is rejected by__check_auth. Thestellar registryplugin inherits this exactly —publish/publish_hashdelegate build+sign+send tocontract invoke, so--author <C…>cannot be signed.The good news: the v1/v2 preimage split is already done (
Address→SorobanAuthorization,AddressV2→SorobanAuthorizationWithAddress, cf. stellar#2701). Only the smart-account credential shape and the CAP-71 delegate assembly are missing.Why the CLI can't just do it generically
A smart account's signature is application-defined — its
__check_authdictates the shape, so the core CLI can't synthesize it blindly. Concretely, OpenZeppelinstellar-accountsaccounts expect:signed over a rule-id-bound digest
sha256(signature_payload || XDR(ScVec[ScU32(rule_id), …])), and — for delegated signers — delivered via a CAP-71AddressWithDelegatescredential whose delegate signs theSorobanAuthorizationWithAddresspreimage. Passkey/WebAuthn accounts have yet another shape. So the app-specific part must be pluggable, while two pieces are generic and belong in core.Proposal
1. First-class CAP-71
AddressWithDelegatessigning (generic, protocol-level).Implement the skipped arm: given a delegate key, sign the
SorobanAuthorizationWithAddresspreimage (already computed forAddressV2) and assembleAddressWithDelegates { address_credentials, delegates: [SorobanDelegateSignature { address, signature, nested_delegates }] }. This is CAP-71 mechanics, not app-specific.2. A pluggable auth-entry signer — the concrete shape stellar#2026 is looking for.
Per @dmkozh's steer in stellar#2026, a plugin should receive the signing context, symmetric with
__check_auth— the payload hash(es) to sign plus the invocation, credential address, nonce, and signature-expiration — and return the credentialsignatureScVal (and, when delegated, the delegate signatures). Support both transports:{ payloads, invocation, address, nonce, expiration, credential_kind } → signature ScVal.This should sit on the
Signtrait / transaction pipeline being extracted in stellar#2632 and stellar#2453 so plugins don't import the whole CLI.3. Command surface. Expose per-entry signing via
stellar auth(stellar#2630:ls/sign/set) and/ortx auth(stellar#1493): sign an individualSorobanAuthorizationEntry(optionally via a signer plugin) and splice it back into the envelope, socontract invoke/registry publishcompose it.stellar tx sendalready submits a supplied envelope untouched, so the transport half is done.4. A reference OZ smart-account plugin (in-tree example or documented): builds the
AuthPayload+ context-rule selection for bothExternalandDelegatedsigners. This subsumes OpenZeppelin'sbrozorec/smart-account-sign(which today displays but skipsDelegatedsigners), and doubles as the acceptance demo.Acceptance
stellarsigns aSorobanCredentials::AddressWithDelegates(CAP-71) entry with a delegate key.__check_auth-symmetric context and returns the credential signature.stellar registry publish --author <C-smart-account> --rule-id N …(delegate-signed) succeeds with the CLI alone.External+Delegated; bespoke external signers can be retired.Related
AddressWithDelegatesstill skipped)stellar authcommand stellar/stellar-cli#2630 —stellar authcommand · Add atx authcommand stellar/stellar-cli#1493 —tx authcommandstellar-tx-pipelinecrate stellar/stellar-cli#2632 —stellar-tx-pipelinecrate · Extract a standalone Rust transaction builder crate from the CLI stellar/stellar-cli#2453 — standalone transaction-builder cratebrozorec/smart-account-sign,soroban-examplesenvelope signer