Skip to main content

Mandate verification

@codespar/sdk/mandate: decode a mandate presentation token and check its Ed25519 signatures offline, with no API call.

2 min read
View MarkdownEdit on GitHub
@codespar/sdkv0.16.0

@codespar/sdk/mandate is a separate entry point of the same package. It answers one question, and it answers it without the network: is this mandate token signed by the agent and the issuer it claims?

import {  } from "@codespar/sdk/mandate";

const  = (..!, {
  : .., // raw 32-byte Ed25519 key, hex
});

if (.) {
  .(.., .., .);
}

Why offline matters. The bank, the auditor and the counterparty can check a mandate without asking CodeSpar anything: the public keys come from the agent's did:web document. The org HMAC that also travels in the token is not offline-verifiable, because checking it needs the org secret; the Ed25519 pair is the part a third party can verify. The same check from a terminal is codespar mandate verify.

verifyMandateToken

ReadverifyMandateToken(token: string, opts?: VerifyMandateOptions): MandateVerification

Offline-verifies a V3 presentation token against the public keys you supply.

ParameterTypeRequiredDescription
tokenstringyesThe presentation token: base64url JSON of the signed fields plus the signature envelope.
opts.agentPublicKeystring | Uint8ArraynoRaw 32-byte Ed25519 agent key, hex or bytes.
opts.issuerPublicKeystring | Uint8ArraynoRaw 32-byte Ed25519 issuer key, hex or bytes.

Returns MandateVerification: verified (true only if at least one carried signature verified and none failed), the decoded mandate, the agentDid and kid when present, and one SignatureCheck for the agent and one for the issuer. Each check reports verified, failed, skipped (carried, but you passed no key for it) or absent.

It throws only when the token cannot be decoded, so a malformed token and a well-formed unverified one are different outcomes.

const  = (, {
  : "7f3c…",
  : "a91b…",
});

..; // "verified" | "failed" | "skipped" | "absent"
..;
..; // UNIX seconds

decodeMandateToken

ReaddecodeMandateToken(token: string): MandateDecodeResult

Splits the envelope from the signed fields and verifies nothing. Returns { ok: true, token } or { ok: false, error }, where error is "invalid_payload" or "mandate_format_unsupported". Use it when you want to read a mandate you are not checking, for example to show scope and ceiling in a UI.

reconstructSigningString

ReadreconstructSigningString(fields: Record<string, unknown>): string

Rebuilds the canonical string the signatures cover: V3 is 14 fields and 13 : separators, V2 is the first 12. Absent optionals render empty so the separator count never changes, purposes is sorted lexicographically and comma-joined with escaping (\ first, then ,), and colons inside agent_kid are emitted verbatim because the string is serialized one way and never re-split.

You need this only to sign or to debug a signature; verifyMandateToken calls it for you.

verifyEd25519

ReadverifyEd25519(signingString: string, signatureB64url: string, pub: Buffer): boolean

One raw signature check. Returns false on malformed input instead of throwing.

agentDidFromKid

ReadagentDidFromKid(kid: string): string

Strips the #<fragment> from a key id (did:web:acme.example#1) and gives back the bare agent DID, which is where you fetch the public keys.

Types

MandateFields is the signed field set: format_version, id, agent_id, type (payment, subscription or delegation), amount as a decimal string, currency, purposes, expires_at in UNIX seconds, the optional max_amount, parent_id and denomination, secret_version, and the two V3-only fields principal_kyc_ref and agent_kid.

DecodedMandateToken adds the envelope: the org HMAC signature, and for V3 the agent_sig, issuer_sig and kid.

The same check elsewhere

Mandate verification | CodeSpar