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

  1. Print envelope[0] and compare with the ENVELOPE_VERSION constant in sealing.rs to identify what produced the bytes
  2. Upgrade the reading service to a version supporting the envelope version found in the payload
  3. Re-seal old payloads with the current version during a migration
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/6cb72981402bb284. Report an issue: GitHub.