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
versionedOptional JSON around armored ciphertext. The underlying encryption remains delegated to rage.
View @ternent/armourWhy 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.
{
"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
Start from an identity
Use a serialized @ternent/identity payload as the capability root for recipient derivation and decryption.
- 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
Keep the ciphertext raw
Armour returns age-compatible ciphertext directly and does not wrap it in a package-specific transport format.
- 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 sourceReady
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.