Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Apr 24, 2024
1 parent 163fa5a commit 3866a9e
Show file tree
Hide file tree
Showing 5 changed files with 441 additions and 32 deletions.
336 changes: 335 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,342 @@ npm i @transmute/verifiable-credentials@latest --save
import * as transmute from "@transmute/verifiable-credentials";
```

### Generating Keys

```ts
const privateKey = await transmute.key.generate({
alg,
type: "application/jwk+json",
});
// console.log(new TextDecoder().decode(privateKey))
// {
// "kid": "xSgm4GQOT_ZyYFApew0GnRvPWt70omVJV9XVB5tsmN8",
// "alg": "ES256",
// "kty": "EC",
// "crv": "P-256",
// "x": "XRkZngz2KSCrLdXKGCRNyDzBgsovioZIqMWnF42nmdg",
// "y": "H2t6Xxdg8p8Cqn2-hsuWnXYj0192He4zTZghAxNXllo",
// ...
// }
const publicKey = await transmute.key.publicFromPrivate({
type: "application/jwk+json",
content: privateKey,
});
// console.log(new TextDecoder().decode(publicKey))
// {
// "kid": "xSgm4GQOT_ZyYFApew0GnRvPWt70omVJV9XVB5tsmN8",
// "alg": "ES256",
// "kty": "EC",
// "crv": "P-256",
// "x": "XRkZngz2KSCrLdXKGCRNyDzBgsovioZIqMWnF42nmdg",
// "y": "H2t6Xxdg8p8Cqn2-hsuWnXYj0192He4zTZghAxNXllo",
// }
```

### Issuing Credentials

```ts
const alg = `ES256`;
const statusListSize = 131072;
const revocationIndex = 94567;
const suspensionIndex = 23452;

