distribute/crypto/behaviour
Crypto provider behaviour contract for secure communications.
This module defines the contract that crypto providers must implement to provide encryption, key exchange, and secure context management in the distribute library.
Design Philosophy
This behaviour follows distribute’s patterns:
- Pure function signatures (like
transport/adapter) - Actor-based implementations for state management
- Integration with
handshakefor key exchange - Opaque handles for secure context (no key leakage)
Implementation Approaches
Providers can be implemented as:
- No-op provider: Identity transformation for development (insecure!)
- AEAD provider: AES-GCM or ChaCha20-Poly1305
- ECDH provider: Elliptic curve key exchange + symmetric encryption
State Machine
Key exchange follows a state machine:
Plain -> KeyExchangeInProgress -> SecureEstablished
|
v
Rekeying
|
v
SecureEstablished
Failed states can occur at any transition.
Security Considerations
- Never log key material
- Use opaque handles for keys and contexts
- Zero sensitive memory on shutdown when possible
- Mark development providers as insecure
Example Implementation
See distribute/crypto/noop_adapter for a reference implementation.
Types
Errors from crypto operations.
Classification
- Transient (retryable):
TransientNetwork,Timeout - Permanent (do not retry):
InvalidSignature,KeyMismatch,DecryptionFailed
pub type CryptoError {
InitFailed(reason: String)
ShutdownFailed(reason: String)
TransientNetwork(reason: String)
InvalidSignature
KeyMismatch
DecryptionFailed(reason: String)
EncryptionFailed(reason: String)
NoSecureContext(node: String)
HandshakeFailed(reason: String)
RekeyFailed(reason: String)
ProviderFailure(reason: String)
Timeout(elapsed_ms: Int)
}
Constructors
-
InitFailed(reason: String)Provider initialization failed
-
ShutdownFailed(reason: String)Provider shutdown failed
-
TransientNetwork(reason: String)Transient network error during key exchange
-
InvalidSignatureInvalid cryptographic signature
-
KeyMismatchKey mismatch during verification
-
DecryptionFailed(reason: String)Decryption failed (invalid ciphertext or wrong key)
-
EncryptionFailed(reason: String)Encryption failed
-
NoSecureContext(node: String)No secure context for node
-
HandshakeFailed(reason: String)Handshake failed
-
RekeyFailed(reason: String)Rekey operation failed
-
ProviderFailure(reason: String)Internal provider error
-
Timeout(elapsed_ms: Int)Operation timed out
Crypto provider metrics.
pub type CryptoMetrics {
CryptoMetrics(
handshakes_initiated: Int,
handshakes_completed: Int,
handshakes_failed: Int,
encrypt_count: Int,
decrypt_count: Int,
rekey_count: Int,
active_contexts: Int,
)
}
Constructors
-
CryptoMetrics( handshakes_initiated: Int, handshakes_completed: Int, handshakes_failed: Int, encrypt_count: Int, decrypt_count: Int, rekey_count: Int, active_contexts: Int, )Arguments
- handshakes_initiated
-
Total handshakes initiated
- handshakes_completed
-
Total handshakes completed successfully
- handshakes_failed
-
Total handshakes failed
- encrypt_count
-
Total encrypt operations
- decrypt_count
-
Total decrypt operations
- rekey_count
-
Total rekey operations
- active_contexts
-
Active secure contexts
Messages exchanged during handshake.
The format is provider-specific but typically includes:
- Public keys or key shares
- Nonces and session identifiers
- Signatures for authentication
pub type HandshakeMessage {
HandshakeMessage(
message_type: String,
payload: BitArray,
metadata: option.Option(dict.Dict(String, String)),
)
}
Constructors
-
HandshakeMessage( message_type: String, payload: BitArray, metadata: option.Option(dict.Dict(String, String)), )Arguments
- message_type
-
Message type identifier
- payload
-
Binary payload
- metadata
-
Optional metadata
Result of a handshake step.
pub type HandshakeResult {
Continue(
state: HandshakeState,
response: option.Option(HandshakeMessage),
)
Established(context: SecureContext)
HandshakeError(error: CryptoError)
}
Constructors
-
Continue( state: HandshakeState, response: option.Option(HandshakeMessage), )Handshake still in progress, continue with next message
-
Established(context: SecureContext)Handshake completed, secure context established
-
HandshakeError(error: CryptoError)Handshake failed
Handshake state machine stages.
Represents the current state of key exchange with a remote node.
pub type HandshakeStage {
Plain
KeyExchangeInProgress
SecureEstablished
Rekeying
Failed(reason: String)
}
Constructors
-
PlainNo secure context established yet
-
KeyExchangeInProgressKey exchange is in progress
-
SecureEstablishedSecure context successfully established
-
RekeyingRekeying in progress (rotating keys)
-
Failed(reason: String)Handshake failed
Handshake state during key exchange.
This opaque type holds the intermediate state during handshake and is passed between handshake_start and handshake_continue calls.
pub opaque type HandshakeState
Health status of a crypto provider.
pub type HealthStatus {
Up
Degraded(reason: String)
Down(reason: String)
}
Constructors
-
UpProvider is fully operational
-
Degraded(reason: String)Provider is operational but degraded
-
Down(reason: String)Provider is down
Options for initializing a crypto provider.
pub type ProviderOptions {
ProviderOptions(
name: String,
is_development: Bool,
key_rotation_interval_ms: Int,
handshake_timeout_ms: Int,
custom: dict.Dict(String, String),
)
}
Constructors
-
ProviderOptions( name: String, is_development: Bool, key_rotation_interval_ms: Int, handshake_timeout_ms: Int, custom: dict.Dict(String, String), )Arguments
- name
-
Registered name for the provider process
- is_development
-
Whether this is a development/insecure provider
- key_rotation_interval_ms
-
Key rotation interval in milliseconds (0 = no auto-rotation)
- handshake_timeout_ms
-
Handshake timeout in milliseconds
- custom
-
Provider-specific custom options
Opaque secure context for a node connection.
Contains the cryptographic material needed for encrypt/decrypt operations. The internal structure is provider-specific and should never be logged or serialized.
pub opaque type SecureContext
Values
pub fn context_key_id(ctx: SecureContext) -> String
Get the key ID from a secure context.
Returns a unique identifier for the current encryption key. Safe to log for debugging; changes after each rekey operation.
pub fn context_node_id(ctx: SecureContext) -> String
Get the node ID from a secure context.
Returns the identifier of the remote node this secure context was established with during handshake.
pub fn context_stage(ctx: SecureContext) -> HandshakeStage
Get the handshake stage from a secure context.
Returns the current state of the security handshake. Should be
SecureEstablished before using the context for encryption.
pub fn default_options(name: String) -> ProviderOptions
Default provider options.
pub fn handshake_remote_node(state: HandshakeState) -> String
Get the remote node from handshake state.
Returns the identifier of the remote node this handshake is being conducted with.
pub fn handshake_stage(state: HandshakeState) -> HandshakeStage
Get the stage from handshake state.
Returns the current stage of the handshake process. Use to determine if the handshake needs more message exchanges or has completed.
pub fn is_permanent_error(error: CryptoError) -> Bool
Check if an error is permanent and should not be retried.
Returns True for errors that indicate a fundamental problem
that won’t be resolved by retrying (e.g., authentication failure).
pub fn is_transient_error(error: CryptoError) -> Bool
Check if an error is transient and should be retried.
Returns True for errors that are temporary and may succeed
on retry (e.g., network issues, timeouts).
pub fn new_handshake_state(
local_node: String,
remote_node: String,
stage: HandshakeStage,
) -> HandshakeState
Create a new handshake state (for provider implementations).
Constructs intermediate state for tracking an in-progress handshake. Used internally by providers to maintain state between handshake steps.
Arguments
local_node- Local node identifierremote_node- Remote node identifierstage- Current handshake stage
pub fn new_secure_context(
node_id: String,
stage: HandshakeStage,
created_at_ms: Int,
key_id: String,
) -> SecureContext
Create a new secure context (for provider implementations).
Constructs a SecureContext for storing encrypted session state.
This is a simpler version that doesn’t include key material - use
types.new_secure_context for full context with key material.
Arguments
node_id- Remote node this context is forstage- Current handshake stagecreated_at_ms- Creation timestampkey_id- Unique key identifier (safe to log)
Note
This is primarily for the behaviour module’s internal use.
Most providers should use types.new_secure_context instead.