Class Attestation

A parent-signed statement binding a derived child key to a name and a grant. Chains of attestations (Identity → Handle → SubHandle) allow any third party to verify sessions offline using only the root public key, with grant constraints enforced at verification time.

Tokens have the form base64url(payload).base64url(signature) and are deterministic: the same inputs (including jti and now) always produce byte-identical tokens.

  • * import { ed25519 } from '@noble/curves/ed25519.js';
    *
    * // Parent (signer) and child (subject) keys — use a CSPRNG in practice:
    * const signerPriv = ed25519.utils.randomPrivateKey();
    * const signerPub = ed25519.getPublicKey(signerPriv);
    * const childPub = ed25519.getPublicKey(ed25519.utils.randomPrivateKey());
    *
    * const A = await Attestation.issue(signerPriv, childPub, 'station-001', {
    * audiences: ['ev-app.com'],
    * scopes: ['charge:start', 'charge:stop'],
    * maxSessionTtl: 7200,
    * subNamePatterns: ['connector-*'],
    * });
    *
    * const ok = await Attestation.verifySignature(A.token, signerPub); // true
    * const payload = Attestation.decode(A.token); // structure only — no sig, no time
    *

Accessors

Methods

  • Verifies the token signature against a public key.

    Parameters

    • token: string
    • signerPublicKey: Uint8Array

    Returns Promise<boolean>

    false for a bad signature or a wrong key. Returns false for any invalid signature, including malformed lengths. It still throws AttestationError MALFORMED/FORMAT for structurally invalid tokens (see decode).

  • Wildcard name matching used for subNamePatterns. A pattern matches if it equals name, or ends with * and name starts with the pattern's prefix. A lone * matches everything.

    Parameters

    • name: string
    • patterns: string[]

    Returns boolean

    Attestation.matchNamePattern('connector-1', ['connector-*']); // true
    Attestation.matchNamePattern('meter-1', ['connector-*']); // false
    Attestation.matchNamePattern('anything', ['*']); // true
  • Creates and signs a new attestation.

    The subject name is canonicalized (NFKC → lowercase → trim) before signing; the subject public key must be exactly 32 bytes. Serialization is deterministic: fields are emitted in a fixed order and undefined grant fields are omitted from the JSON entirely (distinguishable from []).

    Parameters

    • signerPrivateKey: Uint8Array

      32-byte Ed25519 private key of the parent.

    • subjectPublicKey: Uint8Array

      32-byte Ed25519 public key being attested.

    • subjectName: string

      Child name; canonicalized before signing.

    • grant: AttestationGrant

      Constraints enforced by verifiers (Mode 2).

    • Optionalopts: {
          ttlSeconds?: number;
          expiresAt?: number;
          jti?: string;
          now?: number;
      }

      Lifetime control: ttlSeconds (default one year), expiresAt (overrides ttlSeconds), jti (default random UUID), now (fixed clock for deterministic tests).

      • OptionalttlSeconds?: number
      • OptionalexpiresAt?: number
      • Optionaljti?: string
      • Optionalnow?: number

    Returns Promise<Attestation>

    The attestation with payload, token and jti accessors.

    MALFORMED/FORMAT if the grant is structurally invalid (bad scopes/TTL/audiences or a wildcard not at the end of a subNamePatterns entry).

    If subjectPublicKey is not 32 bytes.

    const att = await Attestation.issue(rootPriv, childPub, 'Station-1',
    { scopes: ['read'], maxSessionTtl: 3600 }, { ttlSeconds: 600 });