Skip to content

Commit

Permalink
add sd-jwt
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Feb 17, 2024
1 parent 79d67d0 commit 1b47e1f
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 63 deletions.
19 changes: 15 additions & 4 deletions dist/main.js

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {getHtml} from './src/getHtml';
import { getPrivateKey } from './src/exampleKey';
import { getCoseExample } from './src/exampleCose';
import { getJwtExample } from './src/exampleJwt';
import { getSdJwtExample } from './src/exampleSdJwt';



async function processVcJoseCose() {
// add styling for examples
Expand Down Expand Up @@ -108,10 +111,11 @@ function addVcJoseStyles() {
}

export async function processExample(index, json) {
const privateKey = await getPrivateKey()
const coseExample = await getCoseExample(privateKey, json)
const jwtExample = await getJwtExample(privateKey, json)
const html = getHtml({index, coseExample, jwtExample});
const privateKey = await getPrivateKey();
const coseExample = await getCoseExample(privateKey, json);
const jwtExample = await getJwtExample(privateKey, json);
const sdJwtExample = await getSdJwtExample(privateKey, json);
const html = getHtml({index, coseExample, jwtExample, sdJwtExample});
return {html};
}

Expand Down
9 changes: 4 additions & 5 deletions src/exampleCose.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,13 @@ export const getCoseExample = async (privateKey, messageJson) => {
<pre>
${JSON.stringify(messageJson, null, 2)}
</pre>
// ${messageType} (detached payload)
<pre>
${messageHex}
</pre>
// application/cbor-diagnostic
<pre>
${messageDiag.trim()}
</pre>
// ${messageType} (detached payload)
<pre>
${messageHex}
</pre>
`.trim()
}
11 changes: 5 additions & 6 deletions src/exampleJwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const getBinaryMessage = async (privateKey, messageType, messageJson) =>{
const jws = await new jose.CompactSign(
bytes
)
.setProtectedHeader({ kid: privateKey.kid, alg: 'ES384' })
.setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg })
.sign(await key.importKeyLike({
type: 'application/jwk+json',
content: new TextEncoder().encode(JSON.stringify(privateKey))
Expand Down Expand Up @@ -76,14 +76,13 @@ export const getJwtExample = async (privateKey, messageJson) => {
<pre>
${JSON.stringify(messageJson, null, 2)}
</pre>
// ${messageType}
<pre>
${messageEncoded}
</pre>
// Protected Header
<pre>
${JSON.stringify(decodedHeader, null, 2)}
</pre>
// ${messageType}
<pre>
${messageEncoded}
</pre>
`.trim()
}
126 changes: 126 additions & 0 deletions src/exampleSdJwt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@


import yaml from 'yaml'
// import moment from 'moment';
import { issuer, holder, text, key } from "@transmute/verifiable-credentials";

import * as jose from 'jose'


export const generateIssuerClaims = (example)=> {
const generatedClaimsYaml = yaml.stringify(example).replace(/id\: /g, '!sd id: ').replace(/type\:/g, '!sd type:')
return generatedClaimsYaml
}

export const generateHolderDisclosure = (example) => {
const claims = generateIssuerClaims(example)
// redact nested ideas at depth 2 (spaces)
const edited1 = claims.replace(/ !sd id\:(.*?)\n/g, ` id: False\n`)
// disclose types
const edited2 = edited1.replace(/\!sd type\:/g, `type:`)
// redact remaining ids
const edited3 = edited2.replace(/\!sd id\:/g, `id:`)
return edited3
}



const getSdHtml = (vc) =>{
const [token, ...disclosure] = vc.split('~');
const [header, payload, signature] = token.split('.');
const disclosures = disclosure.map((d)=>{
return `~<span class="sd-jwt-disclosure">${d}</span>`
}).join('')
return `
<div class="sd-jwt-compact"><span class="sd-jwt-header">${header}</span>.<span class="sd-jwt-payload">${payload}</span>.<span class="sd-jwt-signature">${signature}</span>${disclosures}</div>`
}

const getVerifiedHtml = (verified)=> {
return `<div>
<pre class="sd-jwt-header">
// disclosed protected header
${JSON.stringify(verified.protectedHeader, null, 2).trim()}</pre>
<pre class="sd-jwt-payload-verified">
// disclosed protected claimset
${JSON.stringify(verified.claimset, null, 2).trim()}</pre>
</div>
`
}

const getDisclosabilityHtml = (claims)=> {
return `<pre>
${claims.trim().replace(/\!sd/g, `<span class="sd-jwt-disclosure">!sd</span>`)}
</pre>`
}

const getDisclosuresHtml = (disclosure)=> {
return `<pre>${disclosure.trim().replace(/False/g, `<span class="sd-jwt-disclosure">False</span>`)}</pre>`
}

const getCredential = async (privateKey, byteSigner, messageType, messageJson) => {
const message = await issuer({
alg: privateKey.alg,
type: messageType,
signer: byteSigner
}).issue({
claimset: new TextEncoder().encode(generateIssuerClaims(messageJson))
})
return message;
}

const getPresentation = async (privateKey, byteSigner, messageType, messageJson) => {
const credential = await getCredential(privateKey, byteSigner, 'application/vc+ld+json+sd-jwt', messageJson)
// since examples are always enveloped, and truncated, we never actually process key binding or disclosures
return credential
}

const getBinaryMessage = async (privateKey, messageType, messageJson) =>{
const byteSigner = {
sign: async (bytes) => {
const jws = await new jose.CompactSign(
bytes
)
.setProtectedHeader({ kid: privateKey.kid, alg: privateKey.alg })
.sign(await key.importKeyLike({
type: 'application/jwk+json',
content: new TextEncoder().encode(JSON.stringify(privateKey))
}))
return text.encoder.encode(jws)
}
}
switch(messageType){
case 'application/vc+ld+json+sd-jwt': {
return getCredential(privateKey, byteSigner, messageType, messageJson)
}
case 'application/vp+ld+json+sd-jwt': {
return getPresentation(privateKey, byteSigner, messageType, messageJson)
}
default: {
throw new Error('Unknown message type')
}
}
}

export const getSdJwtExample = async (privateKey, messageJson) => {
const type = Array.isArray(messageJson.type) ? messageJson.type : [messageJson.type]
const messageType = type.includes('VerifiableCredential') ? 'application/vc+ld+json+sd-jwt' : 'application/vp+ld+json+sd-jwt'
const message = await getBinaryMessage(privateKey, messageType, messageJson)
const messageEncoded = new TextDecoder().decode(message)
const decodedHeader = jose.decodeProtectedHeader(messageEncoded.split('~')[0])
const issuerClaims = generateIssuerClaims(messageJson)
const messageType2 = 'application/ld+yaml'
return `
// ${messageType2}
<pre>
${issuerClaims}
</pre>
// Protected Header
<pre>
${JSON.stringify(decodedHeader, null, 2)}
</pre>
// ${messageType}
<pre>
${messageEncoded}
</pre>
`.trim()
}
48 changes: 4 additions & 44 deletions src/getHtml.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,14 @@

const getSdHtml = (vc) =>{
const [token, ...disclosure] = vc.split('~');
const [header, payload, signature] = token.split('.');
const disclosures = disclosure.map((d)=>{
return `~<span class="sd-jwt-disclosure">${d}</span>`
}).join('')
return `
<div class="sd-jwt-compact"><span class="sd-jwt-header">${header}</span>.<span class="sd-jwt-payload">${payload}</span>.<span class="sd-jwt-signature">${signature}</span>${disclosures}</div>`
}

const getVerifiedHtml = (verified)=> {
return `<div>
<pre class="sd-jwt-header">
// disclosed protected header
${JSON.stringify(verified.protectedHeader, null, 2).trim()}</pre>
<pre class="sd-jwt-payload-verified">
// disclosed protected claimset
${JSON.stringify(verified.claimset, null, 2).trim()}</pre>
</div>
`
}

const getDisclosabilityHtml = (claims)=> {
return `<pre>
${claims.trim().replace(/\!sd/g, `<span class="sd-jwt-disclosure">!sd</span>`)}
</pre>`
}

const getDisclosuresHtml = (disclosure)=> {
return `<pre>${disclosure.trim().replace(/False/g, `<span class="sd-jwt-disclosure">False</span>`)}</pre>`
}

export const getHtml = ({ index, coseExample, jwtExample })=>{
// TODO: refactor
// const tab1Content = getDisclosabilityHtml(claims);
// const tab2Content = getSdHtml(vc);
// const tab3Content = getDisclosuresHtml(disclosure);
// const tab4Content = getSdHtml(vp);
// const tab5Content = getVerifiedHtml(verified);

// 3 tabs is the most that fits on 1 screen
export const getHtml = ({ index, coseExample, jwtExample, sdJwtExample })=>{
const tab1Content = coseExample;
const tab2Content = jwtExample;
const tab3Content = 'tab3Content';

const tab3Content = sdJwtExample;
return `
<div class="vc-jose-cose-tabbed">
<input type="radio" id="vc-jose-cose-tab-${index}-cose" name="vc-jose-cose-tabs-${index}" >
<input type="radio" id="vc-jose-cose-tab-${index}-jwt" name="vc-jose-cose-tabs-${index}" checked="checked">
<input type="radio" id="vc-jose-cose-tab-${index}-sd-jwt" name="vc-jose-cose-tabs-${index}">
<input type="radio" id="vc-jose-cose-tab-${index}-jwt" name="vc-jose-cose-tabs-${index}" >
<input type="radio" id="vc-jose-cose-tab-${index}-sd-jwt" name="vc-jose-cose-tabs-${index}" checked="checked">
<ul class="vc-jose-cose-tabs">
<li class="vc-jose-cose-tab">
<label for="vc-jose-cose-tab-${index}-cose">COSE</label>
Expand Down

0 comments on commit 1b47e1f

Please sign in to comment.