Reference · 06

NaCl and JWT.

Signing, encryption and tokens inside your methods.

nacl and jwt are synchronous crypto APIs for deployed code. Import them from @flower-js/sdk/crypto or the SDK root. They only work inside Flower, not in Node or a browser.

NaCl

  • Bytes in and out are Uint8Array. Inputs are never modified.
  • Key arguments also accept managed key handles.
  • Wrong types or lengths throw. Failed authentication returns null (or false for detached verify).
  • Random functions need system entropy, which is only available in mutations. In queries, pass explicit seeds, keys and nonces.
APIContract
nacl.randomBytes(length): Uint8ArrayRandom bytes. Mutations only (unless a custom PRNG is set). Length is capped by result budgets.
nacl.setPRNG(source: NaClPRNG | null): voidReplace the random source for NaCl in this callback; null restores the default. You are responsible for its safety; a weak one can repeat keys or nonces. Doesn't affect JWT nonces.
nacl.secretbox(message, nonce, key): Uint8ArrayXSalsa20-Poly1305 encryption. 32-byte key, 24-byte nonce; output is a 16-byte tag plus ciphertext. Never reuse a nonce with the same key.
nacl.secretbox.open(box, nonce, key): Uint8Array | nullDecrypt; null if tampered, wrong key, or too short.
nacl.scalarMult(secret, publicKey): Uint8ArrayX25519, 32-byte inputs and output. Returns all zeros for low-order peer points; your protocol must check for that.
nacl.scalarMult.base(secret): Uint8ArrayX25519 public key from a 32-byte secret.
nacl.box(message, nonce, publicKey, secretKey): Uint8ArrayPublic-key encryption. 32-byte keys, 24-byte nonce, 16 bytes overhead.
nacl.box.open(box, nonce, publicKey, secretKey): Uint8Array | nullDecrypt with the peer's public key and your secret key; null on failure.
nacl.box.before(publicKey, secretKey): Uint8Array | SharedKeyPrecompute a shared key: 32 bytes for raw keys, an opaque SharedKey for managed ones. Valid only in the current callback.
nacl.box.after(message, nonce, sharedKey): Uint8ArraySame as secretbox with the shared key.
nacl.box.open.after(box, nonce, sharedKey): Uint8Array | nullSame as secretbox.open.
nacl.box.keyPair(): NaClKeyPairRandom X25519 key pair. Mutations only.
nacl.box.keyPair.fromSecretKey(secret): NaClKeyPairKey pair from a 32-byte secret. Works in queries.
nacl.sign(message, secretKey): Uint8ArrayEd25519; returns 64-byte signature plus message. Secret key is 64 bytes (seed + public key).
nacl.sign.open(signedMessage, publicKey): Uint8Array | nullVerify and return the message; null if invalid. 32-byte public key.
nacl.sign.detached(message, secretKey): Uint8Array64-byte Ed25519 signature.
nacl.sign.detached.verify(message, signature, publicKey): booleanVerify a detached signature; false if invalid. Rejects weak keys and malleable signatures.
nacl.sign.keyPair(): NaClKeyPairRandom Ed25519 key pair. Mutations only.
nacl.sign.keyPair.fromSeed(seed): NaClKeyPairKey pair from a 32-byte seed. Works in queries.
nacl.sign.keyPair.fromSecretKey(secret): NaClKeyPairKey pair from a 64-byte secret. Throws if its public half doesn't match the seed.
nacl.hash(message): Uint8ArraySHA-512, 64 bytes. Not a password hash.
nacl.verify(a, b): booleanConstant-time equality. false for different lengths or two empty arrays.
nacl.box.after.open(box, nonce, key): Uint8Array | nullAlias of secretbox.open.
Every NaCl length constant
APIContract
nacl.secretbox.keyLength32 bytes.
nacl.secretbox.nonceLength24 bytes.
nacl.secretbox.overheadLength16 bytes.
nacl.scalarMult.scalarLength32 bytes.
nacl.scalarMult.groupElementLength32 bytes.
nacl.box.publicKeyLength32 bytes.
nacl.box.secretKeyLength32 bytes.
nacl.box.sharedKeyLength32 bytes.
nacl.box.nonceLength24 bytes.
nacl.box.overheadLength16 bytes.
nacl.box.after.keyLength32 bytes.
nacl.box.after.nonceLength24 bytes.
nacl.box.after.overheadLength16 bytes.
nacl.sign.publicKeyLength32 bytes.
nacl.sign.secretKeyLength64 bytes.
nacl.sign.seedLength32 bytes.
nacl.sign.signatureLength64 bytes.
nacl.hash.hashLength64 bytes.

