Home
Home/resources/verify a signed record offline with …
Ed25519 signaturesSHA-256 hashes8 SDKs
Status
Sign inStart free
Home
EngineeringVerificationEd25519Document Anchor

How to Verify an Invoance Record With a Standard Ed25519 Library

Each document anchor and AI attestation comes back with the bytes that were signed, the signature over them, and the public key. That is everything an Ed25519 library needs. Here is the check in Node and Python, then pinning the published key and comparing the record to the actual file.

Adeola OkunolaFounder, Invoance6 October 2026·7 min read
On this page
  1. Why the API returns the signature material
  2. What comes back with a record
  3. Checking the signature in Node
  4. The same check in Python
  5. Pin the published key, not the key in the record
  6. Then match the record to the thing in your hand
  7. Why this matters when a record is challenged

Why the API returns the signature material

When you fetch a document anchor or an AI attestation from the Invoance API, the response includes the bytes that were signed, the Ed25519 signature over them, and the public key that signed them. It does not include a field that says the record is verified.

That is deliberate. If we returned a verdict, you would be relying on us to have computed it honestly, and anyone you later showed the record to would be relying on us as well. Returning the material instead means the check can be run by whoever is holding the record, with an Ed25519 implementation of their choosing. It needs no Invoance account and no API call, and if the record is already on disk it needs no network either.

This post goes through that check end to end: what the three fields contain, how to verify the signature in Node and in Python, how to pin the key we publish for your organisation so the check is tied to you rather than to the record, and how to compare the record to the file or text you were actually handed.

What comes back with a record

A document anchor is returned by GET /v1/document/{event_id}. Alongside the document hash, the reference, the metadata and the issuing organisation, the response includes signed_payload_b64, signature_b64 and public_key_b64. All three are standard base64. The signed payload decodes to a small JSON object: a version number, the tenant id, the actor that made the call, the event type, the SHA-256 of the document as hex, and the timestamp the record was written.

An AI attestation is returned by GET /v1/ai/attestations/{attestation_id}. The same three values are there as signed_payload, signature and public_key, encoded as hex rather than base64, together with signature_alg, which is ed25519. The signed payload here is a longer canonical record: the attestation id, the tenant, the attestation type, the SHA-256 hashes of the input and the output, a hash of the full payload, the model provider, name and version, and the creation time.

In both cases the signature covers the decoded payload bytes exactly as returned. You do not need to rebuild anything or know the canonicalisation rules. Decode, verify, then read the JSON to see what was signed.

The signed payload inside a document anchor, after base64 decoding
{
  "v": 2,
  "tenant_id": "b58e03d7-4c1f-4b8e-9a0d-2f6c7e1d41c6",
  "actor_type": "api_key",
  "actor_id": "7c1e4a0b-9d2e-4f3a-8b1c-0a5e6d7f9f2d",
  "event_type": "document.anchored",
  "document_hash_hex": "9f2c81d0c3a7e5b64d1f0a8e2b9c7d6f5e4a3b2c1d0e9f8a7b6c5d4e3f2a1b0e41ab",
  "ts": "2026-10-06T10:42:07.184233Z"
}

Checking the signature in Node

Node's built-in crypto module verifies Ed25519 with no extra dependency. The only wrinkle is that Node wants the public key in SPKI DER form rather than as 32 raw bytes, so you prefix the raw key with the fixed twelve-byte header from RFC 8410 before loading it. The Invoance Node SDK does exactly this internally.

The example below takes a document anchor response and prints whether the signature holds and what was signed. PostgreSQL base64 output can include line breaks, and Node ignores whitespace when decoding, so the values can be used as returned.

Verify a document anchor with node:crypto only
import { createPublicKey, verify } from "node:crypto";

// record is the JSON body from GET /v1/document/{event_id}
const signed = Buffer.from(record.signed_payload_b64, "base64");
const signature = Buffer.from(record.signature_b64, "base64");
const rawKey = Buffer.from(record.public_key_b64, "base64"); // 32 bytes

// Node loads Ed25519 keys as SPKI DER: fixed RFC 8410 prefix + raw key.
const publicKey = createPublicKey({
  key: Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), rawKey]),
  format: "der",
  type: "spki",
});

const valid = verify(null, signed, publicKey, signature);
console.log(valid ? "signature valid" : "signature INVALID");
console.log(JSON.parse(signed.toString("utf8")));

The same check in Python

PyNaCl wraps libsodium and verifies Ed25519 in two lines. It raises on a bad signature rather than returning false, so wrap the call. For an AI attestation, swap base64 decoding for bytes.fromhex and the field names for signed_payload, signature and public_key. Everything else is identical.

Verify a document anchor with PyNaCl
import base64
import json
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError

# record is the JSON body from GET /v1/document/{event_id}
signed = base64.b64decode(record["signed_payload_b64"])
signature = base64.b64decode(record["signature_b64"])
public_key = base64.b64decode(record["public_key_b64"])

try:
    VerifyKey(public_key).verify(signed, signature)
    print("signature valid")
    print(json.loads(signed))
except BadSignatureError:
    print("signature INVALID")

Pin the published key, not the key in the record

A signature that verifies against the public key embedded in the same response proves internal consistency: the payload and the signature belong together. It does not yet prove that the key belongs to the organisation you think it does. Someone who could forge a whole record could forge the embedded key along with it.

The fix is to get the key from somewhere the record cannot influence. Invoance publishes every tenant's public key at a fixed address keyed by the organisation's verified domain. Fetch it once, store it, and compare it to the key in each record before you verify. If they differ, stop. If they match, the signature now ties the record to that organisation, not just to itself.

