Node Stamps & Keys

    How receipts are signed and how verifiers fetch keys.

    Minimum Integration, 3 Steps

    1. Extract receipt, Use getAttestationReceipt(bundle) to get the normalized receipt.
    2. Fetch node keys, Call fetchNodeKeys(nodeUrl) to get the public key document.
    3. Verify signature, Use verifyBundleAttestation(bundle, { nodeUrl }) for the full check.

    Receipt Fields

    After a successful attestation, the bundle includes a normalized AttestationReceipt:

    FieldTypeDescription
    attestationIdstringUnique ID of this attestation event
    certificateHashstringMust match bundle.certificateHash
    nodeRuntimeHashstringHash of the node's runtime state at attestation time
    protocolVersionstringProtocol version the node used for verification
    nodeIdstring?Identifier of the attestation node
    attestedAtstring?ISO 8601 timestamp of attestation
    attestorKeyIdstring?kid of the Ed25519 signing key (v0.5.0+)
    signatureB64Urlstring?Base64url-encoded Ed25519 signature (v0.5.0+)
    import { getAttestationReceipt } from '@nexart/ai-execution';
    
    const receipt = getAttestationReceipt(bundle);
    if (receipt) {
      console.log(receipt.attestationId);
      console.log(receipt.attestorKeyId);    // "key-2025-01"
      console.log(receipt.signatureB64Url);  // "aDEKyu...Q"
    }

    Multiple layouts supported: getAttestationReceipt() extracts receipts from both top-level bundle fields and meta.attestation, depending on the producer. The returned object is always normalized to the same AttestationReceipt shape.

    Node Keys Endpoint

    Every attestation node publishes its public keys at a well-known URL:

    GET {nodeUrl}/.well-known/nexart-node.json

    Live endpoint: nexart-canonical-renderer-production.up.railway.app/.well-known/nexart-node.json

    The document contains keys in three formats:

    FormatFieldUse Case
    JWKkey.publicKeyJwkWeb Crypto API (crypto.subtle.importKey)
    SPKI (Base64)key.publicKeySpkiB64Node.js crypto.createPublicKey
    Raw (Base64url)key.publicKeyDirect 32-byte Ed25519 public key
    import { fetchNodeKeys, selectNodeKey } from '@nexart/ai-execution';
    
    const doc = await fetchNodeKeys(
      'https://nexart-canonical-renderer-production.up.railway.app'
    );
    
    // Select key by kid (from receipt.attestorKeyId)
    const { key } = selectNodeKey(doc, receipt.attestorKeyId);
    
    console.log(key.publicKeyJwk);
    // { kty: "OKP", crv: "Ed25519", x: "<base64url>" }
    
    console.log(key.publicKey);
    // "<base64url-encoded 32-byte Ed25519 public key>"

    selectNodeKey selection order: explicit kid argument → activeKid from the node document → first key in the array. Always pass receipt.attestorKeyId when verifying a specific receipt.

    Key Rotation

    The node keys document includes an activeKid field indicating which key is currently used for new attestations. Older keys remain in the document so historical receipts can still be verified.

    • activeKid, The kid of the key used for new attestations
    • Historical keys remain listed with their kid for backward-compatible verification
    • selectNodeKey(doc) defaults to activeKid when no kid is specified
    • When verifying an old receipt, pass its attestorKeyId to selectNodeKey(doc, kid)

    Troubleshooting

    SymptomCodeCauseFix
    No receipt in bundleATTESTATION_MISSINGBundle was never attested, or attested before v0.5.0Re-attest with current SDK, or accept local-only verification
    Signature failsATTESTATION_INVALID_SIGNATUREReceipt was modified after signingObtain a fresh copy of the bundle
    Key not foundATTESTATION_KEY_NOT_FOUNDThe kid in the receipt doesn't match any key in the node documentVerify you are querying the same node that issued the receipt (nodeUrl must match the attesting node). Historical keys should remain published; if missing, contact the node operator
    Key format unsupportedATTESTATION_KEY_FORMAT_UNSUPPORTEDKey has wrong crv or missing fieldsReport to node operator, key document is malformed

    "Stamp incomplete" is not an error. It means the bundle has legacy attestation fields (pre-v0.5.0) without a signed receipt, these may appear at the top level or under meta.attestation. Local integrity verification still works; only the node signature check is unavailable.