Skip to main content
Version: Current

Cryptography

Opscotch provides cryptographic capabilities at two layers: the packager for key generation and workflow package security, and the runtime for in-workflow cryptographic operations.

This page covers the user-facing cryptographic surface: key formats, key generation, package signing and encryption, bootstrap key configuration, and the workflow CryptoContext API.

Cryptography Overview

Opscotch uses libsodium (also known as Sodium) for all cryptographic operations. libsodium is a modern, well-audited cryptographic library that provides secure, easy-to-use primitives.

Key Concepts

  • Purpose: The role a key plays in cryptographic operations. Opscotch supports four purposes: sign, authenticated, anonymous, and symmetric.
  • Type: Whether the key is secret (private) or public. Key type can be secret or public.
  • Key format: All keys are represented as hex-encoded strings in configuration and API responses.

BYOK: Bring Your Own Keys

Opscotch supports BYOK workflows through multiple key provisioning options. This is a core design principle - you control your cryptographic keys throughout their lifecycle.

Key Provisioning Options

  1. External generation: Generate keys using external tools, convert to hex, configure in bootstrap or supply to the packager
  2. Packager key-gen: Use the /key-gen endpoint to generate libsodium-compatible keys
  3. Runtime registration: Generate or import keys within workflow JavaScript

Keys can be:

  • Preloaded in bootstrap for startup availability
  • Registered on-demand within workflow processors
  • Used for package signing, encryption, or runtime operations

The key format (hex-encoded libsodium keys) ensures interoperability with standard libsodium implementations for external key generation. See Key Size Reference for the exact formats expected.

Why BYOK Matters

  • Key isolation: Keep production keys separate from package builds
  • Key rotation: Update keys without rebuilding packages
  • Compliance: Meet regulatory requirements for key management
  • Security: Keys never leave your controlled environment until needed

Cryptography in Packaging

The packager provides key generation and cryptographic operations for securing workflow packages. See the Packaging documentation for details on running the packager.

Key Generation

The packager's /key-gen endpoint generates key pairs suitable for use with Opscotch's cryptographic system.

GET http://<packager-host>/key-gen?purpose=<purpose>
ParameterValuesDescription
purposesignEd25519 key pair for digital signatures
authenticatedX25519 key pair for authenticated encryption
anonymousX25519 key pair for anonymous encryption
symmetricX25519-Salsa20-Poly1305 symmetric key

Response format:

{
"purpose": "<purpose>",
"publicKey": "<hex-encoded public key>",
"secretKey": "<hex-encoded secret key>"
}

Key Size Reference

PurposeKey TypeHex Lengthlibsodium Primitive
signsecret128 chars (64 bytes)Ed25519 secret key
signpublic64 chars (32 bytes)Ed25519 public key
authenticatedsecret64 chars (32 bytes)X25519 secret key
authenticatedpublic64 chars (32 bytes)X25519 public key
anonymoussecret64 chars (32 bytes)X25519 secret key
anonymouspublic64 chars (32 bytes)X25519 public key
symmetricsecret64 chars (32 bytes)X25519-Salsa20-Poly1305

Package Signing

Signatures provide integrity verification and signer identity. Packages can be signed during creation:

  1. Generate or supply signing keys using /key-gen?purpose=sign
  2. Include the secret signing key in the package request
  3. Specify signers in the request body
{
"packageId": "my-app",
"keys": [
{ "id": "signing-key", "type": "secret", "purpose": "sign", "keyHex": "<secret-key-hex>" }
],
"signers": ["signing-key"]
}

At runtime, the bootstrap must include the matching public key:

{
"deploymentId": "my-deployment",
"remoteConfiguration": "/path/to/my-app.oapp",
"packaging": { "requiredSigners": ["signing-key"] },
"keys": [
{ "id": "signing-key", "type": "public", "purpose": "sign", "keyHex": "<public-key-hex>" }
]
}

Package Encryption

Opscotch supports authenticated encryption for deployment-restricted packages:

  1. Generate authenticated key pairs using /key-gen?purpose=authenticated
  2. Include the packager's secret key and recipient public keys in the package
  3. Specify the packager identity and encryption keys
{
"packageId": "restricted-app",
"keys": [
{ "id": "packager-key", "type": "secret", "purpose": "authenticated", "keyHex": "<packager-secret>" },
{ "id": "recipient-key", "type": "public", "purpose": "authenticated", "keyHex": "<recipient-public>" }
],
"packagerIdentityPrivateKey": "packager-key",
"encryptionKeys": ["recipient-key"]
}

The runtime bootstrap must include:

  • The packager's public key (to verify the package came from you)
  • The recipient's secret key (to decrypt the package)
  • packaging.packagerIdentities naming permitted packager public keys

Bootstrap Encryption

Bootstrap files can be encrypted for sensitive configuration:

POST http://<packager-host>/bootstrap-encrypt

Request: multipart form with JSON body containing encryption keys and the bootstrap file in the stream field.