Formats and names match TweetNaCl, except Ed25519 is stricter (see sign.detached.verify and sign.keyPair.fromSecretKey). These are primitives: peer identity, key rotation and nonce management are up to you.

JWT

APIContract
jwt.sign(claims, key, options): stringSign a compact JWS with HS256 (32+ byte secret), RS256, ES256 or EdDSA. Deterministic; works in queries. Adds no exp or other claims for you.
jwt.verify<Claims>(token, key, options): JWTVerified<Claims>Check the signature against your key and allowed algorithms, then check standard claims. Returns claims and header; throws if invalid.
jwt.encrypt(claims, key, options = {}): stringCompact JWE, dir + A256GCM only. 32-byte key or managed A256GCM handle. Omit the nonce only in a mutation; if you pass one, keep it unique. Anyone with the key can also create tokens.
jwt.decrypt<Claims>(token, key, options = {}): JWTVerified<Claims>Decrypt and check standard claims. Throws on any failure.
APIContract
NaClKeyPair{publicKey: Uint8Array; secretKey: Uint8Array}, in separate buffers.
NaClPRNG(output: Uint8Array, length: number) => void. Fill exactly length bytes. If it throws, the output is zeroed and the error propagates.
JWTAlgorithm"HS256" | "RS256" | "ES256" | "EdDSA". ES256 is P-256; EdDSA is Ed25519. The token header never picks the algorithm.
JWTKeyUint8Array | string | ManagedKey. HMAC/AES keys are bytes; RSA/EC keys are PEM strings or DER bytes. NaCl raw keys are not valid here.
JWTKeyFormat"raw" | "pem" | "der". Defaults: pem for strings, raw for bytes; request der explicitly. PEM: PKCS#8, SPKI and PKCS#1. Certificates and SEC1 are not supported.
JWTClaimsReadonly<Record<string, Json>>. exp, nbf and iat are in seconds, not milliseconds.
JWTSignOptions{algorithm; keyFormat?; kid?; typ?}. algorithm is required for raw keys; typ defaults to JWT.
JWTValidationOptions{issuer?; audience?: readonly string[]; subject?; clockToleranceSeconds?; requireExpiration?; typ?}. exp is required by default; tolerance defaults to 0. If the token has aud, you must pass audience.
JWTVerifyOptionsJWTValidationOptions & {algorithms: readonly JWTAlgorithm[]; keyFormat?}. A required, nonempty allowlist; don't mix symmetric and asymmetric algorithms.
JWTEncryptOptions{nonce?: Uint8Array; kid?: string; typ?: string}. Nonce is 12 bytes and must be unique per key. With a managed key, Flower sets kid.
JWTProtectedHeader{alg: JWTAlgorithm | "dir"; enc?: "A256GCM"; kid?: string; typ?: string}. kid is informational; it doesn't select a key.
JWTVerifiedJWTVerified<Claims extends object = JWTClaims> = {claims: Claims; protectedHeader: JWTProtectedHeader}. The type parameter doesn't validate your custom claims.

Claim checks:

  • exp is required unless requireExpiration: false, and is always checked when present.
  • nbf is checked when present. iat is type-checked only; there's no max age.
  • issuer, subject and typ must match exactly. At least one audience must match.
  • Time is Flower's invocation clock. Queries that verify tokens re-run as time passes, so an expired token isn't accepted from cache.
  • Only alg, kid, typ (and enc) headers are allowed. No JWKS fetching and no unverified decode. Validate custom claims yourself.

Randomness and limits

  • Built-in randomness works only in mutations: not in queries, derived values, or bundle initialization. Elsewhere, pass explicit seeds, keys and nonces.
  • Method arguments, stored values and results are JSON. Encode bytes (for example base64) yourself.
  • Raw keys stored in bundles or records are ordinary replicated data. Use managed keys to keep private bytes out of your code.
  • Input and output sizes are each capped by FLOWER_RESULT_MAX_BYTES; native memory by FLOWER_RUST_MEMORY_BYTES. Exceeding a budget fails the whole call, even if your code catches the error.
  • A long crypto call can't be interrupted midway; the deadline is checked before and after.

See the usage guide, native implementation, and binary guest ABI.