Guide · 10

Sign and encrypt without seeing the keys.

Use NaCl and JWT inside your methods. Private keys stay in the server; your code only gets handles.

Declare the key your code needs. An operator creates the real key and binds it to that declaration. Your TypeScript only gets a handle; private bytes stay in the server.

Declare, create, bind

sessions.ts · declaration and methods
import { define, key, mutation, query } from "@flower-js/sdk";
import { jwt } from "@flower-js/sdk/crypto";

const sessions = key("sessions", {
  algorithm: "Ed25519", usages: ["sign", "verify"],
});
const issue = mutation("session.issue", (ctx, user: string) => {
  // Authenticate the caller and authorize this user before issuing a token.
  if (typeof user !== "string" || !user) throw new TypeError("user required");
  return jwt.sign({ sub: user, iss: "shop", aud: "shop-api",
    exp: ctx.now() / 1000 + 900 }, sessions);
});
const check = query("session.check", (_ctx, token: string) =>
  jwt.verify(token, sessions, { issuer: "shop", audience: ["shop-api"] }).claims);

export default define({ keys: [sessions], http: { issue, check } });
  • This example doesn’t authenticate callers. Add your own authorization before exposing issue.
  • A declaration grants nothing on its own. An operation needs both the deployed declaration and an operator’s binding.
  • jwt.verify checks the key version, expiry, issuer and audience.

An operator sets up the key:

Provision; then bind the declaration
# Install the same protected wrapping file on every authorized server.
umask 077
openssl rand 32 > /secure/flower-wrapping.key
# Start each server with FLOWER_KEYRING_FILE=/secure/flower-wrapping.key.

# SDK CLI: supply FLOWER_URL and FLOWER_ADMIN_TOKEN for your cluster.
flower key generate session-signing --algorithm Ed25519 --request-id create-session-key
flower key bind sessions session-signing --usages sign,verify --request-id bind-sessions
flower deploy sessions.ts --request-id deploy-sessions
flower key rotate session-signing --request-id rotate-sessions-2
flower key list
flower key cache
  • Every authorized server needs the same 32-byte wrapping file. Back it up separately.
  • The default listener is unencrypted h2c. Enable native TLS to protect operator tokens.
  • For a tenant database, add --partition north to deploy and key commands, or use client.partition("north").

To import an existing key, seal it locally first. Only the encrypted envelope is sent:

Import only when an existing identity is needed
# Native runtime executable: plaintext stays on this local machine.
/path/to/native/flower key seal --wrapping-key-file /secure/flower-wrapping.key \
  --format pem < private.pem > sealed.json
# SDK CLI sends only the authenticated encrypted envelope.
flower key import imported-signing sealed.json --algorithm Ed25519 \
  --request-id import-signing

What each key can do

Managed key algorithms
AlgorithmUse it for
Ed25519JWT and NaCl signing and verification
P256, RSA, HS256JWT
A256GCMEncrypted JWT
XSalsa20Poly1305, X25519Matching NaCl operations
  • publicKey(handle) needs a publicKey grant.
  • nacl.box.before(peerPublic, privateHandle) returns a shared key you can use only inside that callback. It needs the derive and encrypt/decrypt grants.
  • Raw Uint8Array keys still work for interoperability.

Randomness:

  • Native randomness and automatic JWT nonces need a mutation. In a query, pass explicit unique nonces: 24 bytes for NaCl, 12 for AES-GCM.
  • nacl.setPRNG only affects NaCl calls in your code, not key generation or JWT nonces. Each callback starts fresh, so a custom stateful generator can repeat outputs.

Rotate and revoke

  • Rotate makes a new version for signing and encryption. JWTs carry their version in kid, so old tokens still verify until that version is revoked.
  • NaCl has no kid. Save keyVersion(key).version next to each ciphertext, and decrypt with keyVersion(key, savedVersion). That handle can’t sign or encrypt.
  • Revoke one version with flower key revoke NAME --version N, or all versions by leaving out --version. After revoking the active version, rotate before new operations can run.
  • Retire keeps verification and decryption. keyDestroy removes the current encrypted material and leaves a tombstone. It does not erase backups.
  • Declaring managed keys makes every query and watch in the app read from the quorum, including aliases marked replica-local.
  • Revocation doesn’t cancel work that has already started. Results that depend on a revoked or locked key fail rather than go stale.
  • Old versions stay stored (encrypted) until you garbage-collect them. Plan disk space.
  • Private bytes never enter collections, bundles, responses or the Raft log.

Key cache and wrapping key

  • FLOWER_KEY_CACHE_BYTES sets the cache for prepared keys. Default 16 MiB; 0 disables it.
  • FLOWER_KEY_CACHE_TTL_MS reloads cached keys after this age. Default 0 (never). It is not a guarantee that key material leaves memory by then.
  • flower key cache shows the cache on the node you connect to.

The wrapping file is read at startup; changes need a restart. To rotate it:

  1. Set the new file in FLOWER_KEYRING_FILE and list the old ones in FLOWER_KEYRING_PREVIOUS_FILES (a JSON array). Restart.
  2. Call keyRewrap on every key catalog.
  3. Remove the old files and restart again.
  • When a partition moves, the destination must unlock every unrevoked version. A missing or wrong wrapping key pauses the move until fixed.
  • Not supported yet: KMS/HSM backends and general exportable secrets.
  • Your JS never sees private material, but the server holds it in memory.

See the managed-key reference for every method, import format, permission and limit, and the NaCl and JWT reference for all functions.