nikivdev/code · error
failed to unseal message
Error message
failed to unseal message
What it means
unseal decrypts a sealed message using XSalsa20Poly1305 with a shared secret derived from the recipient's private key and the claimed sender public key. The AEAD authenticate-then-decrypt step failed, which in practice means the ciphertext was not produced by the matching sender key + recipient key + nonce material — i.e. wrong key, wrong sender, or tampered/truncated ciphertext.
Source
Thrown at src/sealer_crypto.rs:75
Ok(ciphertext)
}
pub fn unseal(
sealed_message: &[u8],
recipient_secret: &str,
sender_id: &str,
nonce_material: &[u8],
) -> Result<Vec<u8>> {
let recipient_secret = decode_secret(recipient_secret)?;
let sender_public = decode_id(sender_id)?;
let recipient_key = StaticSecret::from(recipient_secret);
let sender_key = PublicKey::from(sender_public);
let shared_secret = recipient_key.diffie_hellman(&sender_key).to_bytes();
let nonce = derive_nonce(nonce_material);
let cipher = XSalsa20Poly1305::new(&shared_secret.into());
let plaintext = cipher
.decrypt(&nonce.into(), sealed_message)
.map_err(|_| anyhow::anyhow!("failed to unseal message"))?;
Ok(plaintext)
}
fn decode_secret(value: &str) -> Result<[u8; 32]> {
let encoded = value
.strip_prefix(SECRET_PREFIX)
.ok_or_else(|| anyhow::anyhow!("invalid sealer secret prefix"))?;
let bytes = bs58::decode(encoded)
.into_vec()
.map_err(|e| anyhow::anyhow!("invalid base58 secret: {e}"))?;
bytes
.as_slice()
.try_into()
.map_err(|_| anyhow::anyhow!("invalid secret key length"))
}
fn decode_id(value: &str) -> Result<[u8; 32]> {
let encoded = valueView on GitHub (pinned to a747e741ae)
Solutions
- Verify you are using the recipient secret matching the public key the value was sealed to; re-seal for the current sealer id if identities were rotated.
- Confirm the sender id embedded alongside the ciphertext matches the secret-holder who sealed it.
- Re-obtain or re-seal the original value — the ciphertext cannot be repaired.
- Check nonce_material is identical to what seal used (same project/env context).
Example fix
// before: value sealed for old sealer id, unseal with new id fails let v = unseal(&blob, "sealerSecret_zNewKey...", &old_sender_id, nonce)?; // after: re-seal the value for the current sealer id, then decrypt let sealed = seal_project_env_value(...)?; // with current ids let v = decrypt_project_env_value(...)?;
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check: recipient secret and sender id must decode before attempting decryption
fn keys_decode(secret: &str, id: &str) -> bool {
decode_secret_is_ok(secret) && decode_id_is_ok(id)
} Type guard
fn looks_like_sealer_id(s: &str) -> bool {
s.starts_with("sealer_z")
}
fn looks_like_sealer_secret(s: &str) -> bool {
s.starts_with("sealerSecret_z")
} Try / catch
match unseal(sealed_message, recipient_secret, sender_id, nonce_material) {
Err(e) if e.to_string().contains("failed to unseal message") => {
eprintln!(
"decryption failed: wrong recipient secret, wrong sender id, or corrupted ciphertext \
(sealed-for vs current sealer id mismatch?)"
);
Err(anyhow::anyhow!("sealed value cannot be decrypted with current identity; re-seal required"))
}
other => other,
} Prevention
- Re-seal all stored values whenever sealer identities are rotated.
- Record which sealer id sealed each value alongside the ciphertext.
- Verify your sealer id (get_sealer_id on your secret) matches the intended recipient before decrypting.
- Treat any unseal failure as data integrity issue: re-fetch the value rather than retrying.
When it happens
Trigger: Calling unseal (via decrypt_project_env_value or unseal_private_key) when: the ciphertext was sealed for a different recipient, sealed by a different sender than decode_id resolved, the sealed bytes are truncated/corrupted, or nonce_material differs from what was used at seal time.
Common situations: Rotating sealer identities without re-sealing stored values, pulling env values sealed by a teammate with a different sealer ID, merging files where the sealed blob and the sender id got out of sync, or a partially written/corrupted file.
Related errors
- failed to seal message
- device auth start failed: HTTP {}
- device auth poll failed: HTTP {}
- device code expired. Run `f auth` again.
- MAPLE_API_TOKEN is not configured
AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01).
Data as JSON: /api/errors/436e7cf3e2cbd5f7.
Report an issue: GitHub.