Dev
Two ways to try ATEP: install an experimental alpha from a package registry, or build from the repository. Either way, the examples verify the same test vector, vectors/verify-positive/signed-trust-doc-inline-bundle.cbor, at the reference time 1800000000.
JavaScript: send and verify a message
The shortest way in, with the npm package and no build step. atep.mjs is a small example wrapper (about 100 lines, in examples/quickstart/ of the repository) that does the file handling for you. It is not part of @atep/core at this time: it only calls the package, and the files it writes are ordinary ATEP envelopes. We intend to fold it into the package in a later release; when that happens the import and the details of the API may change. Experimental, like the rest of the alpha (not independently audited; details under Install from a registry below).
# Run these from your project directory, the folder where you keep your code
# Make an empty folder and go into it
mkdir atep-try && cd atep-try
# Create a package.json (accepting every default) and mark the project as ES modules
npm init -y && npm pkg set type=module
# Install the ATEP package from npm (the alpha tag)
npm install @atep/core@alpha
# Download the example wrapper class
curl -O https://raw.githubusercontent.com/atepdev/atep/main/examples/quickstart/atep.mjs
# Download the demo script
curl -o demo.mjs https://raw.githubusercontent.com/atepdev/atep/main/examples/quickstart/demo.mjs
The second download is demo.mjs. This is what is in it; run it with node demo.mjs:
import { ATEP } from "./atep.mjs";
// Open two agents. Each gets a folder holding its keys, created on first use:
// identity.key is the secret (never share it), identity.pub is the public bundle.
// Every key is a hybrid pair: Ed25519 with ML-DSA-65 to sign, X25519 with ML-KEM-768 to encrypt.
const alice = await ATEP.open("./alice");
const bob = await ATEP.open("./bob");
// Alice signs "hello bob", encrypts it so only Bob can read it (using Bob's public
// bundle), and writes the sealed envelope to hello.atep in the current folder.
alice.seal("hello bob", { to: "./bob/identity.pub", out: "hello.atep" });
// Bob reads the file. This decrypts it and verifies Alice's signature, the time and
// that it has not been seen before; only then is the text released.
console.log(bob.read("hello.atep")); // { ok: true, text: 'hello bob', signer: 'atep:...', claims: [] }
// Reading the same envelope again is refused: an envelope cannot be replayed.
console.log(bob.read("hello.atep")); // { ok: false, step: 6, error: 'nonce_replayed' }
To share your public key, alice.exportPublic("./contacts") writes it as atep-<agent id>.pub, for example atep-3fdoguv6hijvfwhizxlf3v6mkbhx4qsjycdrkxyiyg5kt66tylaa.pub. The Agent ID is the hash of the key bundle, so the name checks the contents: when you pass a file with that name as to or as a root, the class refuses it if the contents do not hash to the ID in the name. The name has no colon (Windows does not allow one in a file name). The file holds only public keys and is safe to hand out; the Agent ID is what you compare through a channel you already trust.
The key files are created private (identity.key is the secret). Paths are relative to the folder you run node from, and missing folders in out are created. To make Bob accept only senders who hold a claim from an issuer you trust, open him with ATEP.open("./bob", { roots: ["./issuer/identity.pub"], claim: "operator-of" }). ATEP.check(file) verifies a public, unencrypted document such as an attestation, and an issuer can write one with issuer.attest(subjectId, "operator-of", { out: "operator.atep" }).
Need test keys without the code? The Key Lab on the demo page makes a hybrid identity in your browser and lets you download your secret key as atep-<agent id>.key and your public key as atep-<agent id>.pub (use the secret one with ATEP.fromKey below). They are test keys: see the warnings on that page.
If you already have keys, skip the folder and hand over the secret directly, from a file path or as bytes (for example from a secrets manager). Nothing is written to disk:
// A secret key file you already have, anywhere on disk:
const alice = await ATEP.fromKey("./keys/alice.key");
// Or the secret as bytes, from wherever you keep secrets:
const bob = await ATEP.fromKey(secretBytes);
// Others need your public bundle to send to you; it is on the object:
// alice.bundle (bytes), alice.id (your Agent ID)
// Opening a folder that already contains identity.key and identity.pub
// also loads those keys instead of creating new ones: ATEP.open("./alice")
Install from a registry
These are experimental 0.1.0-alpha releases: there has been no independent security audit, the wire format may change in any 0.x release, and the post-quantum libraries they build on are young. The npm packages are published under the alpha tag and pip needs --pre, so a plain install does not pick them up by accident. The earlier 0.0.1 versions are placeholders with no code.
cargo install atep-cli --version 0.1.0-alpha.5 # the atep command line tool
cargo add [email protected] # the Rust library
npm install @atep/core@alpha # JavaScript and TypeScript (WebAssembly)
npx -y @atep/mcp@alpha # read-only MCP server
pip install --pre atep # Python, import atep_py (pure Python, slow)
Registry pages: atep-cli and atep-core on crates.io, @atep/core and @atep/mcp on npm, atep on PyPI. The test vectors are in the repository, not in the packages: clone it (git clone https://github.com/atepdev/atep) and run the verification from its root, for example atep verify -i vectors/verify-positive/signed-trust-doc-inline-bundle.cbor --now 1800000000.
Build from the repository
Run from the root of the ATEP repository.
Rust CLI
cd rust
cargo build --release -p atep-cli
./target/release/atep verify \
-i ../vectors/verify-positive/signed-trust-doc-inline-bundle.cbor \
--now 1800000000
Prints OK, the signer Agent ID and the envelope fields. Building needs a C compiler or linker.
JavaScript
cd js
npm install
npm run build # needs wasm-bindgen-cli 0.2.129 and the wasm32 target, see js/README.md
node -e 'import("./dist/index.js").then(async m => { await m.init();
const b = require("fs").readFileSync(
"../vectors/verify-positive/signed-trust-doc-inline-bundle.cbor");
console.log(m.verify(new Uint8Array(b), {}, { now: 1800000000 })); })'
Prints { ok: true, signer: 'atep:...' }. The package is the Rust core compiled to WebAssembly. The vector suite also passes under Deno and Bun (in CI on every push) and passed in headless Chromium in a manual run when it had 437 vectors (details in the package README); Firefox, Safari and constrained devices have not been tested.
Python
cd python
python3 -c "
import json
from atep_py.verify import verify_json
n = '../vectors/verify-positive/signed-trust-doc-inline-bundle'
v = json.load(open(n + '.expected.json'))
print(verify_json(open(n + '.cbor', 'rb').read(), v['inputs']['policy']))"
Standard library only, Python 3.8 or later. python3 -m atep_py.vectors check ../vectors runs the 512 of the 533 vectors that apply (it skips by name the 21 for a log and a monitor, which it does not implement) and takes about 30 seconds.
A rejection
./target/release/atep verify -i ../vectors/verify-negative/bad-eddsa-signature.cbor --now 1800000000
REJECTED at step 4 (eddsa_signature_invalid): signature does not verify
Rejections name the step of the ten step algorithm and a stable error code. Data envelopes must be encrypted, so the verification of data vectors also needs the recipient keys that each vector lists. See test vectors.
To see a rejection without installing anything, open the live simulator (opens in a new tab): four simulated units exchange real envelopes in your browser; one is revoked mid-run and refused at a named step, and you can take the fleet controller offline to see certified members keep verifying each other.