feat: engram-lang — new programming language, quantum-sealed prod target, spreading activation types

This commit is contained in:
Will Anderson
2026-04-27 18:46:51 -05:00
commit 9ced941590
5569 changed files with 8153 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
//! Sealed artifact format definition.
//!
//! Binary layout (big-endian):
//!
//! ```text
//! Offset Size Field
//! ────── ───── ─────────────────────────────────────────────────────────
//! 0 8 magic: b"ENGRAM01"
//! 8 2 version: u16 (currently 1)
//! 10 * JSON-encoded SealedArtifact body (algorithm_id, nonce, …)
//! ```
//!
//! The body is JSON so the format is self-describing and forward-compatible.
//! Future versions can add new fields without breaking older parsers.
use serde::{Deserialize, Serialize};
/// Magic header bytes — identify an Engram sealed artifact.
pub const MAGIC: [u8; 8] = *b"ENGRAM01";
/// Current artifact format version.
pub const FORMAT_VERSION: u16 = 1;
// ── Configuration ─────────────────────────────────────────────────────────────
/// Which algorithm was used to seal the artifact.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SealAlgorithm {
/// AES-256-GCM — current default, quantum-resistant at 256-bit.
Aes256Gcm,
/// CRYSTALS-Kyber 768 — when ml-kem crate stabilizes.
MlKem768,
/// CRYSTALS-Kyber 1024 — when ml-kem crate stabilizes.
MlKem1024,
}
impl SealAlgorithm {
pub fn id(&self) -> &'static str {
match self {
SealAlgorithm::Aes256Gcm => "aes256gcm-v1",
SealAlgorithm::MlKem768 => "mlkem768-v1",
SealAlgorithm::MlKem1024 => "mlkem1024-v1",
}
}
}
/// How the deployment key is derived / bound.
#[derive(Debug, Clone)]
pub enum DeploymentBinding {
/// Read the seal key from an environment variable (e.g. `ENGRAM_SEAL_KEY`).
EnvironmentKey(String),
/// Bind to this machine's hostname + OS + CPU model (BLAKE3 hash of all three).
MachineFingerprint,
/// No binding — key is the zero vector. For testing only; offers no security.
None,
}
/// Configuration for the sealing operation.
#[derive(Debug, Clone)]
pub struct SealConfig {
pub algorithm: SealAlgorithm,
pub deployment_binding: DeploymentBinding,
}
impl Default for SealConfig {
fn default() -> Self {
Self {
algorithm: SealAlgorithm::Aes256Gcm,
deployment_binding: DeploymentBinding::EnvironmentKey("ENGRAM_SEAL_KEY".into()),
}
}
}
// ── Artifact ──────────────────────────────────────────────────────────────────
/// A quantum-sealed bytecode artifact.
///
/// This is the output of `el seal` / the `prod` compilation target.
/// It is serialized to disk as: `MAGIC || VERSION_u16_be || JSON(body)`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SealedArtifact {
/// Algorithm identifier — matches [`SealAlgorithm::id()`].
pub algorithm_id: String,
/// BLAKE3-keyed MAC over `(algorithm_id || nonce || ciphertext)`.
/// Used to detect tampering before attempting decryption.
/// In the PQ upgrade path, this becomes an ML-DSA signature.
pub signature: Vec<u8>,
/// The binding-protected symmetric key.
///
/// In the current scheme: `symmetric_key XOR BLAKE3(binding_material)`.
/// Without the deployment key, the binding material cannot be derived,
/// so the symmetric key cannot be recovered.
///
/// In the ML-KEM upgrade: this becomes the KEM-encapsulated key ciphertext.
pub encapsulated_key: Vec<u8>,
/// 96-bit AES-GCM nonce.
pub nonce: Vec<u8>,
/// Encrypted bytecode (AES-256-GCM ciphertext including the 128-bit auth tag).
pub ciphertext: Vec<u8>,
/// BLAKE3 hash of the binding material. Allows the unsealer to verify
/// it is running in the correct deployment environment before decryption.
/// `None` if [`DeploymentBinding::None`] was used.
pub deployment_fingerprint: Option<Vec<u8>>,
}
impl SealedArtifact {
/// Serialize to the on-disk wire format: `MAGIC || version_be16 || JSON`.
pub fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
let mut out = Vec::with_capacity(256);
out.extend_from_slice(&MAGIC);
out.extend_from_slice(&FORMAT_VERSION.to_be_bytes());
let json = serde_json::to_vec(self)?;
out.extend_from_slice(&json);
Ok(out)
}
/// Deserialize from the on-disk wire format.
pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::SealError> {
if bytes.len() < 10 {
return Err(crate::SealError::Serialization("artifact too short".into()));
}
let magic: [u8; 8] = bytes[..8].try_into().unwrap();
if magic != MAGIC {
return Err(crate::SealError::InvalidMagic(magic));
}
// Version check (bytes 8..10) — currently we only support v1
let version = u16::from_be_bytes([bytes[8], bytes[9]]);
if version != FORMAT_VERSION {
return Err(crate::SealError::UnsupportedAlgorithm(format!("format v{version}")));
}
let body = &bytes[10..];
serde_json::from_slice(body).map_err(|e| crate::SealError::Serialization(e.to_string()))
}
}
+35
View File
@@ -0,0 +1,35 @@
//! Seal/unseal error types.
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SealError {
#[error("encryption failed: {0}")]
EncryptionFailed(String),
#[error("decryption failed: {0}")]
DecryptionFailed(String),
#[error("signature verification failed — artifact may be tampered")]
SignatureInvalid,
#[error("invalid magic header: expected ENGRAM01, got {0:?}")]
InvalidMagic([u8; 8]),
#[error("unsupported algorithm version: {0}")]
UnsupportedAlgorithm(String),
#[error("deployment binding mismatch — wrong key or wrong machine")]
BindingMismatch,
#[error("serialization error: {0}")]
Serialization(String),
#[error("environment variable {0} not set — cannot unseal")]
MissingEnvKey(String),
#[error("crypto engine error: {0}")]
CryptoEngine(String),
}
pub type SealResult<T> = Result<T, SealError>;
+40
View File
@@ -0,0 +1,40 @@
//! el-seal — Quantum-sealed production compilation target.
//!
//! The `prod` compilation target encrypts Engram bytecode into a
//! `SealedArtifact` that cannot be decompiled without the deployment key.
//!
//! # Sealing Process
//!
//! 1. Generate a random 256-bit symmetric key.
//! 2. Encrypt the bytecode with AES-256-GCM (authenticated encryption).
//! 3. Derive the deployment binding from the environment key via BLAKE3.
//! 4. "Encapsulate" the symmetric key: XOR it with the BLAKE3 hash of the
//! binding material, so the symmetric key can only be recovered if you
//! know the deployment secret.
//! 5. Sign `(algorithm_id || nonce || ciphertext)` with a BLAKE3 keyed MAC
//! using the symmetric key as the MAC key.
//! 6. Serialize into a `SealedArtifact` with the magic header `ENGRAM01`.
//!
//! # Why "quantum-sealed"?
//!
//! AES-256-GCM is the current NIST standard for symmetric authenticated
//! encryption. Grover's algorithm reduces the effective key space from 2^256
//! to 2^128 — still computationally infeasible for any foreseeable quantum
//! computer. The algorithm_id field reserves space for upgrading to ML-KEM
//! (CRYSTALS-Kyber) when those crates stabilize, without changing the
//! artifact format.
//!
//! # Decompilation resistance
//!
//! Without the deployment key, the `ciphertext` field is indistinguishable
//! from random bytes. Every static analysis tool, disassembler, and
//! decompiler sees garbage. The GCM auth tag additionally makes any
//! tampering detectable.
mod artifact;
mod error;
mod seal;
pub use artifact::{DeploymentBinding, SealAlgorithm, SealConfig, SealedArtifact};
pub use error::{SealError, SealResult};
pub use seal::{seal, unseal, verify};
+371
View File
@@ -0,0 +1,371 @@
//! Core seal/unseal operations.
use aes_gcm::{
aead::{Aead, AeadCore, KeyInit, OsRng},
Aes256Gcm, Key, Nonce,
};
use rand::RngCore;
use crate::artifact::{DeploymentBinding, SealAlgorithm, SealConfig, SealedArtifact};
use crate::error::{SealError, SealResult};
// ── Public API ────────────────────────────────────────────────────────────────
/// Seal `bytecode` into a [`SealedArtifact`] using the given [`SealConfig`].
///
/// # Sealing steps
///
/// 1. Resolve the deployment binding material.
/// 2. Generate a random 256-bit symmetric key.
/// 3. Encrypt bytecode with AES-256-GCM.
/// 4. XOR the symmetric key with `BLAKE3(binding_material)` to produce
/// the `encapsulated_key` field. Possession of the binding secret is
/// required to recover the symmetric key.
/// 5. MAC the header + ciphertext with the symmetric key.
/// 6. Serialize into [`SealedArtifact`].
pub fn seal(bytecode: &[u8], config: &SealConfig) -> SealResult<SealedArtifact> {
match &config.algorithm {
SealAlgorithm::Aes256Gcm => seal_aes256gcm(bytecode, config),
SealAlgorithm::MlKem768 | SealAlgorithm::MlKem1024 => {
Err(SealError::UnsupportedAlgorithm(config.algorithm.id().to_string()))
}
}
}
/// Unseal a [`SealedArtifact`], recovering the original bytecode.
///
/// The `binding_key` must match the key that was used during sealing.
/// For [`DeploymentBinding::EnvironmentKey`], this is the raw env var bytes.
/// For [`DeploymentBinding::MachineFingerprint`], this is the fingerprint bytes.
/// For [`DeploymentBinding::None`], pass `&[]`.
pub fn unseal(artifact: &SealedArtifact, binding_key: &[u8]) -> SealResult<Vec<u8>> {
match artifact.algorithm_id.as_str() {
"aes256gcm-v1" => unseal_aes256gcm(artifact, binding_key),
other => Err(SealError::UnsupportedAlgorithm(other.to_string())),
}
}
/// Verify the MAC/signature on a [`SealedArtifact`] without decrypting.
///
/// Returns `true` if the artifact is intact. This only proves the artifact
/// has not been tampered with — it does not prove the deployment key is
/// correct.
///
/// Note: verification requires the symmetric key, which requires the
/// binding material. For a lightweight integrity check, use the GCM auth
/// tag (which is verified implicitly by [`unseal`]).
pub fn verify(artifact: &SealedArtifact) -> SealResult<bool> {
// Without the binding key we can't recover the symmetric key to verify
// the MAC. What we *can* do is check structural integrity:
// - Magic and version are checked in from_bytes().
// - Nonce must be 12 bytes (AES-GCM).
// - Ciphertext must be non-empty.
let structural_ok = artifact.nonce.len() == 12
&& !artifact.ciphertext.is_empty()
&& !artifact.encapsulated_key.is_empty()
&& !artifact.signature.is_empty();
Ok(structural_ok)
}
// ── AES-256-GCM sealing ───────────────────────────────────────────────────────
fn seal_aes256gcm(bytecode: &[u8], config: &SealConfig) -> SealResult<SealedArtifact> {
let algorithm_id = SealAlgorithm::Aes256Gcm.id().to_string();
// 1. Resolve the binding material
let (binding_material, fingerprint) = resolve_binding(&config.deployment_binding)?;
// 2. Generate a random 256-bit symmetric key
let mut sym_key = [0u8; 32];
OsRng.fill_bytes(&mut sym_key);
// 3. Encrypt bytecode
let aes_key = Key::<Aes256Gcm>::from_slice(&sym_key);
let cipher = Aes256Gcm::new(aes_key);
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ciphertext = cipher
.encrypt(&nonce, bytecode)
.map_err(|e| SealError::EncryptionFailed(e.to_string()))?;
// 4. Encapsulate the symmetric key: XOR with BLAKE3(binding_material)
let binding_hash = blake3_32(&binding_material);
let encapsulated_key: Vec<u8> = sym_key.iter().zip(binding_hash.iter()).map(|(a, b)| a ^ b).collect();
// 5. MAC: BLAKE3 keyed over (algorithm_id || nonce || ciphertext)
let signature = compute_mac(&sym_key, &algorithm_id, nonce.as_slice(), &ciphertext);
Ok(SealedArtifact {
algorithm_id,
signature,
encapsulated_key,
nonce: nonce.to_vec(),
ciphertext,
deployment_fingerprint: fingerprint,
})
}
fn unseal_aes256gcm(artifact: &SealedArtifact, binding_key: &[u8]) -> SealResult<Vec<u8>> {
// 1. Derive binding hash from the provided key.
// If binding_key is empty, use the zero vector (matches DeploymentBinding::None).
let effective_key = if binding_key.is_empty() {
vec![0u8; 32]
} else {
binding_key.to_vec()
};
let binding_hash = blake3_32(&effective_key);
// 1b. If a fingerprint was embedded, verify the binding key matches
if let Some(ref fp) = artifact.deployment_fingerprint {
let expected_fp = blake3_hash(&effective_key);
if expected_fp.as_slice() != fp.as_slice() {
return Err(SealError::BindingMismatch);
}
}
// 2. Recover the symmetric key: XOR encapsulated_key with binding_hash
if artifact.encapsulated_key.len() != 32 {
return Err(SealError::DecryptionFailed("encapsulated key wrong length".into()));
}
let sym_key: Vec<u8> = artifact.encapsulated_key.iter().zip(binding_hash.iter()).map(|(a, b)| a ^ b).collect();
let sym_key_arr: [u8; 32] = sym_key.try_into().unwrap();
// 3. Verify MAC before decrypting
let expected_mac = compute_mac(&sym_key_arr, &artifact.algorithm_id, &artifact.nonce, &artifact.ciphertext);
if expected_mac != artifact.signature {
return Err(SealError::SignatureInvalid);
}
// 4. Decrypt
if artifact.nonce.len() != 12 {
return Err(SealError::DecryptionFailed("invalid nonce length".into()));
}
let nonce = Nonce::from_slice(&artifact.nonce);
let aes_key = Key::<Aes256Gcm>::from_slice(&sym_key_arr);
let cipher = Aes256Gcm::new(aes_key);
let plaintext = cipher
.decrypt(nonce, artifact.ciphertext.as_slice())
.map_err(|e| SealError::DecryptionFailed(e.to_string()))?;
Ok(plaintext)
}
// ── Binding resolution ────────────────────────────────────────────────────────
/// Resolve a deployment binding to raw bytes and an optional fingerprint.
///
/// Returns `(binding_material, deployment_fingerprint)`.
/// The fingerprint is stored in the artifact; the binding material is never stored.
fn resolve_binding(binding: &DeploymentBinding) -> SealResult<(Vec<u8>, Option<Vec<u8>>)> {
match binding {
DeploymentBinding::EnvironmentKey(var_name) => {
let val = std::env::var(var_name)
.map_err(|_| SealError::MissingEnvKey(var_name.clone()))?;
let material = val.into_bytes();
let fingerprint = blake3_hash(&material);
Ok((material, Some(fingerprint)))
}
DeploymentBinding::MachineFingerprint => {
// Derive from hostname + OS
let hostname = get_hostname();
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let raw = format!("{hostname}::{os}::{arch}");
let material = raw.into_bytes();
let fingerprint = blake3_hash(&material);
Ok((material, Some(fingerprint)))
}
DeploymentBinding::None => {
// Zero vector — trivially recoverable, testing only
Ok((vec![0u8; 32], None))
}
}
}
fn get_hostname() -> String {
std::env::var("HOSTNAME")
.or_else(|_| std::env::var("COMPUTERNAME"))
.unwrap_or_else(|_| "unknown-host".into())
}
// ── Crypto helpers ────────────────────────────────────────────────────────────
fn blake3_32(data: &[u8]) -> [u8; 32] {
*blake3::hash(data).as_bytes()
}
fn blake3_hash(data: &[u8]) -> Vec<u8> {
blake3::hash(data).as_bytes().to_vec()
}
fn compute_mac(key: &[u8; 32], algorithm_id: &str, nonce: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let mut hasher = blake3::Hasher::new_keyed(key);
hasher.update(algorithm_id.as_bytes());
hasher.update(nonce);
hasher.update(ciphertext);
hasher.finalize().as_bytes().to_vec()
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
use crate::artifact::{DeploymentBinding, SealAlgorithm, SealConfig};
fn no_binding_config() -> SealConfig {
SealConfig {
algorithm: SealAlgorithm::Aes256Gcm,
deployment_binding: DeploymentBinding::None,
}
}
fn env_binding_config(var: &str) -> SealConfig {
SealConfig {
algorithm: SealAlgorithm::Aes256Gcm,
deployment_binding: DeploymentBinding::EnvironmentKey(var.to_string()),
}
}
#[test]
fn test_seal_unseal_roundtrip_no_binding() {
let bytecode = b"PUSH 42\nCALL print\nRETURN";
let config = no_binding_config();
let artifact = seal(bytecode, &config).unwrap();
let recovered = unseal(&artifact, &[]).unwrap();
assert_eq!(recovered, bytecode);
}
#[test]
fn test_seal_unseal_roundtrip_env_key() {
std::env::set_var("_EL_TEST_SEAL_KEY", "super-secret-deployment-key");
let bytecode = b"sealed bytecode payload";
let config = env_binding_config("_EL_TEST_SEAL_KEY");
let artifact = seal(bytecode, &config).unwrap();
let recovered = unseal(&artifact, b"super-secret-deployment-key").unwrap();
assert_eq!(recovered, bytecode);
std::env::remove_var("_EL_TEST_SEAL_KEY");
}
#[test]
fn test_wrong_binding_key_rejected() {
let bytecode = b"secret bytecode";
let config = no_binding_config();
let artifact = seal(bytecode, &config).unwrap();
// Use wrong key — MAC should fail
let mut bad_artifact = artifact.clone();
bad_artifact.encapsulated_key = vec![0xAA; 32]; // wrong key
let result = unseal(&bad_artifact, &[]);
assert!(result.is_err());
}
#[test]
fn test_tampered_ciphertext_rejected() {
let bytecode = b"important bytecode";
let config = no_binding_config();
let mut artifact = seal(bytecode, &config).unwrap();
// Flip a byte in the ciphertext
if let Some(b) = artifact.ciphertext.first_mut() {
*b ^= 0xFF;
}
let result = unseal(&artifact, &[]);
assert!(result.is_err());
}
#[test]
fn test_tampered_mac_rejected() {
let bytecode = b"important bytecode";
let config = no_binding_config();
let mut artifact = seal(bytecode, &config).unwrap();
// Flip the first byte of the MAC
if let Some(b) = artifact.signature.first_mut() {
*b ^= 0xFF;
}
let result = unseal(&artifact, &[]);
assert!(result.is_err());
}
#[test]
fn test_serialization_roundtrip() {
let bytecode = b"fn main() { return 42 }";
let config = no_binding_config();
let artifact = seal(bytecode, &config).unwrap();
let bytes = artifact.to_bytes().unwrap();
let restored = SealedArtifact::from_bytes(&bytes).unwrap();
let recovered = unseal(&restored, &[]).unwrap();
assert_eq!(recovered, bytecode);
}
#[test]
fn test_magic_header_present() {
let artifact = seal(b"test", &no_binding_config()).unwrap();
let bytes = artifact.to_bytes().unwrap();
assert_eq!(&bytes[..8], b"ENGRAM01");
}
#[test]
fn test_wrong_magic_rejected() {
let mut bytes = seal(b"test", &no_binding_config()).unwrap().to_bytes().unwrap();
bytes[0] = 0xFF; // corrupt magic
let result = SealedArtifact::from_bytes(&bytes);
assert!(result.is_err());
}
#[test]
fn test_verify_structural_ok() {
let artifact = seal(b"bytecode", &no_binding_config()).unwrap();
assert!(verify(&artifact).unwrap());
}
#[test]
fn test_empty_bytecode_sealable() {
let artifact = seal(b"", &no_binding_config()).unwrap();
let recovered = unseal(&artifact, &[]).unwrap();
assert_eq!(recovered, b"");
}
#[test]
fn test_large_bytecode_sealable() {
let bytecode = vec![0x42u8; 100_000];
let artifact = seal(&bytecode, &no_binding_config()).unwrap();
let recovered = unseal(&artifact, &[]).unwrap();
assert_eq!(recovered, bytecode);
}
#[test]
fn test_algorithm_id_stored() {
let artifact = seal(b"test", &no_binding_config()).unwrap();
assert_eq!(artifact.algorithm_id, "aes256gcm-v1");
}
#[test]
fn test_nonce_is_12_bytes() {
let artifact = seal(b"test", &no_binding_config()).unwrap();
assert_eq!(artifact.nonce.len(), 12);
}
#[test]
fn test_encapsulated_key_is_32_bytes() {
let artifact = seal(b"test", &no_binding_config()).unwrap();
assert_eq!(artifact.encapsulated_key.len(), 32);
}
#[test]
fn test_different_seals_produce_different_ciphertexts() {
let bytecode = b"same input";
let config = no_binding_config();
let a1 = seal(bytecode, &config).unwrap();
let a2 = seal(bytecode, &config).unwrap();
// Random nonce means ciphertexts differ
assert_ne!(a1.ciphertext, a2.ciphertext);
assert_ne!(a1.nonce, a2.nonce);
}
#[test]
fn test_missing_env_key_returns_error() {
std::env::remove_var("_EL_NONEXISTENT_KEY");
let result = seal(b"test", &env_binding_config("_EL_NONEXISTENT_KEY"));
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), SealError::MissingEnvKey(_)));
}
}