bitwarden/create-testvectors
Record a serialized/encrypted format as a permanent test vector. Use when adding a type whose serialization format must stay backward-compatible (envelopes, COSE blobs, encrypted/wire formats), or when the user asks to "create a test vector", "record a serialized format", or "lock in the wire format". Generates output with a temporary ignored test, then pins it in a permanent deserialization unit test.
npx skills add https://github.com/bitwarden/sdk-internal --skill create-testvectors
Backward compatibility of serialized data is critical in this SDK: existing encrypted/serialized
data must keep deserializing across versions. A test vector locks a known-good serialized blob
into a permanent unit test that fails if the format ever silently breaks.
The workflow has two halves: a throwaway generator test that prints the blob, and a permanent test
that parses a hardcoded copy of that blob and asserts it decodes to the expected value.
Read these before writing a new one; copy whichever style fits:
crates/bitwarden-crypto/src/safe/data_envelope.rs — base64 &str constants (preferred forlarger/COSE blobs). See generate_test_vectors (the ignored generator) and
test_data_envelope_test_vector (the permanent check).
crates/bitwarden-crypto/src/safe/password_protected_key_envelope.rs — &[u8] byte-arrayconstants. See TESTVECTOR_* consts and test_testvector_cosekey / test_testvector_legacykey.
&str (preferred) when the type implements From<T> for String / FromStr (or serdeto a B64 string), e.g. DataEnvelope. Compact in source.
&[u8] byte array when the type only round-trips through raw bytes (From<&T> for Vec<u8> /TryFrom<&Vec<u8>>), e.g. PasswordProtectedKeyEnvelope.
Use a fixed, deterministic input value and a stable test namespace (e.g. ExampleNamespace)
so the permanent test is self-contained.
Add to the crate's #[cfg(test)] mod tests. Mark it #[ignore] so it never runs in CI — its only
job is to print constants you paste back into the source.
#[test]
#[ignore = "Generates test vectors; run manually"]
fn generate_test_vectors() {
// Construct with a fixed input + stable namespace.
let data: TestData = TestDataV1 { field: 123 }.into();
let (envelope, cek) =
DataEnvelope::seal_ref(&data, DataEnvelopeNamespace::ExampleNamespace).unwrap();
// Round-trip once here to confirm the generated vector is itself valid.
let unsealed: TestData = envelope
.unseal_ref(DataEnvelopeNamespace::ExampleNamespace, &cek)
.unwrap();
assert_eq!(unsealed, data);
// Print constants in the exact form you'll paste back.
println!(
"const TEST_VECTOR_CEK: &str = \"{}\";",
B64::from(SymmetricCryptoKey::XChaCha20Poly1305Key(cek).to_encoded())
);
println!("const TEST_VECTOR_ENVELOPE: &str = \"{}\";", String::from(envelope));
}
For the byte-array style, print with println!("{:?}", some_bytes) and paste the array.
cargo test -p <crate> <module>::tests::generate_test_vectors -- --ignored --nocapture
Copy the printed const lines verbatim.
Paste the captured constants as module-level consts, then write a permanent (non-ignored) test
that reconstructs from the constant and asserts the decoded value equals the known input. This is
what guards against format breakage.
const TEST_VECTOR_CEK: &str = "pQEEAlB5RTKA0xXdA7C4iQE4QfVU...";
const TEST_VECTOR_ENVELOPE: &str = "g1hLpQE6AAERbwN4I2FwcGxpY2F0...";
#[test]
fn test_data_envelope_test_vector() {
let cek = SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_CEK).unwrap()).unwrap();
let SymmetricCryptoKey::XChaCha20Poly1305Key(ref cek) = cek else {
panic!("Invalid CEK type");
};
let envelope: DataEnvelope = TEST_VECTOR_ENVELOPE.parse().unwrap();
let unsealed: TestData = envelope
.unseal_ref(DataEnvelopeNamespace::ExampleNamespace, cek)
.unwrap();
assert_eq!(unsealed, TestDataV1 { field: 123 }.into());
}
#[ignore] generator test (the reference files keep theirs, which is alsoacceptable — keep it only if it's genuinely useful to regenerate later).
cargo test -p <crate> <module>::tests::test_..._test_vector
npm run lint
decoding means a real backward-compatibility break — fix the code, not the vector.
Take bitwarden/create-testvectors from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.