Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
bumping web5 versions (#149)
Browse files Browse the repository at this point in the history
* bumping web5 versions. removed jwt related methods and credential methods in dev-tools

* adding dht method to did creation devtool method

* fixing crypto by comparing with main

* reverting changes to rfq spec

* reverting changes to crypto.spec.ts

* using didresolver from web5/dids

* Use pre instantiated DidResolver

* adding a changeset

* removing type in get required claims method

---------

Co-authored-by: Diane Huxley <[email protected]>
  • Loading branch information
jiyoonie9 and Diane Huxley authored Jan 23, 2024
1 parent 738cb1f commit c471b3d
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 239 deletions.
6 changes: 6 additions & 0 deletions .changeset/real-schools-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tbdex/http-client": minor
"@tbdex/protocol": minor
---

Upgrading web5 versions in protocol and http-client
16 changes: 10 additions & 6 deletions packages/http-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,21 @@
},
"dependencies": {
"@tbdex/protocol": "workspace:*",
"@web5/common": "0.2.1",
"@web5/crypto": "0.2.2",
"@web5/dids": "0.2.2",
"query-string": "8.1.0"
"@web5/common": "0.2.2",
"@web5/credentials": "0.4.1",
"@web5/crypto": "0.2.4",
"@web5/dids": "0.2.4",
"ms": "2.1.3",
"query-string": "8.1.0",
"typeid-js": "0.3.0"
},
"devDependencies": {
"@playwright/test": "1.34.3",
"@types/chai": "4.3.5",
"@types/eslint": "8.37.0",
"@types/mocha": "10.0.1",
"@types/sinon": "^17.0.2",
"@types/ms": "0.7.34",
"@types/sinon": "17.0.1",
"@typescript-eslint/eslint-plugin": "5.59.0",
"@typescript-eslint/parser": "5.59.0",
"chai": "4.3.10",
Expand All @@ -78,7 +82,7 @@
"mkdirp": "3.0.1",
"mocha": "10.2.0",
"rimraf": "4.4.0",
"sinon": "15.0.2",
"sinon": "17.0.1",
"typedoc": "0.25.0",
"typedoc-plugin-markdown": "3.16.0",
"typescript": "5.2.2"
Expand Down
11 changes: 6 additions & 5 deletions packages/protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,18 @@
},
"dependencies": {
"@noble/hashes": "1.3.2",
"@web5/common": "0.2.1",
"@web5/credentials": "0.3.2",
"@web5/crypto": "0.2.2",
"@web5/dids": "0.2.2",
"@web5/common": "0.2.2",
"@web5/credentials": "0.4.1",
"@web5/crypto": "0.2.4",
"@web5/dids": "0.2.4",
"ajv": "8.12.0",
"bignumber.js": "^9.1.2",
"canonicalize": "2.0.0",
"typeid-js": "0.3.0"
},
"devDependencies": {
"@playwright/test": "1.34.3",
"@types/sinon": "17.0.2",
"chai": "4.3.10",
"esbuild": "0.16.17",
"karma": "6.4.1",
Expand All @@ -75,7 +76,7 @@
"mocha": "10.2.0",
"node-stdlib-browser": "1.2.0",
"rimraf": "4.4.0",
"sinon": "15.0.2",
"sinon": "17.0.1",
"typedoc": "0.25.0",
"typedoc-plugin-markdown": "3.16.0",
"typescript": "5.2.2"
Expand Down
38 changes: 17 additions & 21 deletions packages/protocol/src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import type {
PrivateKeyJwk as Web5PrivateKeyJwk,
CryptoAlgorithm,
Web5Crypto,
JwsHeaderParams,
JwkParamsEcPrivate,
JwkParamsOkpPrivate,
JwkParamsEcPublic,
JwkParamsOkpPublic,
PrivateKeyJwk,
PublicKeyJwk
} from '@web5/crypto'

import { sha256 } from '@noble/hashes/sha256'
import { Convert } from '@web5/common'
import { EcdsaAlgorithm, EdDsaAlgorithm, Jose } from '@web5/crypto'
import { deferenceDidUrl, isVerificationMethod } from './did-resolver.js'
import { EcdsaAlgorithm, EdDsaAlgorithm } from '@web5/crypto'
import { DidResolver, isVerificationMethod } from './did-resolver.js'

import canonicalize from 'canonicalize'
import { PortableDid } from '@web5/dids'
import { PortableDid, VerificationMethod } from '@web5/dids'

/**
* Options passed to {@link Crypto.sign}
Expand Down Expand Up @@ -51,7 +54,7 @@ type SignerValue<T extends Web5Crypto.Algorithm> = {

const secp256k1Signer: SignerValue<Web5Crypto.EcdsaOptions> = {
signer : new EcdsaAlgorithm(),
options : { name: 'ECDSA', hash: 'SHA-256' },
options : { name: 'ECDSA' },
alg : 'ES256K',
crv : 'secp256k1'
}
Expand Down Expand Up @@ -101,7 +104,7 @@ export class Crypto {
static async sign(opts: SignOptions) {
const { did, payload, detached } = opts

const privateKeyJwk = did.keySet.verificationMethodKeys?.[0]?.privateKeyJwk
const privateKeyJwk = did.keySet.verificationMethodKeys?.[0]?.privateKeyJwk as JwkParamsEcPrivate | JwkParamsOkpPrivate

const algorithmName = privateKeyJwk?.['alg'] || ''
let namedCurve = Crypto.extractNamedCurve(privateKeyJwk)
Expand All @@ -114,19 +117,17 @@ export class Crypto {

let verificationMethodId = did.document.verificationMethod?.[0]?.id || ''
if (verificationMethodId.startsWith('#')) {
verificationMethodId = `${did.did}#${verificationMethodId}`
verificationMethodId = `${did.did}${verificationMethodId}`
}

const jwsHeader: JwsHeader = { alg: algorithm.alg, kid: verificationMethodId }
const base64UrlEncodedJwsHeader = Convert.object(jwsHeader).toBase64Url()
const base64urlEncodedJwsPayload = Convert.uint8Array(payload).toBase64Url()

const key = await Jose.jwkToCryptoKey({ key: privateKeyJwk as Web5PrivateKeyJwk })

const toSign = `${base64UrlEncodedJwsHeader}.${base64urlEncodedJwsPayload}`
const toSignBytes = Convert.string(toSign).toUint8Array()

const signatureBytes = await algorithm.signer.sign({ key, data: toSignBytes, algorithm: algorithm.options })
const signatureBytes = await algorithm.signer.sign({ key: privateKeyJwk, data: toSignBytes, algorithm: algorithm.options })
const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url()

if (detached) {
Expand Down Expand Up @@ -170,13 +171,16 @@ export class Crypto {
throw new Error('Signature verification failed: Expected JWS header to contain alg and kid')
}

const verificationMethod = await deferenceDidUrl(jwsHeader.kid)
const dereferenceResult = await DidResolver.dereference({ didUrl: jwsHeader.kid })

const verificationMethod = dereferenceResult.contentStream as VerificationMethod
if (!isVerificationMethod(verificationMethod)) { // ensure that appropriate verification method was found
throw new Error('Signature verification failed: Expected kid in JWS header to dereference to a DID Document Verification Method')
}

// will be used to verify signature
const { publicKeyJwk } = verificationMethod
const publicKeyJwk = verificationMethod.publicKeyJwk as JwkParamsEcPublic | JwkParamsOkpPublic

if (!publicKeyJwk) { // ensure that Verification Method includes public key as a JWK.
throw new Error('Signature verification failed: Expected kid in JWS header to dereference to a DID Document Verification Method with publicKeyJwk')
}
Expand All @@ -189,15 +193,7 @@ export class Crypto {
const algorithmId = `${jwsHeader['alg']}:${Crypto.extractNamedCurve(publicKeyJwk)}`
const { signer, options } = Crypto.algorithms[algorithmId]

// TODO: remove this monkeypatch once 'ext' is no longer a required property within a jwk passed to `jwkToCryptoKey`
const monkeyPatchPublicKeyJwk = {
...publicKeyJwk,
ext : 'true' as const,
key_ops : ['verify']
}

const key = await Jose.jwkToCryptoKey({ key: monkeyPatchPublicKeyJwk })
const isLegit = await signer.verify({ algorithm: options, key, data: signedDataBytes, signature: signatureBytes })
const isLegit = await signer.verify({ algorithm: options, key: publicKeyJwk, data: signedDataBytes, signature: signatureBytes })

if (!isLegit) {
throw new Error('Signature verification failed: Integrity mismatch')
Expand Down
98 changes: 8 additions & 90 deletions packages/protocol/src/dev-tools.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@

import type { OfferingData, QuoteData, RfqData } from './types.js'
import type { PortableDid } from '@web5/dids'

import { DidIonMethod, DidKeyMethod } from '@web5/dids'
import { utils as vcUtils } from '@web5/credentials'
import { DidDhtMethod, DidIonMethod, DidKeyMethod } from '@web5/dids'
import { VerifiableCredential } from '@web5/credentials'
import { Offering } from './resource-kinds/index.js'
import { Convert } from '@web5/common'
import { Crypto } from './crypto.js'
import { Jose } from '@web5/crypto'
import { Rfq } from './message-kinds/index.js'
import { Resource } from './resource.js'

Expand Down Expand Up @@ -69,9 +65,11 @@ export class DevTools {
*/
static async createDid(didMethod: DidMethodOptions = 'key') {
if (didMethod === 'key') {
return DidKeyMethod.create()
return await DidKeyMethod.create()
} else if (didMethod === 'ion') {
return DidIonMethod.create()
return await DidIonMethod.create()
} else if (didMethod === 'dht') {
return await DidDhtMethod.create()
} else {
throw new Error(`${didMethod} method not implemented.`)
}
Expand Down Expand Up @@ -219,9 +217,9 @@ export class DevTools {
let credential: any = ''

if (opts?.sender) {
const { signedCredential } = await DevTools.createCredential({
const signedCredential = VerifiableCredential.create({
type : 'YoloCredential',
issuer : opts.sender,
issuer : opts.sender.did,
subject : opts.sender.did,
data : {
'beep': 'boop'
Expand Down Expand Up @@ -251,84 +249,4 @@ export class DevTools {
claims : [credential]
}
}

/**
* creates a verifiable credential using the options provided. This method is intended for testing purposes
* @param opts - options used to create the credential
* @returns
*/
static async createCredential(opts: CreateCredentialOptions) {
const credential = {
'@context' : ['https://www.w3.org/2018/credentials/v1'],
'id' : Date.now().toString(),
'type' : ['VerifiableCredential', opts.type],
'issuer' : opts.issuer.did,
'issuanceDate' : vcUtils.getCurrentXmlSchema112Timestamp(),
'credentialSubject' : { id: opts.subject, ...opts.data }
}

const signedCredential = await DevTools.createJwt({
issuer : opts.issuer,
subject : credential.credentialSubject.id,
payload : { vc: credential }
})

return { credential, signedCredential }
}

/**
* Creates a JWT using the options provided.
* It's signed with the issuer's first verification method private key JWK
*
* @param opts - options used to create the JWT
* @returns a compact JWT
*/
static async createJwt(opts: CreateJwtOptions) {
const { issuer, subject, payload } = opts
const privateKeyJwk = issuer.keySet.verificationMethodKeys?.[0].privateKeyJwk
if (!privateKeyJwk) {
throw Error('Could not get private key JWK from issuer')
}

// build jwt header
const algorithmName = privateKeyJwk['alg'] || ''
let namedCurve = Crypto.extractNamedCurve(privateKeyJwk)
const algorithmId = `${algorithmName}:${namedCurve}`
const algorithm = Crypto.algorithms[algorithmId]
const jwtHeader = { alg: algorithm.alg, kid: issuer.document.verificationMethod?.[0]?.id }
const base64urlEncodedJwtHeader = Convert.object(jwtHeader).toBase64Url()

// build jwt payload
const jwtPayload = { iss: issuer.did, sub: subject, ...payload }
const base64urlEncodedJwtPayload = Convert.object(jwtPayload).toBase64Url()

// build what will be signed
const toSign = `${base64urlEncodedJwtHeader}.${base64urlEncodedJwtPayload}`
const bytesToSign = Convert.string(toSign).toUint8Array()

// select signer based on the provided key's named curve
const { signer, options } = algorithm
const signingKey = await Jose.jwkToCryptoKey({ key: privateKeyJwk })

// generate signature
const signatureBytes = await signer.sign({ key: signingKey, data: bytesToSign, algorithm: options })
const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url()

return `${base64urlEncodedJwtHeader}.${base64urlEncodedJwtPayload}.${base64UrlEncodedSignature}`
}

/**
* convenience method that can be used to decode a COMPACT JWT
* @param compactJwt - the JWT to decode
* @returns
*/
static decodeJwt(compactJwt: string) {
const [base64urlEncodedJwtHeader, base64urlEncodedJwtPayload, base64urlEncodedSignature] = compactJwt.split('.')

return {
header : Convert.base64Url(base64urlEncodedJwtHeader).toObject(),
payload : Convert.base64Url(base64urlEncodedJwtPayload).toObject(),
base64urlEncodedSignature
}
}
}
50 changes: 2 additions & 48 deletions packages/protocol/src/did-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DidDocument, DidService, VerificationMethod } from '@web5/dids'

import { DidResolver as Web5DidResolver, DidKeyMethod, DidIonMethod, DidDhtMethod, utils as didUtils } from '@web5/dids'
import { DidResolver as Web5DidResolver, DidKeyMethod, DidIonMethod, DidDhtMethod } from '@web5/dids'

/**
* Can be used to resolve did:ion and did:key DIDs
Expand Down Expand Up @@ -36,58 +36,12 @@ export async function resolveDid(did: string): Promise<DidDocument> {
*/
export type DidResource = DidDocument | VerificationMethod | DidService

// TODO https://github.com/TBD54566975/tbdex-js/issues/147 Remove deferenceDidUrl with web5/dids DidResolver#dereference
/**
* Dereferences a DID URL according to [specification](https://www.w3.org/TR/did-core/#did-url-dereferencing).
* See also: [DID URL Syntax](https://www.w3.org/TR/did-core/#did-url-syntax)
*
* **Note**: Support is limited to did#fragment within [Verification Method](https://www.w3.org/TR/did-core/#verification-methods)
* and [Service](https://www.w3.org/TR/did-core/#services) only
* @param didUrl - the did url to dereference
* @returns the dereferenced resource
* @throws if DID URL cannot be parsed
* @throws if DID cannot be resolved
* @beta
*/
export async function deferenceDidUrl(didUrl: string): Promise<DidResource | undefined> {
const parsedDid = didUtils.parseDid({ didUrl })
if (!parsedDid) {
throw new Error('failed to parse did')
}

const didDocument = await resolveDid(didUrl)

// return the entire DID Document if no fragment is present on the did url
if (!parsedDid.fragment) {
return didDocument
}

const { service, verificationMethod } = didDocument

// create a set of possible id matches. the DID spec allows for an id to be the entire did#fragment or just #fragment.
// See: https://www.w3.org/TR/did-core/#relative-did-urls
// using a set for fast string comparison. DIDs can be lonnng.
const idSet = new Set([didUrl, parsedDid.fragment, `#${parsedDid.fragment}`])

for (let vm of verificationMethod || []) {
if (idSet.has(vm.id)) {
return vm
}
}

for (let svc of service || []) {
if (idSet.has(svc.id)) {
return svc
}
}
}

/**
* type guard for {@link @web5/dids#VerificationMethod}
* @param didResource - the resource to check
* @returns true if the didResource is a `VerificationMethod`
* @beta
*/
export function isVerificationMethod(didResource: DidResource | undefined): didResource is VerificationMethod {
export function isVerificationMethod(didResource: DidResource | null): didResource is VerificationMethod {
return !!didResource && 'id' in didResource && 'type' in didResource && 'controller' in didResource
}
Loading

0 comments on commit c471b3d

Please sign in to comment.