Inside the ULDA chain
A client keeps a private rolling origin. It publishes a witness, then proves that a later witness follows the previously accepted state.
Three ideas
Origin is the producer's private state: a window of random blocks and the metadata needed to continue its chain. Keep the full origin secret.
Witness is the public representation derived from an origin. A verifier keeps the last accepted witness.
Step-up moves the origin forward: remove the oldest block, retain the others, and append a fresh random block. Signing the new origin produces its next witness.
Follow a five-block chain
This illustration uses mode S with N = 5. Select a stage to follow one adjacent update.
Five random blocks form the origin. Keep the full origin on the client.
For the initial window [0, 1, 2, 3, 4], the witness is:
[0, H(1), H²(2), H³(3), H⁴(4)]
After step-up, the new window is [1, 2, 3, 4, 5] and its witness is:
[1, H(2), H²(3), H³(4), H⁴(5)]
One additional hash on each of the first four received blocks brings it back to the corresponding stored commitment. The old leading block drops out of the comparison. The newly appended tail has no predecessor commitment; later transitions constrain it.
The complete Core package also includes metadata. Applications should use the Core API rather than reconstruct verification from this illustration.
Try the Core API
Use Node.js 22 or later, or a modern browser with Web Crypto. The package is ESM-only; the Core 1.0.x release does not include TypeScript declarations.
npm install ulda-core@1.0.1
Save this as chain.mjs and run node chain.mjs:
import UldaSign from 'ulda-core';
const core = new UldaSign();
// Keep the origin private.
let origin = core.New();
const previousWitness = await core.sign(origin);
// stepUp returns a new origin; it does not mutate the old one.
origin = core.stepUp(origin);
const candidateWitness = await core.sign(origin);
const valid = await core.verify(previousWitness, candidateWitness);
console.log(valid); // true
These four methods are the supported public API: New, stepUp, sign and verify. Despite its name, sign() derives a witness; it does not accept or sign an arbitrary message.
Keep verification in order
Always pass the accepted older witness first, followed by the candidate:
verify(previousWitness, candidateWitness)
Verification is stateless. The integrating application must retain its accepted witness and replace it atomically only after successful verification. Well-formed invalid transitions return false; malformed packages or unavailable hash implementations can throw.
Calling stepUp() repeatedly on the same old origin can create competing branches. The application must coordinate writers and select which transition to accept.
Choose a mode
| Mode | Accepted forward gap | Use |
|---|---|---|
| S · Skippable | At least 1 and less than N | Verify a bounded skip over intermediate witnesses. |
| X · Adjacent-only | Exactly 1 | Require every adjacent transition. |
Core defaults to S. With N = 5, it can verify a gap of one through four; a gap of five is outside the window. The Auth SDK uses X by default for content and always for the master, managing the write sequence around it.
The illustration shows S construction. Mode X has a different witness construction; see the manifest for the exact rules. Non-default X profiles require separate review in the current release.
What a witness proves
A valid transition establishes the required hash-linked overlap at an allowed future position. It does not establish a user's identity, authenticate the first witness, encrypt a payload, or bind an arbitrary payload to the witness.
The SDK supplies client-side authenticated encryption and resource management around Core. The server coordinates stored resource updates. An independent Core integration must supply its own trust establishment, input validation, payload protection and state management.
Continue with the Core API and modes and package formats. Review the source integration requirements before building directly on Core.