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.

identity → recipient → ciphertext

Stable
Identity
Shared capability root
Modes
Recipients or passphrase
Output
Age-compatible ciphertext
Boundary
No artifact format here

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.

proof.json
import {
  initArmour,
  encryptForIdentities,
  decryptWithIdentity
} from "@ternent/armour"

await initArmour()

const ciphertext = await encryptForIdentities({
  identities: [identity],
  data: new TextEncoder().encode("portable payload"),
  output: "armor"
})

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

Armour owns the bridge from identity to age-compatible encryption. Sealed artifacts belong elsewhere in the stack.

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.