Skip to content

Sign smart-account (__check_auth) authorizations in the CLI — CAP-71 AddressWithDelegates + a pluggable auth-entry signer #26

Description

@willemneal

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:

  1. 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."
  2. 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 (AddressSorobanAuthorization, AddressV2SorobanAuthorizationWithAddress, 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

  • stellar signs a SorobanCredentials::AddressWithDelegates (CAP-71) entry with a delegate key.
  • A signer-plugin interface receives __check_auth-symmetric context and returns the credential signature.
  • End-to-end: stellar registry publish --author <C-smart-account> --rule-id N … (delegate-signed) succeeds with the CLI alone.
  • Reference OZ plugin covers External + Delegated; bespoke external signers can be retired.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions