Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SD-JWT VC implementation #1413

Merged
merged 37 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b7853fb
Resolver trait and CompoundResolver macro
UMR1352 Jun 11, 2024
0114b35
invert Resolver type parameters
UMR1352 Jun 11, 2024
88a538e
associated type Target instead of type parameter T
UMR1352 Jun 11, 2024
bfd5d59
fix type issue in #[resolver(..)] annotation, support for multiple re…
UMR1352 Jun 12, 2024
5671118
resolver integration
UMR1352 Jun 13, 2024
d7c2cd9
Merge branch 'main' into identity2/resolver
UMR1352 Sep 16, 2024
7c815c5
feature gate resolver-v2
UMR1352 Sep 16, 2024
b28fa9a
structures & basic operations
UMR1352 Sep 17, 2024
2f7fadf
SdJwtVc behaves as a superset of SdJwt
UMR1352 Sep 17, 2024
0d3127f
issuer's metadata fetching & validation
UMR1352 Sep 18, 2024
55319c5
type metadata & credential verification
UMR1352 Sep 19, 2024
704b395
change resolver's constraints
UMR1352 Sep 19, 2024
130af00
integrity metadata
UMR1352 Sep 19, 2024
d278060
display metadata
UMR1352 Sep 20, 2024
0a1ed27
claim metadata
UMR1352 Sep 20, 2024
8d00263
fetch issuer's JWK (to ease verification)
UMR1352 Sep 20, 2024
2111e3b
validate claim disclosability
UMR1352 Sep 24, 2024
06e31f2
add missing license header
UMR1352 Sep 24, 2024
e5e8537
resolver change, validation
UMR1352 Sep 24, 2024
9bf907f
SdJwtVcBuilder & tests
UMR1352 Sep 30, 2024
67a0abc
validation test
UMR1352 Oct 1, 2024
a510185
KB-JWT validation
UMR1352 Oct 2, 2024
654b188
review comment
UMR1352 Oct 11, 2024
72333bb
undo resolver-v2
UMR1352 Dec 11, 2024
468809e
Merge branch 'main' into feat/sd-jwt-vc
UMR1352 Dec 11, 2024
3f992c4
fix CI errors
UMR1352 Dec 11, 2024
66f7b79
make clippy happy
UMR1352 Dec 11, 2024
03f1483
add missing license headers
UMR1352 Dec 11, 2024
51e1b28
add 'SdJwtVcBuilder::from_credential' to easily convert into a SD-JWT VC
UMR1352 Dec 11, 2024
035b50f
cargo clippy
UMR1352 Dec 12, 2024
f2c0704
fix wasm compilation errors, clippy
UMR1352 Dec 12, 2024
9f8f997
WASM Bindings for SD-JWT VC (#1493)
UMR1352 Jan 10, 2025
00fae28
Apply suggestions from code review
UMR1352 Jan 13, 2025
6efd431
review comments
UMR1352 Jan 13, 2025
0a3131f
clippy & fmt
UMR1352 Jan 14, 2025
2703d06
clippy & fmt
UMR1352 Jan 17, 2025
6ee84b2
dprint fmt
UMR1352 Jan 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ edition = "2021"
homepage = "https://www.iota.org"
license = "Apache-2.0"
repository = "https://github.com/iotaledger/identity.rs"
rust-version = "1.65"

[workspace.lints.clippy]
result_large_err = "allow"
13 changes: 12 additions & 1 deletion bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ description = "Web Assembly bindings for the identity-rs crate."
crate-type = ["cdylib", "rlib"]

[dependencies]
anyhow = { version = "1.0.94", features = ["std"] }
async-trait = { version = "0.1", default-features = false }
bls12_381_plus = "0.8.17"
console_error_panic_hook = { version = "0.1" }
Expand All @@ -26,6 +27,7 @@ js-sys = { version = "0.3.61" }
json-proof-token = "0.3.4"
proc_typescript = { version = "0.1.0", path = "./proc_typescript" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"
serde_json = { version = "1.0", default-features = false }
serde_repr = { version = "0.1", default-features = false }
# Want to use the nice API of tokio::sync::RwLock for now even though we can't use threads.
Expand All @@ -37,7 +39,16 @@ zkryptium = "0.2.2"
[dependencies.identity_iota]
path = "../../identity_iota"
default-features = false
features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021", "jpt-bbs-plus"]
features = [
"client",
"revocation-bitmap",
"resolver",
"domain-linkage",
"sd-jwt",
"status-list-2021",
"jpt-bbs-plus",
"sd-jwt-vc",
]

[dev-dependencies]
rand = "0.8.5"
Expand Down
167 changes: 167 additions & 0 deletions bindings/wasm/examples/src/1_advanced/10_sd_jwt_vc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import {
IJwk,
IJwkParams,
IResolver,
IssuerMetadata,
Jwk,
JwkType,
JwsVerificationOptions,
KeyBindingJwtBuilder,
KeyBindingJWTValidationOptions,
SdJwtVcBuilder,
Sha256Hasher,
Timestamp,
TypeMetadataHelper,
} from "@iota/identity-wasm/node";
import { exportJWK, generateKeyPair, JWK, JWTHeaderParameters, JWTPayload, SignJWT } from "jose";

const vc_metadata: TypeMetadataHelper = JSON.parse(`{
"vct": "https://example.com/education_credential",
"name": "Betelgeuse Education Credential - Preliminary Version",
"description": "This is our development version of the education credential. Don't panic.",
"claims": [
{
"path": ["name"],
"display": [
{
"lang": "de-DE",
"label": "Vor- und Nachname",
"description": "Der Name des Studenten"
},
{
"lang": "en-US",
"label": "Name",
"description": "The name of the student"
}
],
"sd": "allowed"
},
{
"path": ["address"],
"display": [
{
"lang": "de-DE",
"label": "Adresse",
"description": "Adresse zum Zeitpunkt des Abschlusses"
},
{
"lang": "en-US",
"label": "Address",
"description": "Address at the time of graduation"
}
],
"sd": "always"
},
{
"path": ["address", "street_address"],
"display": [
{
"lang": "de-DE",
"label": "Straße"
},
{
"lang": "en-US",
"label": "Street Address"
}
],
"sd": "always",
"svg_id": "address_street_address"
},
{
"path": ["degrees", null],
"display": [
{
"lang": "de-DE",
"label": "Abschluss",
"description": "Der Abschluss des Studenten"
},
{
"lang": "en-US",
"label": "Degree",
"description": "Degree earned by the student"
}
],
"sd": "allowed"
}
]
}`);

const keypair_jwk = async (): Promise<[JWK, JWK]> => {
const [sk, pk] = await generateKeyPair("ES256").then(res => [res.privateKey, res.publicKey]);
const sk_jwk = await exportJWK(sk);
const pk_jwk = await exportJWK(pk);

return [sk_jwk, pk_jwk];
};

const signer = async (header: object, payload: object, sk_jwk: JWK) => {
return new SignJWT(payload as JWTPayload)
.setProtectedHeader(header as JWTHeaderParameters)
.sign(sk_jwk)
.then(jws => new TextEncoder().encode(jws));
};

export async function sdJwtVc() {
const hasher = new Sha256Hasher();
const issuer = "https://example.com/";
const [sk_jwk, pk_jwk] = await keypair_jwk();
const issuer_public_jwk = { ...pk_jwk, kty: JwkType.Ec, kid: "key1" } as IJwk;
const issuer_signer = (header: object, payload: object) => signer(header, payload, sk_jwk);
const issuer_metadata = new IssuerMetadata(issuer, { jwks: { keys: [issuer_public_jwk] } });
const dummy_resolver = {
resolve: async (input: string) => {
if (input == "https://example.com/.well-known/jwt-vc-issuer/") {
return new TextEncoder().encode(JSON.stringify(issuer_metadata.toJSON()));
}
if (input == "https://example.com/.well-known/vct/education_credential") {
return new TextEncoder().encode(JSON.stringify(vc_metadata));
}
},
} as IResolver<string, Uint8Array>;
const [holder_sk, holder_pk] = await keypair_jwk();
const holder_public_jwk = { ...holder_pk, kty: JwkType.Ec, kid: "key2" } as IJwk;
const holder_signer = (header: object, payload: object) => signer(header, payload, holder_sk);

/// Issuer creates an SD-JWT VC.
let sd_jwt_vc = await new SdJwtVcBuilder({
name: "John Doe",
address: {
street_address: "A random street",
number: "3a",
},
degree: [],
}, hasher)
.header({ kid: "key1" })
.vct("https://example.com/education_credential")
.iat(Timestamp.nowUTC())
.iss(issuer)
.requireKeyBinding({ kid: holder_public_jwk.kid })
.makeConcealable("/address/street_address")
.makeConcealable("/address")
.finish({ sign: issuer_signer }, "ES256");

console.log(`issued SD-JWT VC: ${sd_jwt_vc.toString()}`);

// Holder receives its SD-JWT VC and attaches its keybinding JWT.
const kb_jwt = await new KeyBindingJwtBuilder()
.iat(Timestamp.nowUTC())
.header({ kid: holder_public_jwk.kid })
.nonce("abcdefghi")
.aud("https://example.com/verify")
.finish(sd_jwt_vc.asSdJwt(), "ES256", { sign: holder_signer });
const { disclosures, sdJwtVc } = sd_jwt_vc.intoPresentation(hasher).attachKeyBindingJwt(kb_jwt).finish();
console.log(`presented SD-JWT VC: ${sdJwtVc}`);

// Verifier checks the presented sdJwtVc.
await sdJwtVc.validate(dummy_resolver, hasher);
sdJwtVc.validateKeyBinding(
new Jwk(holder_public_jwk as IJwkParams),
hasher,
new KeyBindingJWTValidationOptions({ nonce: "abcdefghi", jwsOptions: new JwsVerificationOptions() }),
);

console.log("The presented SdJwtVc is valid!");
}
3 changes: 3 additions & 0 deletions bindings/wasm/examples/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createVC } from "./0_basic/5_create_vc";
import { createVP } from "./0_basic/6_create_vp";
import { revokeVC } from "./0_basic/7_revoke_vc";
import { didControlsDid } from "./1_advanced/0_did_controls_did";
import { sdJwtVc } from "./1_advanced/10_sd_jwt_vc";
import { didIssuesNft } from "./1_advanced/1_did_issues_nft";
import { nftOwnsDid } from "./1_advanced/2_nft_owns_did";
import { didIssuesTokens } from "./1_advanced/3_did_issues_tokens";
Expand Down Expand Up @@ -64,6 +65,8 @@ async function main() {
return await zkp();
case "9_zkp_revocation":
return await zkp_revocation();
case "10_sd_jwt_vc":
return await sdJwtVc();
default:
throw "Unknown example name: '" + argument + "'";
}
Expand Down
Loading
Loading