armour

Encrypt for identities with explicit age-compatible primitives.

Armour is the bridge between @ternent/identity and @ternent/rage. It derives age-compatible recipients from shared identities, keeps recipient and passphrase modes explicit, and gives browser and package surfaces the same clean encryption contract.

Identity stays the capability root. Rage stays the crypto engine. Armour keeps encryption explicit and portable.

Encryption is not signing. Authenticity belongs to Seal.

armour-envelope.json

versioned
Kindarmour-envelope
Moderecipients
Bridge@ternent/identity -> @ternent/rage
CiphertextAge-compatible armor
MetadataOptional JSON transport only
BoundaryEncryption is not signing

Optional JSON around armored ciphertext. The underlying encryption remains delegated to rage.

View @ternent/armour

Why Armour

The bridge layer

Armour is an opinionated protocol library for identity-backed encryption. It derives age-compatible recipients and secret keys through @ternent/identity, delegates encryption and decryption to @ternent/rage, and keeps the public contract explicit across browser and package surfaces.

Identity-backed

Start from one serialized identity model and derive the age-compatible capability you need without splitting the concept in two.

Explicit modes

Recipient mode and passphrase mode stay separate on purpose. Armour does not guess which contract you meant.

Age-compatible output

Ciphertext comes from rage and stays aligned with age expectations and tooling instead of inventing a new format.

Async-first

Public APIs stay Promise-based and initialization remains explicit through initArmour().

Encryption Model

Raw ciphertext, clear boundaries

Armour does not define an envelope or portable artifact format. It focuses on explicit encryption and decryption, leaving sealed transport objects and proof-bearing formats to Seal.

armour-envelope.json
optional container
{
  "version": 1,
  "kind": "armour-envelope",
  "mode": "recipients",
  "encoding": "armor",
  "ciphertext": "-----BEGIN AGE ENCRYPTED FILE-----\n...\n-----END AGE ENCRYPTED FILE-----\n",
  "recipients": [
    "age1x7k5m0example9a2n4d4ptf9g5x2v3s7m6p8u4wq"
  ],
  "metadata": {
    "purpose": "document-share"
  }
}

The envelope stays explicit and versioned.

It carries armored ciphertext without changing age semantics or implying authenticity.

Surfaces

One encryption model. Multiple surfaces.

Armour keeps the same bridge contract across the JavaScript package and the browser app, so the mental model does not change when the surface does.

JavaScript package

Use @ternent/armour directly when you want identity-based encryption, passphrase mode, and explicit control in your own runtime.

Browser app

The browser surface uses the same language and the same model. It is not a separate encryption product with different rules.

Concord ecosystem

Armour is the ergonomic bridge for Ternent surfaces that already use @ternent/identity and need age-compatible encryption without muddying boundaries.

How it works

Encrypt for identities without changing the primitive

Create or import an identity, encrypt to one or more identities, and move raw ciphertext between environments without changing the underlying contract.

  1. 1

    Start from an identity

    Use a serialized @ternent/identity payload as the capability root for recipient derivation and decryption.

  2. 2

    Choose an explicit mode

    Encrypt for identities when you have recipients, or use passphrase mode when that is the right contract for the job.

  3. 3

    Keep the ciphertext raw

    Armour returns age-compatible ciphertext directly and does not wrap it in a package-specific transport format.

  4. 4

    Decrypt with the matching capability

    Decrypt with the derived age secret key from the identity, or with the passphrase when passphrase mode was used.

Armour keeps the bridge stable while identity and rage keep their own responsibilities.

For Developers

A clean layer above rage

@ternent/armour is the opinionated library layer above @ternent/rage. It adds identity integration and browser-safe helpers without changing the underlying crypto responsibilities or introducing artifact semantics.

@ternent/armour

@ternent/identity

@ternent/rage

JavaScript

Encrypt directly for identities

import { createIdentity } from "@ternent/identity"
import {
  initArmour,
  encryptTextForIdentities,
  decryptTextWithIdentity
} from "@ternent/armour"

await initArmour()

const identity = await createIdentity()
const ciphertext = await encryptTextForIdentities({
  identities: [identity],
  text: "hello world"
})

const plaintext = await decryptTextWithIdentity({
  identity,
  data: ciphertext
})

Identity-based APIs are the first-class surface. Recipient derivation comes from @ternent/identity and encryption comes from @ternent/rage.

View package source

Ready

Use one identity model. Keep encryption explicit.

Start from the shared identity model, use the package directly, and keep encryption, signing, and artifact responsibilities separate across every surface.

ternent.dev

© 2026.