{"record":{"id":"13adea282a41d01d","repo":"nikivdev/code","slug":"invalid-secret-key-length","errorCode":null,"errorMessage":"invalid secret key length","messagePattern":"invalid secret key length","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/sealer_crypto.rs","lineNumber":89,"sourceCode":"    let nonce = derive_nonce(nonce_material);\n    let cipher = XSalsa20Poly1305::new(&shared_secret.into());\n    let plaintext = cipher\n        .decrypt(&nonce.into(), sealed_message)\n        .map_err(|_| anyhow::anyhow!(\"failed to unseal message\"))?;\n    Ok(plaintext)\n}\n\nfn decode_secret(value: &str) -> Result<[u8; 32]> {\n    let encoded = value\n        .strip_prefix(SECRET_PREFIX)\n        .ok_or_else(|| anyhow::anyhow!(\"invalid sealer secret prefix\"))?;\n    let bytes = bs58::decode(encoded)\n        .into_vec()\n        .map_err(|e| anyhow::anyhow!(\"invalid base58 secret: {e}\"))?;\n    bytes\n        .as_slice()\n        .try_into()\n        .map_err(|_| anyhow::anyhow!(\"invalid secret key length\"))\n}\n\nfn decode_id(value: &str) -> Result<[u8; 32]> {\n    let encoded = value\n        .strip_prefix(ID_PREFIX)\n        .ok_or_else(|| anyhow::anyhow!(\"invalid sealer id prefix\"))?;\n    let bytes = bs58::decode(encoded)\n        .into_vec()\n        .map_err(|e| anyhow::anyhow!(\"invalid base58 id: {e}\"))?;\n    bytes\n        .as_slice()\n        .try_into()\n        .map_err(|_| anyhow::anyhow!(\"invalid public key length\"))\n}\n\nfn derive_nonce(nonce_material: &[u8]) -> [u8; 24] {\n    let hash = blake3::hash(nonce_material);\n    let mut nonce = [0u8; 24];","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/sealer_crypto.rs#L71-L107","documentation":"decode_secret requires the base58-decoded bytes to be exactly 32 bytes (x25519 scalar). If decoding succeeds but the length is wrong, this error is thrown. The string looks like a valid secret but encodes the wrong amount of key material.","triggerScenarios":"Calling seal or unseal with a prefixed secret whose decoded length is not 32 bytes — truncated keys, keys from other schemes, or accidental concatenation.","commonSituations":"Truncated paste into config, secrets generated by incompatible tooling, or editing the secret by hand.","solutions":["Provide the complete 32-byte secret; re-copy the full value.","Regenerate via create_sealer_identity / new_x25519_private_key and re-seal any values.","Re-encode exactly 32 bytes as \"sealerSecret_z\" + base58 if migrating.","Pre-validate: decode and assert 32-byte length before calling seal/unseal."],"exampleFix":"// before: decodes to 24 bytes\nlet k = decode_secret(\"sealerSecret_zTooShort\")?;\n// after: full 32-byte key\nlet k = decode_secret(\"sealerSecret_zFull32ByteBase58Body...\")?;","handlingStrategy":"validation","validationCode":"fn decode_secret_len_ok(s: &str) -> bool {\n    s.strip_prefix(\"sealerSecret_z\")\n        .and_then(|b| bs58::decode(b).into_vec().ok())\n        .map(|v| v.len() == 32)\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match unseal(blob, recipient_secret, sender_id, nonce) {\n    Err(e) if e.to_string().contains(\"invalid secret key length\") => {\n        eprintln!(\"recipient secret is not a 32-byte x25519 key; regenerate identity\");\n        return Err(e);\n    }\n    other => other?,\n}","preventionTips":["Generate secrets exclusively with new_x25519_private_key (32 bytes).","Check decoded length once at config load and fail fast.","Never concatenate or pad key strings by hand.","When importing keys, verify byte length before re-encoding with the prefix."],"tags":["crypto","x25519","key-length"],"backgroundTag":"invalid-key-length","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}