Conformance vectors
The CredenShare application and the four SDKs share no code. That is deliberate: a package the production application depended on would be a supply-chain surface, and for a product whose claim is that we cannot read your data, it is the wrong surface to open.
The cost of that decision is drift between five independent implementations. Drift here does not produce a test failure — it produces content that can never be decrypted. The mitigation is a specification plus a fixture that every implementation must reproduce byte for byte.
What ships
Every client embeds the same fixture inside the installed artifact, so you can check the exact build you installed rather than a copy in a repository you are trusting to match.
| Client | Path | Mechanism |
|---|---|---|
| Node | conformance-vectors.json | imported with with { type: 'json' }, so it works where there is no filesystem |
| Python | vectors.v1.json | package data |
| Go | vectors.v1.json | //go:embed, so it travels inside the compiled binary |
| Rust | src/vectors.v1.json | include_str!, re-exported as conformance::VECTORS_JSON |
conformance-vectors.json; the other three use vectors.v1.json. The bytes are identical — 7,849 bytes, SHA-256 91e70661be51edbc4522d202c533292d1eac92691d1fbb02e9eaa13eb23a582c in all four. Only the filename differs, which matters if you are hashing the path in CI.Each repository pins that digest in its own test suite, so editing the fixture in one client fails that client's build rather than silently redefining the standard.
Running it
npx --package @credenshare/sdk credenshare-conformance
node dist/conformance-cli.js # from a built clone
python -m credenshare.conformance
go run github.com/CredenShare/credenshare-sdk-go/cmd/credenshare-conformance@latest
cargo run --bin credenshare-conformance # from a clone only
All four take -v for one line per check — under cargo write -- -v, since a bare -v is consumed by cargo itself. None needs a test runner, and all exit non-zero on failure, so they work as a deployment gate. Run one in the environment that will actually do the encrypting — a client that fails these produces content nothing else can read, and the failure is otherwise invisible until somebody opens a link.
Cargo does not expose a dependency's binary targets, so Rust projects that merely depend on the crate should call credenshare::conformance::run from their own test suite instead. All four expose the checks programmatically for that purpose — Node from its ./conformance subpath export, Python as credenshare.conformance, Go as ConformanceChecks(), and Rust as conformance::run.
The 24 checks
hkdf/empty salt, short info
hkdf/16-byte salt
hkdf/output longer than one SHA-256 block, exercising the counter
fragment/encode
fragment/decode
fragment/rejects/0/missing-key
fragment/rejects/1/malformed-key
fragment/rejects/2/malformed-key
access_token
passcode_verifier/0
passcode_verifier/1
passcode_verifier/2
content/no passcode/encrypt
content/no passcode/decrypt
content/passcode mixed into info, not into the salt/encrypt
content/passcode mixed into info, not into the salt/decrypt
content/characters a JSON serialiser may mangle: HTML escapes, quotes, unicode/encrypt
content/characters a JSON serialiser may mangle: HTML escapes, quotes, unicode/decrypt
seed_keypair/recipient seed
seed_keypair/ephemeral seed
custody_keypair
ecdh_wrap/wrap
ecdh_wrap/unwrap
ecdh_wrap/roundtrip
Five of those — fragment/decode, the three content/*/decrypt cases and ecdh_wrap/unwrap — read material the fixture generator produced, rather than checking a client against itself. Those are the ones that actually demonstrate interoperability: passing them means this client can read what another one wrote. The remaining nineteen catch drift earlier and more precisely, which is why both directions are there.
Three cases are worth singling out. The first two were written into the original fixture as pins on the parts of the spec easiest to get wrong; the third was added later, and caught a live bug immediately:
hkdf/empty salt, short info— an empty salt is a zero-length byte string, not a block of zero bytes. An implementation that pads an absent salt produces different output. Pinned preemptively.content/passcode mixed into info, not into the salt— the passcode belongs in the HKDFinfo, never in the salt. Also pinned preemptively.characters a JSON serialiser may mangle— HTML-escapable characters, quotes and non-ASCII text. The fixture was all ASCII until this case was added, and it immediately caught a real divergence: one client was escaping every non-ASCII character on the wire.
What it does not cover
Worth stating plainly, because a green run is easy to over-read:
- No webhook signatures. Signature verification is tested in each repository's own suite, not by this fixture.
- No HTTP surface — no endpoints, status codes, error codes, pagination or idempotency behaviour.
- No field-member preservation. The fixture's field arrays carry only
key,valueandtype, so nothing here would notice a client that dropped a member a newer sender had added. Go and Rust used to drop them and all 24 checks still passed; both preserve them now, and the gap in the fixture is unchanged.
A passing run means the cryptography and the wire encodings are right. It does not mean the client is complete.
The generator
The fixture is produced by a script that implements the specification from scratch against node:crypto and imports nothing from the application. That independence is the point: a generator built on the app's own crypto would merely restate whatever the app does, and an app bug would become the standard every SDK is held to.
Vectors are data. They are vendored and compared, never imported — data crosses the boundary between public and private code, and code does not.