nautechsystems/nautilus_trader · error
Unsupported signed transaction payload envelope version {}
Error message
Unsupported signed transaction payload envelope version {} What it means
The first byte of a sealed envelope is a version tag (ENVELOPE_VERSION). If it does not match the version this binary supports, the envelope format is unknown and cannot be safely parsed, so parse_envelope fails. This protects against misinterpreting bytes produced by a different envelope layout.
Source
Thrown at crates/adapters/blockchain/src/execution/sealing.rs:412
struct ParsedEnvelope<'a> {
key_id: [u8; KEY_ID_LEN],
nonce: &'a [u8],
ciphertext_and_tag: &'a [u8],
}
fn parse_envelope(envelope: &[u8]) -> anyhow::Result<ParsedEnvelope<'_>> {
anyhow::ensure!(
envelope.len() <= MAX_SEALED_TRANSACTION_BYTES,
"Sealed transaction payload is {} bytes, exceeding the {} byte limit",
envelope.len(),
MAX_SEALED_TRANSACTION_BYTES
);
anyhow::ensure!(
envelope.len() >= ENVELOPE_HEADER_LEN + TAG_LEN,
"Sealed transaction payload is truncated"
);
anyhow::ensure!(
envelope[0] == ENVELOPE_VERSION,
"Unsupported signed transaction payload envelope version {}",
envelope[0]
);
let key_id = envelope[1..1 + KEY_ID_LEN]
.try_into()
.expect("fixed key ID slice length");
let nonce_start = 1 + KEY_ID_LEN;
let ciphertext_start = nonce_start + NONCE_LEN;
Ok(ParsedEnvelope {
key_id,
nonce: &envelope[nonce_start..ciphertext_start],
ciphertext_and_tag: &envelope[ciphertext_start..],
})
}
fn validate_context(context: &PayloadContext, deployment_id: &str) -> anyhow::Result<()> {View on GitHub (pinned to 18893faf8b)
Solutions
- Print envelope[0] and compare with the ENVELOPE_VERSION constant in sealing.rs to identify what produced the bytes
- Upgrade the reading service to a version supporting the envelope version found in the payload
- Re-seal old payloads with the current version during a migration
- Verify you are slicing from offset 0 — an off-by-one start shifts the version byte
Example fix
// before let sealed = &blob[1..]; // accidental offset unseal(sealed, ...)?; // reads wrong version byte // after let sealed = &blob[..]; unseal(sealed, ...)?;
Defensive patterns
Strategy: validation
Validate before calling
fn check_envelope_version(envelope: &[u8]) -> anyhow::Result<()> {
anyhow::ensure!(!envelope.is_empty(), "empty envelope");
anyhow::ensure!(
envelope[0] == ENVELOPE_VERSION,
"envelope version {} not supported (need {})",
envelope[0],
ENVELOPE_VERSION
);
Ok(())
} Type guard
fn has_supported_version(envelope: &[u8]) -> bool {
!envelope.is_empty() && envelope[0] == ENVELOPE_VERSION
} Try / catch
match unseal(&blob, deployment_id) {
Ok(tx) => tx,
Err(e) if e.to_string().contains("envelope version") => {
// route to migration/re-seal path; do not attempt re-parse
}
Err(e) => return Err(e),
} Prevention
- Check the version byte before unsealing and branch on it explicitly
- Migrate old-version payloads during deployments, not lazily at read time
- Never pass raw ciphertext; always persist the full versioned envelope
When it happens
Trigger: Calling unseal() or envelope_key_id() on bytes whose first byte != ENVELOPE_VERSION — e.g. decrypting data written by a newer/older deployment with a different envelope version, or passing ciphertext that was never enveloped (random/plaintext bytes).
Common situations: Rolling deployment where a newer service re-sealed payloads with version N+1 and an older consumer reads them; passing raw AES-GCM ciphertext without the version header; byte offset bug causing the slice to start one byte late.
Related errors
- Sealed transaction payload is {} bytes, exceeding the {} byt
- Sealed transaction payload is truncated
- Payload deployment ID does not match the configured key set
- Persisted transaction signature is not EIP-2 normalized
- Failed to load active execution intent: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/6cb72981402bb284.
Report an issue: GitHub.