Response: Base64-encoded encrypted bootstrap file.

To decrypt at runtime, set the environment variable:

OPSCOTCH_BOOTSTRAP_SECRETKEY=<senderPublicKeyHex>/<decryptPrivateKeyHex>

String Encryption

Sensitive strings (like authenticated host credentials) can be encrypted for embedding in configuration:

POST http://<packager-host>/string-encrypt

Request: multipart form with encryption keys and plaintext.

Response: Encoded string (proprietary format).

To decrypt at runtime:

OPSCOTCH_STRING_SECRETKEYS=<senderPublicKeyHex>/<decryptPrivateKeyHex>,...

Cryptography in Bootstrap

Bootstrap configuration can preload keys for runtime use. This enables BYOK workflows where keys are generated externally or by the packager, then configured in bootstrap.

See the Licensing documentation for how cryptographic keys interact with license management.

Bootstrap Keys Structure

{
"keys": [
{
"id": "my-signing-key",
"purpose": "sign",
"type": "secret",
"keyHex": "a1b2c3...",
"metadata": {
"description": "Key for signing outbound messages"
}
}
]
}
FieldRequiredDescription
idyesUnique identifier for referencing the key
purposeyesOne of: sign, authenticated, anonymous, symmetric
typeyesOne of: public, secret
keyHexyesHex-encoded key material
metadatanoOptional object with description

Key Constraints

The bootstrap schema enforces key size constraints:

  • sign + secret: 128 hex characters (Ed25519 secret key)
  • sign + public: 64 hex characters (Ed25519 public key)
  • authenticated: 64 hex characters (X25519 key)
  • anonymous: 64 hex characters (X25519 key)
  • symmetric: 64 hex characters (256-bit symmetric key)

JavaScript CryptoContext

Workflow processors access cryptographic functions through context.crypto(). This provides a runtime API for signing, encryption, hashing, and random generation.

See the API Reference for the complete method documentation, and ByteContext for buffer handling.

Accessing CryptoContext

const crypto = context.crypto();

Buffer Handle Model

CryptoContext operates on buffer handles rather than raw byte arrays. The agent owns memory and exposes handles to workflows. Convert between representations using ByteContext:

const bytes = context.bytes();

// Create buffer from string
const payload = bytes.createFromString("Hello, world!");

// Convert to hex for logging or external use
const hex = bytes.binaryToHex(payload);

// Convert back from hex
const restored = bytes.createFromHex(hex);

Key Registration

Keys defined in bootstrap are available by ID. Keys can also be registered at runtime:

const crypto = context.crypto();
const bytes = context.bytes();

// Register an existing key
crypto.registerKey("sign", "secret", "a1b2c3d4...");

// Validate before use
const isValid = crypto.validateKey("sign", "secret", "a1b2c3d4...");
if (!isValid) {
context.addSystemError("Invalid key");
}

Available Operations

OperationPurposeDescription
sign(payload, secretKeyId)signSign a payload with a secret key
verifySignature(signature, payload, publicKeyId)signVerify a signature
encryptPublicKey(payload, recipientPublicId, senderSecretId)authenticatedAuthenticated encryption
decryptPublicKey(sealed, senderPublicId, recipientSecretId)authenticatedAuthenticated decryption
encryptAnonymous(payload, recipientPublicId)anonymousAnonymous encryption (sender hidden)
decryptAnonymous(sealed, recipientPublicId, recipientSecretId)anonymousAnonymous decryption
encryptSymmetric(payload, secretKeyId, nonce)symmetricSymmetric encryption
decryptSymmetric(sealed, secretKeyId, nonce)symmetricSymmetric decryption
hash(payload)-Compute SHA-256 hash
randomBytes(length)-Generate random bytes

Example: Signing

const crypto = context.crypto();
const bytes = context.bytes();

// Sign a message
const message = bytes.createFromString("Important data");
const signature = crypto.sign(message, "my-signing-key");

// Get the signature as hex for external use
const signatureHex = bytes.binaryToHex(signature);

// Verify
const isValid = crypto.verifySignature(signature, message, "my-verification-key");

Example: Authenticated Encryption

const crypto = context.crypto();
const bytes = context.bytes();

// Encrypt with authenticated sender and recipient
const plaintext = bytes.createFromString("Secret message");
const nonce = crypto.randomBytes(24);
const sealed = crypto.encryptPublicKey(plaintext, "recipient-public", "sender-secret");

// Decrypt - validates sender identity
const decrypted = crypto.decryptPublicKey(sealed, "sender-public", "recipient-secret");

Example: Symmetric Encryption

const crypto = context.crypto();

// Register symmetric key
crypto.registerKey("symmetric", "secret", "0123456789abcdef...");

const nonce = crypto.randomBytes(24);
const sealed = crypto.encryptSymmetric(plaintext, "my-symmetric-key", nonce);
const restored = crypto.decryptSymmetric(sealed, "my-symmetric-key", nonce);