UNPKG

@stellar/stellar-sdk

Version:

A library for working with the Stellar network, including communication with the Horizon and Soroban RPC servers.

442 lines (441 loc) 21.4 kB
import xdr from "./xdr.js"; import { Keypair } from "./keypair.js"; type BufferLike = ArrayBuffer | Buffer | Uint8Array; /** * A callback for signing an XDR structure representing all of the details * necessary to authorize an invocation tree. * * @param preimage - the entire authorization envelope whose hash you should * sign, so that you can inspect the entire structure if necessary (rather * than blindly signing a hash) * @param payload - the 32-byte signing payload, i.e. the sha256 hash of the * preimage bytes (`hash(preimage.toXDR())`), provided as a convenience so * you never have to re-derive it (e.g. for HSMs or remote signers that only * accept a digest) * * @returns one of the following: * * - the signature of the payload as a naked buffer, implying it is signed by * the key corresponding to the public key in the entry you pass to * {@link authorizeEntry} (decipherable from its * `credentials().address().address()`), * - an object with the `signature` alongside an explicit `publicKey` string * identifying the Ed25519 signer, or * - an object with a `signatureScVal`: an arbitrary, caller-built * {@link xdr.ScVal} that is placed verbatim into the credentials' * `signature` field. Use this for custom account contracts (smart wallets, * passkey/WebAuthn signers, etc.) whose `__check_auth` expects a signature * structure other than the built-in Stellar account * `{public_key, signature}` vector. No Ed25519 verification is performed on * this variant, and no `scvVec` wrapping is applied — you own the exact * shape. The optional `address` selects which credential node receives the * signature (like `forAddress` on {@link authorizeEntry}, which takes * precedence if both are given). */ export type SigningCallback = (preimage: xdr.HashIdPreimage, payload: Buffer) => Promise<BufferLike | { signature: BufferLike; publicKey: string; } | { signatureScVal: xdr.ScVal; address?: string; }>; /** * Actually authorizes an existing authorization entry using the given * credentials and expiration details, returning a signed copy. * * This "fills out" the authorization entry with a signature, indicating to the * {@link Operation.invokeHostFunction} its attached to that: * - a particular identity (i.e. signing {@link Keypair} or other signer) * - approving the execution of an invocation tree (i.e. a simulation-acquired * {@link xdr.SorobanAuthorizedInvocation} or otherwise built) * - on a particular network (uniquely identified by its passphrase, see * {@link Networks}) * - until a particular ledger sequence is reached. * * This one lets you pass either a {@link Keypair} (or, more accurately, * anything with a `sign(Buffer): Buffer` method) or a callback function (see * {@link SigningCallback}) to handle signing the envelope hash. * * @param entry - an unsigned authorization entry * @param signer - either a {@link Keypair} instance or a function (see * {@link SigningCallback}) which receives the * {@link xdr.HashIdPreimage} input payload plus its 32-byte signing hash * and returns EITHER * * (a) an object containing a `signature` of the hash of the raw payload * bytes as a Buffer-like and a `publicKey` string representing who just * created this signature, * (b) just the naked signature of the hash of the raw payload bytes (where * the signing key is implied to be the address in the `entry`), or * (c) an object containing a `signatureScVal` — an arbitrary, caller-built * {@link xdr.ScVal} written verbatim as the credentials' signature, * for custom account contracts (smart wallets, passkey/WebAuthn * signers) whose `__check_auth` expects a non-Ed25519 signature shape. * * Option (b) is JUST for backwards compatibility and will be * removed in the future. * @param validUntilLedgerSeq - the (exclusive) future ledger sequence number * until which this authorization entry should be valid (if * `currentLedgerSeq==validUntil`, this is expired) * @param networkPassphrase - the network passphrase is incorporated into the * signature (see {@link Networks} for options) * * If using the `SigningCallback` variation, the signer is assumed to be * the entry's credential address unless you use the variant that returns * the object. * * @param forAddress - which credential node the signature should be written * to. Only relevant for `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES`, where * a single entry can be signed by the top-level account and/or any of its * (possibly nested) delegates. Per CAP-71-01 every one of these signers * signs the *same* payload (bound to the top-level address), so the * signature produced here is written to whichever node(s) carry * `forAddress`. When omitted, the signature is written to the top-level * credentials, which preserves the behavior for `SOROBAN_CREDENTIALS_ADDRESS` * / `SOROBAN_CREDENTIALS_ADDRESS_V2` and for accounts whose signing key * differs from the credential address (e.g. multisig). * * @see authorizeInvocation * @example * ```ts * import { * SorobanRpc, * Transaction, * Networks, * authorizeEntry * } from '@stellar/stellar-sdk'; * * // Assume signPayloadCallback is a well-formed signing callback. * // * // It might, for example, pop up a modal from a browser extension, send the * // transaction to a third-party service for signing, or just do simple * // signing via Keypair like it does here: * function signPayloadCallback(preimage, payload) { * // `payload` is hash(preimage.toXDR()) — inspect `preimage` if you want * // to display/verify what is being authorized before signing. * return signer.sign(payload); * } * * function multiPartyAuth( * server: SorobanRpc.Server, * // assume this involves multi-party auth * tx: Transaction, * ) { * return server * .simulateTransaction(tx) * .then((simResult) => { * tx.operations[0].auth.map(entry => * authorizeEntry( * entry, * signPayloadCallback, * currentLedger + 1000, * Networks.TESTNET) * ); * * return server.prepareTransaction(tx, simResult); * }) * .then((preppedTx) => { * preppedTx.sign(source); * return server.sendTransaction(preppedTx); * }); * } * ``` */ export declare function authorizeEntry(entry: xdr.SorobanAuthorizationEntry, signer: Keypair | SigningCallback, validUntilLedgerSeq: number, networkPassphrase: string, forAddress?: string): Promise<xdr.SorobanAuthorizationEntry>; export interface AuthorizeInvocationParams { signer: Keypair | SigningCallback; validUntilLedgerSeq: number; invocation: xdr.SorobanAuthorizedInvocation; networkPassphrase: string; publicKey?: string; /** * Build `SOROBAN_CREDENTIALS_ADDRESS_V2` (CAP-71) credentials instead of the * legacy `SOROBAN_CREDENTIALS_ADDRESS`. V2 credentials bind the address into * the signed payload but are only valid on networks that have activated * CAP-71, so leave this off until the activation vote passes for your target * network. The default flips to `true` once V2 becomes mandatory. * @defaultValue false */ authV2?: boolean; } /** * This builds an entry from scratch, allowing you to express authorization as a * function of: * - a particular identity (i.e. signing {@link Keypair} or other signer) * - approving the execution of an invocation tree (i.e. a simulation-acquired * {@link xdr.SorobanAuthorizedInvocation} or otherwise built) * - on a particular network (uniquely identified by its passphrase, see * {@link Networks}) * - until a particular ledger sequence is reached. * * This is in contrast to {@link authorizeEntry}, which signs an existing entry. * * @param params - the parameters for building and signing the authorization * - `signer`: either a {@link Keypair} instance (or anything with a * `.sign(buf): Buffer-like` method) or a function which takes a payload (a * {@link xdr.HashIdPreimageSorobanAuthorization} instance) input and returns * the signature of the hash of the raw payload bytes (where the signing key * should correspond to the address in the `entry`) * - `validUntilLedgerSeq`: the (exclusive) future ledger sequence * number until which this authorization entry should be valid (if * `currentLedgerSeq==validUntilLedgerSeq`, this is expired) * - `invocation`: the invocation tree that we're authorizing * (likely, this comes from transaction simulation) * - `networkPassphrase`: the network passphrase is incorporated into * the signature (see {@link Networks} for options) * - `publicKey`: the public identity of the signer (when providing a * {@link Keypair} to `signer`, this can be omitted, as it just uses * {@link Keypair.publicKey}) * - `authV2`: build `SOROBAN_CREDENTIALS_ADDRESS_V2` (CAP-71) credentials * rather than the legacy `SOROBAN_CREDENTIALS_ADDRESS`. Defaults to `false`; * only enable it for networks that have activated CAP-71. * * @see authorizeEntry */ export declare function authorizeInvocation(params: AuthorizeInvocationParams): Promise<xdr.SorobanAuthorizationEntry>; /** * Builds the {@link xdr.HashIdPreimage} whose hash a signer must sign to * authorize `entry`. This is the low-level signature payload used by * {@link authorizeEntry}, exposed for callers that drive signing themselves — * most notably for `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES`, where the * client (not simulation) decides which delegates sign and how. * * For `SOROBAN_CREDENTIALS_ADDRESS` this is the legacy, non-address-bound * `ENVELOPE_TYPE_SOROBAN_AUTHORIZATION` preimage. For `SOROBAN_CREDENTIALS_ADDRESS_V2` * and `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES` it is the address-bound * `ENVELOPE_TYPE_SOROBAN_AUTHORIZATION_WITH_ADDRESS` preimage (CAP-71). For the * delegates variant this single payload — bound to the *top-level* address — is * what the top-level account and every (nested) delegate each sign. * * To get the raw bytes to sign, hash the XDR: `hash(preimage.toXDR())`. * * @param entry - the authorization entry to build the payload for * @param validUntilLedgerSeq - the expiration ledger committed into the payload * (must match the `signatureExpirationLedger` on the credentials you submit) * @param networkPassphrase - the network passphrase mixed into the payload * @throws `Error` if `entry` carries source-account or otherwise non-address * credentials */ export declare function buildAuthorizationEntryPreimage(entry: xdr.SorobanAuthorizationEntry, validUntilLedgerSeq: number, networkPassphrase: string): xdr.HashIdPreimage; /** * A delegate signer to attach to a * `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES` entry via * {@link buildWithDelegatesEntry}. */ export interface DelegateSignature { /** the delegate's address (`G…` account or `C…` contract). */ address: string; /** * the delegate's signature value. Defaults to a `scvVoid` placeholder, which * you can fill afterwards with {@link authorizeEntry} (passing this address * as `forAddress`) or by editing the entry directly. */ signature?: xdr.ScVal; /** signers this delegate in turn delegates to (recursive). */ nestedDelegates?: DelegateSignature[]; } /** Parameters for {@link buildWithDelegatesEntry}. */ export interface BuildWithDelegatesParams { /** * an existing `SOROBAN_CREDENTIALS_ADDRESS` or * `SOROBAN_CREDENTIALS_ADDRESS_V2` entry — typically one returned by * simulation — whose address credentials should be wrapped. */ entry: xdr.SorobanAuthorizationEntry; /** the expiration ledger sequence stored on the top-level credentials. */ validUntilLedgerSeq: number; /** the delegate signers to attach. */ delegates: DelegateSignature[]; /** * the top-level account's signature. Defaults to `scvVoid`, which is valid * for accounts that authorize purely via delegated signers (CAP-71-01). */ signature?: xdr.ScVal; } /** * Builds a `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES` authorization entry by * wrapping the address credentials of an existing `ADDRESS`/`ADDRESS_V2` entry * (e.g. one returned by simulation) together with a caller-provided set of * delegate signers. * * Simulation never emits the delegates variant on its own — which accounts use * delegated authentication is account-specific policy known only to the client * (much like a multisig policy). This helper just assembles the wrapper XDR; * you supply the delegate tree (addresses and, optionally, signatures). To * produce the signatures, build the shared payload with * {@link buildAuthorizationEntryPreimage} on the returned entry and sign it, * or fill each node afterwards with {@link authorizeEntry} (passing the * signer's address as `forAddress`). * * Each delegates array (the top-level set and every `nestedDelegates`) is * sorted by address in ascending order, and duplicate addresses within an array * are rejected, as the protocol requires (CAP-71-01) — otherwise the host * rejects the entry. * * @param params - see {@link BuildWithDelegatesParams} * @throws `Error` if `entry` is not an `ADDRESS`/`ADDRESS_V2` entry, or if any * delegates array contains a duplicate address. */ export declare function buildWithDelegatesEntry(params: BuildWithDelegatesParams): xdr.SorobanAuthorizationEntry; /** * Internal helper — intentionally NOT re-exported from `base/index.js`, so it * is not part of the public SDK API. Shared with the contract package, which * imports it directly from this module. If a public need arises, add it to the * explicit auth re-exports in `base/index.ts`. * * Extracts the {@link xdr.SorobanAddressCredentials} from any address-based * Soroban credential, regardless of which credential type variant is used. * * This unifies access across `SOROBAN_CREDENTIALS_ADDRESS`, * `SOROBAN_CREDENTIALS_ADDRESS_V2` (which carries identical fields but binds * the address into the signature payload), and * `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES` (which wraps the same address * credentials alongside a set of delegate signatures). * * @param credentials - the credentials to inspect * @returns the inner address credentials, or `null` for source-account * credentials (which carry no address payload) */ export declare function getAddressCredentials(credentials: xdr.SorobanCredentials): xdr.SorobanAddressCredentials | null; /** The credential arm of a {@link xdr.SorobanAuthorizationEntry}. */ export type AuthEntryCredentialType = "sourceAccount" | "address" | "addressV2" | "addressWithDelegates"; /** * A single ed25519 signature parsed out of a credential node's signature * value, in the map format written by {@link authorizeEntry}. */ export interface AuthEntrySignature { /** the signer's public key, as a `G…` strkey. */ publicKey: string; /** the raw 64-byte ed25519 signature. */ signature: Buffer; } /** * One node of an authorization entry that can carry a signature: the top-level * address credentials and, for `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES`, * each (possibly nested) delegate. */ export interface AuthEntrySigner { /** the node's address (`G…` account or `C…` contract). */ address: string; /** * whether a signature payload is present on this node (i.e. its signature is * neither `scvVoid` nor an empty `scvVec`). For contract (`C…`) addresses * this only means *something* is attached — whether it satisfies the * contract's `__check_auth` cannot be verified client-side. */ signed: boolean; /** * the signature payload parsed as the SDK's standard ed25519 format (a vec * of `{public_key, signature}` maps, see {@link authorizeEntry}), or `null` * when the payload has some other, signer-defined shape (as custom accounts * such as WebAuthn/passkey wallets use). Of the two unsigned placeholder * forms, an empty `scvVec` parses as `[]` while `scvVoid` (not a vec at all) * parses as `null` — check `signed` rather than this field to tell whether a * node is unsigned. */ signatures: AuthEntrySignature[] | null; /** the raw signature value, whatever its shape. */ rawSignature: xdr.ScVal; } /** * A structured, read-only view of a {@link xdr.SorobanAuthorizationEntry}, * returned by {@link inspectAuthEntry}. */ export interface AuthEntryInfo { credentialType: AuthEntryCredentialType; /** the authorizing address, or `null` for source-account credentials. */ address: string | null; /** the credential nonce, or `null` for source-account credentials. */ nonce: bigint | null; /** * the (exclusive) ledger sequence until which the signature is valid, or * `null` for source-account credentials. Note that unsigned entries commonly * carry a placeholder (often `0`) until {@link authorizeEntry} sets it. */ signatureExpirationLedger: number | null; /** * every node that can carry a signature: the top-level credentials first, * then (for the delegates variant) each delegate, depth-first. Empty for * source-account credentials. */ signers: AuthEntrySigner[]; /** * whether every signer node carries a signature payload. Always `false` for * source-account credentials (which have no signature nodes — they are * instead covered by the transaction envelope signature; use * {@link checkAuthEntryReadiness} for a submit check). For the delegates * variant note that an account's policy may accept an unsigned top-level * node when its delegates have signed (CAP-71-01) — consult `signers` if you * support that. */ signed: boolean; /** the invocation tree this entry authorizes. */ invocation: xdr.SorobanAuthorizedInvocation; } /** The result of {@link checkAuthEntryReadiness}. */ export interface AuthEntryReadiness { /** `true` when the entry is fully signed and not expired. */ ready: boolean; /** * `true` when `currentLedgerSeq >= signatureExpirationLedger` (expiration is * exclusive). Always `false` for source-account credentials. */ expired: boolean; /** addresses of signer nodes that carry no signature payload. */ unsignedBy: string[]; } /** * Decodes a {@link xdr.SorobanAuthorizationEntry} into a plain, typed summary: * which credential variant it uses, which address authorizes it, its nonce and * expiration ledger, and — for every node that can carry a signature (the * top-level credentials plus any CAP-71 delegates) — whether it is signed and, * when the payload uses the SDK's standard ed25519 format, by which keys. * * This is the read-side complement to {@link authorizeEntry} / * {@link authorizeInvocation}: those fill entries with signatures, this * inspects what an entry (e.g. one returned by transaction simulation, or * received from a counterparty in a multi-party signing flow) requires and * already carries, without reaching into raw XDR accessors. * * @param entry - the authorization entry to inspect * @returns a {@link AuthEntryInfo} summary of the entry * * @see checkAuthEntryReadiness * @example * ```ts * const info = inspectAuthEntry(entry); * if (!info.signed && info.address !== null) { * console.log(`${info.address} still needs to sign`, info.signers); * } * ``` */ export declare function inspectAuthEntry(entry: xdr.SorobanAuthorizationEntry): AuthEntryInfo; /** * Reports whether an authorization entry is ready to submit at a given ledger: * fully signed and not yet expired. * * Source-account entries are always ready — they carry no signature or * expiration of their own and are instead covered by the transaction envelope * signature. * * The current ledger sequence is taken as a parameter (fetch it from a source * you trust, e.g. `rpc.Server.getLatestLedger`) rather than looked up here, so * this stays a pure decode with no network dependency. * * For `SOROBAN_CREDENTIALS_ADDRESS_WITH_DELEGATES`, this conservatively * requires *every* node (top-level and all delegates) to be signed. An * account's policy may accept an unsigned top-level node when its delegates * have signed (CAP-71-01); if you support that, check * {@link inspectAuthEntry}'s `signers` yourself. * * @param entry - the authorization entry to check * @param currentLedgerSeq - the network's current ledger sequence, compared * (exclusively) against the entry's `signatureExpirationLedger` * @returns a {@link AuthEntryReadiness}: `ready`, `expired`, and which * addresses are still `unsignedBy` * @throws `Error` if `currentLedgerSeq` cannot represent a uint32 ledger * sequence (non-integer, negative, or above 2^32 - 1), which would make the * expiration comparison unreliable */ export declare function checkAuthEntryReadiness(entry: xdr.SorobanAuthorizationEntry, currentLedgerSeq: number): AuthEntryReadiness; export {};