The published key is base64url without padding, so decode it accordingly before comparing bytes. Node accepts base64url directly. In Python, append padding and use urlsafe_b64decode.

A tenant's published signing key, by verified domain
curl https://api.invoance.com/v1/keys/acme.example

{
  "domain": "acme.example",
  "public_key": "tR9kq2mX4pL8vN1cB6zY3hW0jF7aE5dG2uS9oK4iQ8w",
  "algorithm": "ed25519",
  "key_id": "invoance:pk_b58e03d7"
}
Verify against the key you fetched and pinned, not the key that arrived with the record. The first proves who signed it. The second only proves the record agrees with itself.

Then match the record to the thing in your hand

A valid signature tells you the record is what Invoance signed at that time. The second half of any real check is whether the record describes the file or the text you were actually given.

For a document, hash the file you hold with SHA-256 and compare the hex digest to document_hash_hex inside the signed payload. On a Mac or Linux machine that is one command. If the digests match, the file is byte for byte the one that was anchored at the recorded time. If they differ, the file changed after anchoring, or it was never the anchored file at all, and you now know that with certainty rather than suspicion.

For an AI attestation, hash the prompt and the output the same way and compare to input_hash and output_hash. If you would rather not do the hashing yourself, the attestation's verify endpoint accepts the raw text and does the comparison, and the public verification page does it in a browser. Both are conveniences. The offline route is always available and needs nothing from us.

Does the file match the anchored hash?
shasum -a 256 contract-final.pdf
# 9f2c81d0c3a7e5b64d1f0a8e2b9c7d6f5e4a3b2c1d0e9f8a7b6c5d4e3f2a1b0e41ab  contract-final.pdf

# Compare to document_hash_hex in the signed payload. Equal means unchanged.

Why this matters when a record is challenged

Consider a counterparty producing a contract eighteen months after signing with a different liability cap. Your side anchored the executed PDF on signing day. The other side's lawyer, their expert, or the court can take your anchor record, check the signature against your published key with the code above, hash the PDF they were handed, and compare. Nobody in that room has to trust you, and nobody has to trust Invoance. The maths either holds or it does not.

The same shape applies to an AI output in a regulatory inquiry, a policy document in an employment dispute, or an invoice in a payment fraud case. In every one, the useful property is that the person asking the question can run the check themselves. A verification that depends on calling the vendor is only as good as the vendor's availability, honesty and continued existence. A verification that runs against a public key and a standard algorithm depends on none of those.

That is why Invoance returns the signed bytes, the signature and the key with every record instead of a verification result. A result is something you take our word for. The material is something you can check.

  • Verification overview Every way to check an Invoance record: API, public page and offline.
  • Document anchoring Anchor a file by hash and read the record back.
  • Verifying AI attestations The verify endpoint and the offline signature check in every SDK.
  • SDKs Node and Python ship offline verifiers; the other six mirror the surface.
On this page
  1. Why the API returns the signature material
  2. What comes back with a record
  3. Checking the signature in Node
  4. The same check in Python
  5. Pin the published key, not the key in the record
  6. Then match the record to the thing in your hand
  7. Why this matters when a record is challenged
Adeola Okunola

Adeola Okunola

Founder, Invoance
About the author

I'm Adeola, founder of Invoance.

I build proof infrastructure for audit logs, AI attestations, and business records that need to stand up to security, compliance, and legal scrutiny.

Most systems document what happened. Invoance helps prove it.

All articles by Adeola
Related product

AI Attestation

The evidence layer for AI agent actions: what an agent was asked, what it decided, and what it did.

Open AI Attestation
Keep readingAll articles
Product7 min read

Introducing Document Anchor: Cryptographic Proof That a Document Existed, Unchanged, at a Specific Moment

Contracts get disputed. Filings get questioned. Wire instructions get spoofed. Document Anchor replaces 'trust our DMS' with cryptographic proof anyone can verify, and breaks the BEC playbook in the process.

Adeola Okunola·7 April 2026Read
Trust Infrastructure11 min read

How to Tell If a PDF Has Been Edited or Backdated (And How to Actually Prove It Wasn't)

A PDF's Created and Modified dates are just editable text, and a clean re-save erases the very traces forensics looks for. You cannot reliably detect tampering or backdating after the fact. Here is why, and how to prove a document is unchanged with a hash, a signature, and a timestamp its author never controlled.

Adeola Okunola·22 June 2026Read
AI Governance10 min read

AI Attestation: What It Is, Why It Matters, and How to Implement It

AI systems make decisions that affect loans, diagnoses, hiring, and contracts. When those decisions are challenged, organizations need proof of what the model produced, when, and with what inputs. AI attestation provides that proof.

Adeola Okunola·9 March 2026Read

Try it on the free plan.

Create a signed record and verify it yourself.

Start freeRead the docs

Verified documents and records. Invoance records the exact version of every invoice, report and notice your company sends, so the people who receive them can confirm they have the version you sent, without taking your word for it.

Products

  • Evidence Rooms
  • Audit Logs
  • Event Ledger
  • AI Attestation
  • Document Anchoring
  • Traces

Developers

  • Documentation
  • API reference
  • SDKs
  • How it works
  • How traces seal
  • System status

Verify

  • Audit Log
  • Event
  • AI Attestation
  • Document
  • Trace

Company

  • Company overview
  • What is Invoance
  • Pricing
  • Security
  • Compliance teams
  • Finance teams
  • Partners
  • Brand assets
  • Resources
  • Help center
  • Contact
© 2025 – 2026 Invoance, Inc. All rights reserved.© 2026 Invoance, Inc. All rights reserved.
PrivacyLegal noticeLegal FAQ