sd-cwt
Selective Disclosure CBOR Web Token (SD-CWT) helpers for Rust, built on
cose2 and
cbor2.
This crate implements the SD-CWT disclosure and redaction mechanics from
draft-ietf-spice-sd-cwt-08. It does not replace cose2's COSE signing and
verification APIs. Instead, it provides the SD-CWT-specific pieces you compose
with cose2::Sign1Message:
- registered SD-CWT header labels:
sd_claims,sd_alg,sd_aead_encrypted_claims,sd_aead,kcwt,CWT_Claims, andtyp; simple(59)forredacted_claim_keys, usingcbor2::Value::Simple;- tag 60 redacted array elements;
- tag 58 / tag 62 pre-issuance conversion into issued redactions and disclosures;
- Salted Disclosed Claim encoding, decoding, and SHA-256 hashing;
- Holder and Verifier restoration modes;
- AEAD encrypted disclosure wire structures and header helpers.
The crate does not generate randomness and does not implement KBT signing
policy for you. Issuers provide 16-byte salts; applications still use
cose2 to sign and verify SD-CWT and KBT messages.
Install
[]
= "0.3"
= "0.1"
When working from this repository:
[]
= { = ".." }
= { = "../sd-cwt" }
Basic flow
The example below shows the core SD-CWT flow without real cryptography:
- The Holder or client sends a pre-issued claims value with tag 58 redaction requests.
- The Issuer converts it into an issued claims value, creating
sd_claims. - The Issuer signs the redacted payload with
cose2::Sign1Message. - The Holder validates with the strict one-to-one disclosure rule.
- The Verifier receives a presentation with only selected disclosures and removes the rest.
use Value;
use ;
use ;
# ;
#
# ;
#
#
Run the full example:
API by task
| Task | API |
|---|---|
Create simple(59) redacted map key label |
redacted_claim_keys_label() |
| Create tag 60 redacted array element | redacted_element(hash) |
| Convert tag 58/62 pre-issuance claims into issued SD-CWT claims | issue_from_preissuance(...) |
| Provide issuance salts | Implement SaltGenerator, or pass a FnMut() -> [u8; 16] |
| Build a Salted Disclosed Claim manually | Disclosure::claim, Disclosure::element, Disclosure::decoy |
Read or write sd_claims |
disclosures_from_unprotected, set_disclosures, DisclosureSet::from_unprotected |
Read or write sd_alg |
sd_alg, set_sd_alg, default_hasher_for_sd_alg |
| Restore as Holder | restore_for_holder or restore_payload_from_message(..., RestoreMode::Holder) |
| Restore as Verifier | restore_for_verifier or restore_payload_from_message(..., RestoreMode::Verifier) |
| Handle AEAD encrypted disclosure metadata | AeadEncryptedDisclosure, set_aead_encrypted_disclosures, aead_encrypted_disclosures_from_unprotected |
Holder vs Verifier restoration
RestoreMode::Holder is strict. Every redacted claim hash in the payload must
have a matching disclosure, and every disclosure must match a redaction. This
is the mode for issuance-time Holder validation.
RestoreMode::Verifier accepts partial disclosure presentations. Disclosures
must still match a redacted claim hash, but redactions without a selected
disclosure are removed from the validated claims set.
In both modes, a disclosure that restores a map key already present at the same level is rejected.
Protocol boundaries
sd_claimsis an unprotected COSE header parameter. A production presentation needs a Key Binding Token (KBT) to bind the selected disclosures to the Holder.sd_algdefaults to SHA-256 (-16) when omitted. The built-in helper supports SHA-256; profiles using another hash can implementRedactionHasher.- AEAD encrypted disclosures are represented by wire structures and header helpers. Key selection, key management, and concrete AEAD encryption or decryption are profile/application responsibilities.
- Salted disclosures are hashed over their bstr-encoded Salted Disclosed Claim
bytes.
Disclosure::from_encodedpreserves those exact bytes for hashing.
Verification
RUSTDOCFLAGS='-D warnings'