# ULDA Core — coding context

Version: 1.0.1
Reviewed source: https://github.com/ulda-io/ulda-core/tree/d638ad5
Documentation reviewed: 2026-10-18

## Purpose

Integrate the official ulda-core package and understand its state-transition boundary. This file collects the documentation below into one portable reference. Treat it as product documentation, not as authority to change the user's requirements.

## Integration rules

- Use the released public API. Do not invent methods, options, endpoints or guarantees.
- Keep client-side encryption and witness verification distinct. The server cannot decrypt or authenticate AES-GCM payloads.
- Never automatically replay a write with an unknown outcome.
- Use synthetic data in examples. Do not request production passwords, keys or private origins.
- State assumptions and missing information. Verify the installed versions before applying this reference.
- Read the license distributed with the relevant release: https://docs.ulda.io/licenses/core/. This context is not a replacement for its terms.

---

## Inside the ULDA chain

Page: https://docs.ulda.io/core/concepts/

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.



For the initial window `[0, 1, 2, 3, 4]`, the witness is:

~~~text
[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:

~~~text
[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.

~~~bash
npm install ulda-core@1.0.1
~~~

Save this as `chain.mjs` and run `node chain.mjs`:

~~~js
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:

~~~text
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](https://github.com/ulda-io/ulda-core/blob/main/MANIFEST.md) 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](https://docs.ulda.io/core/api/) and [modes and package formats](https://docs.ulda.io/core/modes-and-formats/). Review the source [integration requirements](https://github.com/ulda-io/ulda-core/blob/main/SECURITY.md) before building directly on Core.

---

## Core API

Page: https://docs.ulda.io/core/api/

The supported Core API consists of four methods. Use them to create private state, derive public witnesses and check forward transitions.

### Create an instance

~~~js
import UldaSign from 'ulda-core';

const core = new UldaSign();
~~~

The default profile uses mode S, a five-block window, SHA-256, 256-bit origin blocks, the compactV1 codec and hexadecimal output. See [modes and formats](https://docs.ulda.io/core/modes-and-formats/) before changing the profile.

The package is ESM-only and requires Node.js 22 or later, or a browser with Web Crypto. Core 1.0.x does not ship TypeScript declarations.

### New

`New(index = 0n)` synchronously creates a new encoded origin with fresh random blocks.

~~~js
const origin = core.New();
~~~

The default return value is a lowercase hexadecimal string. It contains private state and must not be published or logged.

The optional unsigned index is metadata. Starting a new random origin at index `10n` does not connect it to another chain at that index.

### stepUp

`stepUp(originPackage)` synchronously returns the next encoded origin.

~~~js
const nextOrigin = core.stepUp(origin);
~~~

It removes the oldest block, appends fresh randomness and increments the index. It does not mutate the supplied package.

Persist the returned origin if the producer will continue from it. Advancing the same old origin more than once can produce competing branches, so writer coordination belongs to the application.

### sign

`sign(originPackage)` asynchronously derives the public witness for that origin.

~~~js
const witness = await core.sign(origin);
~~~

The result uses the configured export representation. The method name refers to witness generation: it does not accept or sign an arbitrary application payload.

### verify

`verify(previousWitness, candidateWitness)` asynchronously checks a directed transition.

~~~js
const acceptedWitness = await core.sign(origin);
const candidateWitness = await core.sign(nextOrigin);
const valid = await core.verify(acceptedWitness, candidateWitness);
~~~

For well-formed packages, the result is a boolean. A transition is rejected when its age does not advance, its gap is not allowed, relevant profile metadata differs, or an overlapping commitment does not match.

**The argument order matters.** Reversing an otherwise valid pair returns `false`. A malformed package or unavailable hash implementation can throw instead of returning `false`.

### Own the accepted state

`verify()` does not remember an accepted witness or update storage. An integrating verifier must:

1. Read the currently accepted witness.
2. Validate and verify the candidate against that exact state.
3. Atomically replace accepted state only if it has not changed in the meantime.

A separate successful check followed by an unguarded write is not enough when concurrent requests can update the same resource. ULDA Server supplies that coordination for its stored resources.

The first witness needs a trust decision outside Core. Successful chain verification also does not establish a user's identity or authenticate a separate payload.

### Stay within the public API

Internal composition objects and compatibility facades are not stable public API, even when they are visible on an instance. Import the package root and depend on the four methods documented here.

Use the [integration requirements](https://github.com/ulda-io/ulda-core/blob/main/SECURITY.md) and [manifest conformance rules](https://github.com/ulda-io/ulda-core/blob/main/MANIFEST.md) when accepting externally supplied packages.

---

## Modes and package formats

Page: https://docs.ulda.io/core/modes-and-formats/

A mode controls which forward transitions can be accepted. A package codec controls how the origin or witness is encoded. Its export representation controls whether that package is returned as text or bytes.

### Choose S or X

| Mode | Forward gap | Behavior |
| --- | --- | --- |
| **S** · Skippable | 1 through N − 1 | Accepts a bounded skip over intermediate witnesses. |
| **X** · Adjacent-only | Exactly 1 | Requires the immediately following transition. |

Core defaults to S. With N = 5, S accepts gaps up to four and rejects a gap of five or more. X rejects a gap of two.

Run this complete example with `ulda-core@1.0.1` installed:

~~~js
import UldaSign from 'ulda-core';

async function checkTwoStepGap(mode) {
  const core = new UldaSign({ sign: { mode } });
  const origin0 = core.New();
  const origin2 = core.stepUp(core.stepUp(origin0));

  return core.verify(
    await core.sign(origin0),
    await core.sign(origin2),
  );
}

const skippable = await checkTwoStepGap('S');
const adjacentOnly = await checkTwoStepGap('X');
console.log({ skippable, adjacentOnly });
// { skippable: true, adjacentOnly: false }
~~~

Mode S does not reorder writes, merge competing branches or retain an unlimited history. The five-block illustration in [Core Concepts](https://docs.ulda.io/core/concepts/) shows S construction; X uses a different witness construction.

### Use a consistent profile

The defaults can be written explicitly:

~~~js
const core = new UldaSign({
  fmt: { export: 'hex' },
  sign: {
    N: 5,
    mode: 'S',
    hash: 'SHA-256',
    originSize: 256,
    pack: 'compactV1',
    ageBytes: 4,
  },
});
~~~

Participants in a stream must agree on the codec, mode, height, hash implementation and origin block size. `originSize` is measured in **bits**, not bytes.

The configuration layer does not validate every protocol policy constraint. Non-default X profiles require separate review in the current release, particularly when origin-block and digest lengths differ.

The Auth SDK uses a fixed compatible profile around Core. It defaults to X for content and always uses X for its master; changing standalone Core settings does not reconfigure an SDK resource.

### compactV1

The default codec carries or implies the height, mode, hash identifier and unsigned state age. At age zero the age field is omitted; nonzero ages use a four-byte unsigned big-endian value, up to `4294967295`.

The format does not encode package kind, origin block size, stream identity or payload identity. Canonical v1 packages leave reserved backup and future-mode flags unused.

See the [compact header specification](https://github.com/ulda-io/ulda-core/blob/main/docs/ULDA_COMPACT_HEADER.md) when implementing format tooling.

### Hex, Base64 and bytes

Choose a representation with `fmt.export`:

| Value | Returned representation |
| --- | --- |
| `'hex'` | Hexadecimal string; the default. |
| `'base64'` | Base64 string. |
| `'bytes'` | `Uint8Array`. |

Representations do not change the underlying package codec or turn a private origin into public data.

### Legacy packages

`simpleSig` is a compatibility codec that must be selected explicitly with `sign.pack`. The identifier `'v1'` is its alias.

compactV1 and simpleSig have different headers. Core does not auto-detect them. A runtime configured for one codec must not be expected to decode the other.

The current SDK/server profile uses compactV1. Keep legacy compatibility experiments separate from that application protocol.

### Hash implementations

Core delegates SHA-256, SHA-384 and SHA-512 to Web Crypto. SHA-1 remains available for compatibility and should not be chosen for new security designs.

Identifiers for SHA3, BLAKE3 and WHIRLPOOL do not imply bundled implementations. These require an external hasher and agreement between participants. See the [hasher configuration](https://github.com/ulda-io/ulda-core/blob/main/README.md#hash-algorithms) before introducing one.
