Skip to main content

basic/
basic.rs

1use cbor2::Value;
2use cose2::{iana, Error, Label, Sign1Message, Signer, Verifier};
3use sd_cwt::{
4    issue_from_preissuance, restore_payload_from_message, set_disclosures, set_sd_alg,
5    set_sd_cwt_typ, RedactionHasher, RestoreMode, Sha256RedactionHasher, TO_BE_DECOY_TAG,
6    TO_BE_REDACTED_TAG,
7};
8
9fn toy_tag(secret: &[u8], data: &[u8]) -> Vec<u8> {
10    let mut out = [0u8; 16];
11    for (i, byte) in secret.iter().chain(data).enumerate() {
12        out[i % out.len()] ^= byte.wrapping_add(i as u8);
13    }
14    out.to_vec()
15}
16
17struct Issuer;
18
19impl Signer for Issuer {
20    fn alg(&self) -> Option<Label> {
21        Some(iana::AlgorithmEdDSA.into())
22    }
23
24    fn kid(&self) -> Option<&[u8]> {
25        Some(b"issuer-key-1")
26    }
27
28    fn sign(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
29        Ok(toy_tag(b"issuer signing secret", data))
30    }
31}
32
33struct IssuerVerifier;
34
35impl Verifier for IssuerVerifier {
36    fn alg(&self) -> Option<Label> {
37        Some(iana::AlgorithmEdDSA.into())
38    }
39
40    fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), Error> {
41        if toy_tag(b"issuer signing secret", data) == signature {
42            Ok(())
43        } else {
44            Err(Error::verify("SD-CWT issuer signature mismatch"))
45        }
46    }
47}
48
49fn deterministic_demo_salts() -> impl FnMut() -> [u8; 16] {
50    let mut next = 1u8;
51    move || {
52        let salt = [next; 16];
53        next = next.wrapping_add(1);
54        salt
55    }
56}
57
58fn main() -> Result<(), Error> {
59    // Start from a pre-issued claim set. Tag 58 asks the issuer to redact a
60    // map key/value pair or an array element. Tag 62 asks for a decoy digest.
61    let preissued_claims = Value::Map(vec![
62        (Value::from(1), Value::from("https://issuer.example")),
63        (Value::from(8), Value::Map(vec![])), // cnf; fill with a real COSE key in production.
64        (
65            Value::Tag(TO_BE_REDACTED_TAG, Box::new(Value::from("name"))),
66            Value::from("Alice Example"),
67        ),
68        (
69            Value::from("roles"),
70            Value::Array(vec![
71                Value::Tag(TO_BE_REDACTED_TAG, Box::new(Value::from("admin"))),
72                Value::from("user"),
73            ]),
74        ),
75        (
76            Value::Tag(TO_BE_DECOY_TAG, Box::new(Value::from(1))),
77            Value::Null,
78        ),
79    ]);
80
81    // The library never generates randomness. Issuers provide fresh 16-byte
82    // salts. This deterministic generator is for a reproducible example only.
83    let mut salts = deterministic_demo_salts();
84    let issued = issue_from_preissuance(preissued_claims, &mut salts, &Sha256RedactionHasher)?;
85
86    // Sign the issued SD-CWT as a COSE_Sign1 message. The payload contains the
87    // redacted claims; sd_claims carries all disclosures for the Holder.
88    let mut sd_cwt = Sign1Message::new(Some(cbor2::to_vec(&issued.value)?));
89    set_sd_cwt_typ(&mut sd_cwt.protected);
90    set_sd_alg(&mut sd_cwt.protected, Sha256RedactionHasher.algorithm());
91    set_disclosures(&mut sd_cwt.unprotected, issued.disclosures.as_slice());
92    let encoded = sd_cwt.sign_and_encode(&Issuer, None)?;
93
94    // Holder receives every disclosure at issuance, so Holder validation uses
95    // the strict one-to-one rule: every redaction must have a disclosure.
96    let holder_sd_cwt = Sign1Message::verify_and_decode(&IssuerVerifier, &encoded, None)?;
97    let holder_claims = restore_payload_from_message(&holder_sd_cwt, RestoreMode::Holder)?;
98    println!("holder disclosed claims: {}", holder_claims.disclosed);
99
100    // During presentation, the Holder can disclose a subset. Unprotected
101    // headers are not issuer-signed; production presentations bind the selected
102    // disclosures with a Key Binding Token (KBT).
103    let mut presented = holder_sd_cwt.clone();
104    let selected = issued.disclosures.as_slice()[0].clone();
105    set_disclosures(&mut presented.unprotected, std::slice::from_ref(&selected));
106
107    let verifier_claims = restore_payload_from_message(&presented, RestoreMode::Verifier)?;
108    println!(
109        "verifier disclosed claims: {}, removed redactions: {}",
110        verifier_claims.disclosed, verifier_claims.removed_redactions
111    );
112
113    Ok(())
114}