-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for ConditionalProof2022 verificationMethods (#272)
- Loading branch information
1 parent
986ff37
commit 9bebe3f
Showing
8 changed files
with
864 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,9 @@ | |
"contributors": [ | ||
"Mircea Nistor <[email protected]>", | ||
"Oliver Terbu <[email protected]>", | ||
"Joel Thorstensson <[email protected]>" | ||
"Joel Thorstensson <[email protected]>", | ||
"Jack Tanner <[email protected]>", | ||
"Rebal Alhaqash <[email protected]>" | ||
], | ||
"repository": { | ||
"type": "git", | ||
|
@@ -66,8 +68,10 @@ | |
"@babel/preset-env": "7.20.2", | ||
"@babel/preset-typescript": "7.21.0", | ||
"@ethersproject/address": "5.7.0", | ||
"@greymass/eosio": "^0.6.9", | ||
"@semantic-release/changelog": "6.0.3", | ||
"@semantic-release/git": "10.0.1", | ||
"@tonomy/antelope-did": "^0.1.5", | ||
"@types/jest": "28.1.8", | ||
"@types/jsonwebtoken": "^8.5.9", | ||
"@types/jwk-to-pem": "^2.0.1", | ||
|
@@ -105,4 +109,4 @@ | |
"eslintIgnore": [ | ||
"*.test.ts" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import type { VerificationMethod } from 'did-resolver' | ||
import { EcdsaSignature } from './util' | ||
import { JWT_ERROR } from './Errors' | ||
import { JWTDecoded, JWTVerifyOptions, resolveAuthenticator, verifyJWT, verifyJWTDecoded } from './JWT' | ||
|
||
export type Signer = (data: string | Uint8Array) => Promise<EcdsaSignature | string> | ||
export type SignerAlgorithm = (payload: string, signer: Signer) => Promise<string> | ||
|
||
export const CONDITIONAL_PROOF_2022 = 'ConditionalProof2022' | ||
|
||
export async function verifyProof( | ||
jwt: string, | ||
{ header, payload, signature, data }: JWTDecoded, | ||
authenticator: VerificationMethod, | ||
options: JWTVerifyOptions | ||
): Promise<VerificationMethod> { | ||
if (authenticator.type === CONDITIONAL_PROOF_2022) { | ||
return await verifyConditionalProof(jwt, { payload, header, signature, data }, authenticator, options) | ||
} else { | ||
return await verifyJWTDecoded({ header, payload, data, signature }, [authenticator]) | ||
} | ||
} | ||
|
||
export async function verifyConditionalProof( | ||
jwt: string, | ||
{ header, payload, signature, data }: JWTDecoded, | ||
authenticator: VerificationMethod, | ||
options: JWTVerifyOptions | ||
): Promise<VerificationMethod> { | ||
// Validate the condition according to it's condition property | ||
if (authenticator.conditionWeightedThreshold) { | ||
return await verifyConditionWeightedThreshold(jwt, { header, payload, data, signature }, authenticator, options) | ||
} else if (authenticator.conditionDelegated) { | ||
return await verifyConditionDelegated(jwt, { header, payload, data, signature }, authenticator, options) | ||
} | ||
// TODO other conditions | ||
|
||
throw new Error( | ||
`${JWT_ERROR.INVALID_JWT}: conditional proof type did not find condition for authenticator ${authenticator.id}.` | ||
) | ||
} | ||
|
||
async function verifyConditionWeightedThreshold( | ||
jwt: string, | ||
{ header, payload, data, signature }: JWTDecoded, | ||
authenticator: VerificationMethod, | ||
options: JWTVerifyOptions | ||
): Promise<VerificationMethod> { | ||
if (!authenticator.conditionWeightedThreshold || !authenticator.threshold) { | ||
throw new Error('Expected conditionWeightedThreshold and threshold') | ||
} | ||
|
||
const issuers: string[] = [] | ||
const threshold = authenticator.threshold | ||
let weightCount = 0 | ||
|
||
for (const weightedCondition of authenticator.conditionWeightedThreshold) { | ||
const currentCondition = weightedCondition.condition | ||
let foundSigner: VerificationMethod | undefined | ||
|
||
try { | ||
if (currentCondition.type === CONDITIONAL_PROOF_2022) { | ||
if (!options.didAuthenticator) { | ||
throw new Error('Expected didAuthenticator') | ||
} | ||
|
||
const newOptions = { | ||
...options, | ||
...{ | ||
didAuthenticator: { | ||
didResolutionResult: options.didAuthenticator?.didResolutionResult, | ||
authenticators: [currentCondition], | ||
issuer: currentCondition.id, | ||
}, | ||
}, | ||
} | ||
const { verified } = await verifyJWT(jwt, newOptions as JWTVerifyOptions) | ||
if (verified) { | ||
foundSigner = currentCondition | ||
} | ||
} else { | ||
foundSigner = await verifyJWTDecoded({ header, payload, data, signature }, currentCondition) | ||
} | ||
} catch (e) { | ||
if (!(e as Error).message.startsWith(JWT_ERROR.INVALID_SIGNATURE)) throw e | ||
} | ||
|
||
if (foundSigner && !issuers.includes(foundSigner.id)) { | ||
issuers.push(foundSigner.id) | ||
weightCount += weightedCondition.weight | ||
|
||
if (weightCount >= threshold) { | ||
return authenticator | ||
} | ||
} | ||
} | ||
throw new Error(`${JWT_ERROR.INVALID_SIGNATURE}: condition for authenticator ${authenticator.id} is not met.`) | ||
} | ||
|
||
async function verifyConditionDelegated( | ||
jwt: string, | ||
{ header, payload, data, signature }: JWTDecoded, | ||
authenticator: VerificationMethod, | ||
options: JWTVerifyOptions | ||
): Promise<VerificationMethod> { | ||
if (!authenticator.conditionDelegated) { | ||
throw new Error('Expected conditionDelegated') | ||
} | ||
if (!options.resolver) { | ||
throw new Error('Expected resolver') | ||
} | ||
|
||
let foundSigner: VerificationMethod | undefined | ||
|
||
const issuer = authenticator.conditionDelegated | ||
const didAuthenticator = await resolveAuthenticator(options.resolver, header.alg, issuer, options.proofPurpose) | ||
const didResolutionResult = didAuthenticator.didResolutionResult | ||
|
||
if (!didResolutionResult?.didDocument) { | ||
throw new Error(`${JWT_ERROR.RESOLVER_ERROR}: Could not resolve delegated DID ${issuer}.`) | ||
} | ||
|
||
const delegatedAuthenticator = didAuthenticator.authenticators.find((authenticator) => authenticator.id === issuer) | ||
if (!delegatedAuthenticator) { | ||
throw new Error( | ||
`${JWT_ERROR.NO_SUITABLE_KEYS}: Could not find delegated authenticator ${issuer} in it's DID Document` | ||
) | ||
} | ||
|
||
if (delegatedAuthenticator.type === CONDITIONAL_PROOF_2022) { | ||
const { verified } = await verifyJWT(jwt, { | ||
...options, | ||
...{ | ||
didAuthenticator: { | ||
didResolutionResult, | ||
authenticators: [delegatedAuthenticator], | ||
issuer: delegatedAuthenticator.id, | ||
}, | ||
}, | ||
}) | ||
if (verified) { | ||
foundSigner = delegatedAuthenticator | ||
} | ||
} else { | ||
try { | ||
foundSigner = await verifyJWTDecoded({ header, payload, data, signature }, delegatedAuthenticator) | ||
} catch (e) { | ||
if (!(e as Error).message.startsWith('invalid_signature:')) throw e | ||
} | ||
} | ||
|
||
if (foundSigner) { | ||
return authenticator | ||
} | ||
|
||
throw new Error(`${JWT_ERROR.INVALID_SIGNATURE}: condition for authenticator ${authenticator.id} is not met.`) | ||
} |
Oops, something went wrong.