const issuer = `did:example:123`;
const baseURL = `https://vendor.example/api`;
const issued = await transmute
.issuer({
alg,
type: "application/vc+ld+json+jwt",
signer: {
sign: async (bytes: Uint8Array) => {
const jws = await new jose.CompactSign(bytes)
.setProtectedHeader({ kid: `${issuer}#key-42`, alg })
.sign(
await transmute.key.importKeyLike({
type: "application/jwk+json",
content: privateKey,
})
);
return transmute.text.encoder.encode(jws);
},
},
})
.issue({
claimset: transmute.text.encoder.encode(`
"@context":
- https://www.w3.org/ns/credentials/v2
- https://www.w3.org/ns/credentials/examples/v2
id: ${baseURL}/credentials/3732
type:
- VerifiableCredential
- ExampleDegreeCredential
issuer:
id: ${issuer}
name: "Example University"
validFrom: ${moment().toISOString()}
credentialSchema:
id: ${baseURL}/schemas/product-passport
type: JsonSchema
credentialStatus:
- id: ${baseURL}/credentials/status/3#${revocationIndex}
type: BitstringStatusListEntry
statusPurpose: revocation
statusListIndex: "${revocationIndex}"
statusListCredential: "${baseURL}/credentials/status/3"
- id: ${baseURL}/credentials/status/4#${suspensionIndex}
type: BitstringStatusListEntry
statusPurpose: suspension
statusListIndex: "${suspensionIndex}"
statusListCredential: "${baseURL}/credentials/status/4"
credentialSubject:
id: did:example:ebfeb1f712ebc6f1c276e12ec21
degree:
type: ExampleBachelorDegree
subtype: Bachelor of Science and Arts
`),
});
// console.log(new TextDecoder().decode(issued))
// eyJraWQiOiJkaWQ6ZXhhbXBsZToxMjMja2V5LTQyIiwiYWxnIjoiRVMyNTYifQ.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvbnMvY3JlZGVudGlhbHMvdjIiLCJodHRwczovL3ZlbmRvci5leGFtcGxlL2FwaS9jb250ZXh0L3YyIl0sImlkIjoiaHR0cHM6Ly92ZW5kb3IuZXhhbXBsZS9hcGkvY3JlZGVudGlhbHMvMzczMiIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJFeGFtcGxlRGVncmVlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJkaWQ6ZXhhbXBsZToxMjMiLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5In0sInZhbGlkRnJvbSI6IjIwMjQtMDQtMjRUMjI6MjM6MDIuODU2WiIsImNyZWRlbnRpYWxTY2hlbWEiOnsiaWQiOiJodHRwczovL3ZlbmRvci5leGFtcGxlL2FwaS9zY2hlbWFzL3Byb2R1Y3QtcGFzc3BvcnQiLCJ0eXBlIjoiSnNvblNjaGVtYSJ9LCJjcmVkZW50aWFsU3RhdHVzIjpbeyJpZCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy8zIzk0NTY3IiwidHlwZSI6IkJpdHN0cmluZ1N0YXR1c0xpc3RFbnRyeSIsInN0YXR1c1B1cnBvc2UiOiJyZXZvY2F0aW9uIiwic3RhdHVzTGlzdEluZGV4IjoiOTQ1NjciLCJzdGF0dXNMaXN0Q3JlZGVudGlhbCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy8zIn0seyJpZCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy80IzIzNDUyIiwidHlwZSI6IkJpdHN0cmluZ1N0YXR1c0xpc3RFbnRyeSIsInN0YXR1c1B1cnBvc2UiOiJzdXNwZW5zaW9uIiwic3RhdHVzTGlzdEluZGV4IjoiMjM0NTIiLCJzdGF0dXNMaXN0Q3JlZGVudGlhbCI6Imh0dHBzOi8vdmVuZG9yLmV4YW1wbGUvYXBpL2NyZWRlbnRpYWxzL3N0YXR1cy80In1dLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSIsImRlZ3JlZSI6eyJ0eXBlIjoiRXhhbXBsZUJhY2hlbG9yRGVncmVlIiwic3VidHlwZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19.xHjfiUwx61qmoVMGLrHT8FI-ZYUHXQy4B6oF0Cb5EOTYYPXdwjW9sa1l5aa008xvsFvrcNats9TywmN2nNKz6A
```

### Validating Credentials

```ts
const validated = await transmute
.validator({
resolver: {
resolve: async ({ id, type, content }) => {
// Resolve external resources according to verifier policy
// In this case, we return inline exampes...
if (id === `${baseURL}/schemas/product-passport`) {
return {
type: `application/schema+json`,
content: transmute.text.encoder.encode(`
{
"$id": "${baseURL}/schemas/product-passport",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Example JSON Schema",
"description": "This is a test schema",
"type": "object",
"properties": {
"credentialSubject": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
}
}
`),
};
}
if (id === `${baseURL}/credentials/status/3`) {
return {
type: `application/vc+ld+json+jwt`,
content: await transmute
.issuer({
alg: "ES384",
type: "application/vc+ld+json+cose",
signer: {
sign: async (bytes: Uint8Array) => {
const jws = await new jose.CompactSign(bytes)
.setProtectedHeader({ kid: `${issuer}#key-42`, alg })
.sign(
await transmute.key.importKeyLike({
type: "application/jwk+json",
content: privateKey,
})
);
return transmute.text.encoder.encode(jws);
},
},
})
.issue({
claimset: transmute.text.encoder.encode(
`
"@context":
- https://www.w3.org/ns/credentials/v2
id: ${baseURL}/status/3#list
type:
- VerifiableCredential
- BitstringStatusListCredential
issuer:
id: ${issuer}
validFrom: ${moment().toISOString()}
credentialSubject:
id: ${baseURL}/status/3#list#list
type: BitstringStatusList
statusPurpose: revocation
encodedList: ${await transmute.status
.bs(statusListSize)
.set(revocationIndex, false)
.encode()}
`.trim()
),
}),
};
}
if (id === `${baseURL}/credentials/status/4`) {
return {
type: `application/vc+ld+json+jwt`,
content: await transmute
.issuer({
alg: "ES384",
type: "application/vc+ld+json+cose",
signer: {
sign: async (bytes: Uint8Array) => {
const jws = await new jose.CompactSign(bytes)
.setProtectedHeader({ kid: `${issuer}#key-42`, alg })
.sign(
await transmute.key.importKeyLike({
type: "application/jwk+json",
content: privateKey,
})
);
return transmute.text.encoder.encode(jws);
},
},
})
.issue({
claimset: transmute.text.encoder.encode(
`
"@context":
- https://www.w3.org/ns/credentials/v2
id: ${baseURL}/status/4#list
type:
- VerifiableCredential
- BitstringStatusListCredential
issuer:
id: ${issuer}
validFrom: ${moment().toISOString()}
credentialSubject:
id: ${baseURL}/status/4#list#list
type: BitstringStatusList
statusPurpose: suspension
encodedList: ${await transmute.status
.bs(statusListSize)
.set(suspensionIndex, false)
.encode()}
`.trim()
),
}),
};
}
if (content != undefined && type === `application/vc+ld+json+jwt`) {
const { kid } = jose.decodeProtectedHeader(
transmute.text.decoder.decode(content)
);
// lookup public key by kid on a trusted resolver
if (kid === `did:example:123#key-42`) {
return {
type: "application/jwk+json",
content: publicKey,
};
}
}
throw new Error("Resolver option not supported.");
},
},
})
.validate({
type: "application/vc+ld+json+jwt",
content: issued,
});

