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

bumping web5 versions #149

Merged
merged 9 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
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"
Comment on lines +57 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding ms and typeid-js to http-client in this PR? If ms is necessary in dependencies, why is @types/ms in devDepencencies?

Copy link
Contributor Author

@jiyoontbd jiyoontbd Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding ms and typeid-js to http-client in this PR?

so this PR is part of a 4 PR series in an effort to break down moe's 122-request-token PR which is a chonky boi.

i just ported over the client/package.json changes from the original PR, which includes ms and typeid-js since they're being used by the client during request token generation.

the PR that will use the ms and typeid-js is here

If ms is necessary in dependencies, why is @types/ms in devDepencencies?

i thought we needed to include @types/xyz in devDependencies that correspond to package xyz in dependencies in order to write with type checking during development. is that incorrect?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought we needed to include @types/xyz in devDependencies that correspond to package xyz in dependencies in order to write with type checking during development. is that incorrect?

ah, u right

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yee that's correct @jiyoontbd

},
"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.2",
"@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",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should sinon and @types/sinon have matching versions? Only different by patch version so probably not a big deal regardless

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, ty!

"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
58 changes: 20 additions & 38 deletions packages/protocol/src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type {
PrivateKeyJwk as Web5PrivateKeyJwk,
CryptoAlgorithm,
Web5Crypto,
JwsHeaderParams,
PrivateKeyJwk,
PublicKeyJwk
JwkParamsEcPrivate,
JwkParamsOkpPrivate,
JwkParamsEcPublic,
JwkParamsOkpPublic
} from '@web5/crypto'

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

import canonicalize from 'canonicalize'
Expand Down Expand Up @@ -44,14 +45,14 @@ export type VerifyOptions = {
*/
type SignerValue<T extends Web5Crypto.Algorithm> = {
signer: CryptoAlgorithm,
options: T,
options?: T,
alg: JwsHeader['alg'],
crv: JsonWebKey['crv']
}

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,32 +102,30 @@ 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)
const algorithmName = privateKeyJwk['alg'] || ''
const namedCurve = privateKeyJwk['crv'] || ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is failing because you're reverting some of the changes from #141. Might need to rebase.

const algorithmId = `${algorithmName}:${namedCurve}`

const algorithm = this.algorithms[algorithmId]
if (!algorithm) {
throw new Error(`Algorithm (${algorithmId}) not supported`)
throw new Error(`${algorithmId} not supported`)
}

let verificationMethodId = did.document.verificationMethod?.[0]?.id || ''
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 +169,14 @@ export class Crypto {
throw new Error('Signature verification failed: Expected JWS header to contain alg and kid')
}

const verificationMethod = await deferenceDidUrl(jwsHeader.kid)
const verificationMethod = await deferenceDidUrl(jwsHeader.kid as string)
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 @@ -186,37 +186,19 @@ export class Crypto {

const signatureBytes = Convert.base64Url(base64UrlEncodedSignature).toUint8Array()

const algorithmId = `${jwsHeader['alg']}:${Crypto.extractNamedCurve(publicKeyJwk)}`
const algorithmId = `${jwsHeader['alg']}:${publicKeyJwk['crv']}`
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')
}

const [did] = jwsHeader.kid.split('#')
const [did] = (jwsHeader as JwsHeaderParams).kid.split('#')
return did
}

/**
* Gets crv property from a PublicKeyJwk or PrivateKeyJwk. Returns empty string if crv is undefined.
*/
static extractNamedCurve(jwk: PrivateKeyJwk | PublicKeyJwk | undefined): string {
if (jwk && 'crv' in jwk) {
return jwk.crv
} else {
return ''
}
}
}

/**
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
}
}
}
8 changes: 4 additions & 4 deletions packages/protocol/src/message-kinds/rfq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Ajv from 'ajv'
*/
export type CreateRfqOptions = {
data: MessageKindModel<'rfq'>
metadata: Omit<MessageMetadata<'rfq'>, 'id' |'kind' | 'createdAt' | 'exchangeId'>
metadata: Omit<MessageMetadata<'rfq'>, 'id' | 'kind' | 'createdAt' | 'exchangeId'>
private?: Record<string, any>
}

Expand Down Expand Up @@ -61,7 +61,7 @@ export class Rfq extends Message<'rfq'> {
* @throws if {@link Rfq.payoutMethod} property `paymentDetails` cannot be validated against the provided offering's payoutMethod requiredPaymentDetails
*/
async verifyOfferingRequirements(offering: Offering | ResourceModel<'offering'>) {
if (offering.metadata.id !== this.offeringId) {
if (offering.metadata.id !== this.offeringId) {
throw new Error(`offering id mismatch. (rfq) ${this.offeringId} !== ${offering.metadata.id} (offering)`)
}

Expand Down Expand Up @@ -146,14 +146,14 @@ export class Rfq extends Message<'rfq'> {
return
}

const credentials = PresentationExchange.selectCredentials(this.claims, offering.data.requiredClaims)
const credentials = PresentationExchange.selectCredentials({ vcJwts: this.claims, presentationDefinition: offering.data.requiredClaims })

if (!credentials.length) {
throw new Error(`claims do not fulfill the offering's requirements`)
}

for (let credential of credentials) {
await VerifiableCredential.verify(credential)
await VerifiableCredential.verify({ vcJwt: credential })
}
}

Expand Down
Loading
Loading