// expect(validated.valid).toBe(true)
// expect(validated.schema[`${baseURL}/schemas/product-passport`].valid).toBe(true)
// expect(validated.status[`${baseURL}/credentials/status/3#${revocationIndex}`].valid).toBe(false)
// expect(validated.status[`${baseURL}/credentials/status/4#${suspensionIndex}`].valid).toBe(false)
```

### Issuing Presentations

```ts
const presentation = await transmute
.holder({
alg,
type: "application/vp+ld+json+jwt",
})
.issue({
signer: {
sign: async (bytes: Uint8Array) => {
const jws = await new jose.CompactSign(bytes)
.setProtectedHeader({ kid: `${issuer}#key-42`, alg })
.sign(
await transmute.key.importKeyLike({
type: "application/jwk+json",
content: privateKey,
})
);
return transmute.text.encoder.encode(jws);
},
},
presentation: {
"@context": ["https://www.w3.org/ns/credentials/v2"],
type: ["VerifiablePresentation"],
holder: `${baseURL}/holders/565049`,
// this part is built from disclosures without key binding below.
// "verifiableCredential": [{
// "@context": "https://www.w3.org/ns/credentials/v2",
// "id": "data:application/vc+ld+json+sd-jwt;QzVjV...RMjU",
// "type": "EnvelopedVerifiableCredential"
// }]
},
disclosures: [
{
type: `application/vc+ld+json+jwt`,
credential: issued,
},
],
});
```

### Validating Presentations

```ts
// todo...
const validation = await transmute
.validator({
resolver: {
resolve: async ({ type, content }) => {
// Resolve external resources according to verifier policy
// In this case, we return inline exampes...
if (content != undefined && type === `application/vp+ld+json+jwt`) {
const { kid } = jose.decodeProtectedHeader(
transmute.text.decoder.decode(content)
);
// lookup public key on a resolver
if (kid === `did:example:123#key-42`) {
return {
type: "application/jwk+json",
content: publicKey,
};
}
}
throw new Error("Resolver option not supported.");
},
},
})
.validate<transmute.TraceablePresentationValidationResult>({
type: `application/vp+ld+json+jwt`,
content: presentation,
});
// {
// "valid": true,
// "content": {
// "@context": [
// "https://www.w3.org/ns/credentials/v2"
// ],
// "type": [
// "VerifiablePresentation"
// ],
// "holder": "https://vendor.example/api/holders/565049",
// "verifiableCredential": [
// {
// "@context": "https://www.w3.org/ns/credentials/v2",
// "id": "data:application/vc+ld+json+jwt;eyJraWQiOiJkaWQ6ZX...
```

## Develop
Expand Down
8 changes: 4 additions & 4 deletions src/cr1/key/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export type RequestGenerateCredentialKey = {

}

export const generate = async <T = Uint8Array>(req: RequestGenerateCredentialKey): Promise<Uint8Array> => {
export const generate = async <T = Uint8Array>(req: RequestGenerateCredentialKey): Promise<T> => {
if (req.type === 'application/jwk+json') {
const obj = await cose.key.generate(req.alg, 'application/jwk+json')
const text = JSON.stringify(obj, null, 2)
return encoder.encode(text)
return encoder.encode(text) as T
}
if (req.type === 'application/cose-key') {
const result = await cose.key.generate(req.alg, 'application/cose-key')
return new Uint8Array(cose.cbor.encode(result))
return new Uint8Array(cose.cbor.encode(result)) as T
}
if (req.type === 'application/pkcs8') {
const result = await cose.certificate.root({
Expand All @@ -35,7 +35,7 @@ export const generate = async <T = Uint8Array>(req: RequestGenerateCredentialKey
nbf: req.nbf || moment().toISOString(), // now
exp: req.nbf || moment().add(5, 'minutes').toISOString() // in 5 minutes
})
return encoder.encode(result.private)
return encoder.encode(result.private) as T
}
throw new Error('Unsupported content type for private key')
}
5 changes: 5 additions & 0 deletions src/cr1/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,9 @@ export type ValidationResult = {
schema: Record<string, { valid: boolean, errors?: JsonSchemaError[] }>
status: Record<string, { valid: boolean, purpose: string, errors?: StatusListError[] }>
warnings: ConformanceWarningMessage[]
}


export type TraceablePresentationValidationResult = ValidationResult & {
content: VerifiablePresentationWithHolderObject & VerifiablePresentationOfEnveloped
}
Loading

0 comments on commit 3866a9e

Please sign in to comment.