11 changed files with 181 additions and 97 deletions
			
			
		@ -0,0 +1,106 @@ | 
				
			|||||
 | 
					/** | 
				
			||||
 | 
					 * Verifiable Credential & DID functions, specifically for EndorserSearch.org tools | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					import * as didJwt from "did-jwt"; | 
				
			||||
 | 
					import { IIdentifier } from "@veramo/core"; | 
				
			||||
 | 
					import * as u8a from "uint8arrays"; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					import { createDidPeerJwt } from "@/libs/crypto/vc/passkeyDidPeer"; | 
				
			||||
 | 
					import {JWTDecoded} from "did-jwt/lib/JWT"; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Meta info about a key | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export interface KeyMeta { | 
				
			||||
 | 
					  /** | 
				
			||||
 | 
					   * Decentralized ID for the key | 
				
			||||
 | 
					   */ | 
				
			||||
 | 
					  did: string; | 
				
			||||
 | 
					  /** | 
				
			||||
 | 
					   * Stringified IIDentifier object from Veramo | 
				
			||||
 | 
					   */ | 
				
			||||
 | 
					  identity?: string; | 
				
			||||
 | 
					  /** | 
				
			||||
 | 
					   * The Webauthn credential ID in hex, if this is from a passkey | 
				
			||||
 | 
					   */ | 
				
			||||
 | 
					  passkeyCredIdHex?: string; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 * Tell whether a key is from a passkey | 
				
			||||
 | 
					 * @param keyMeta contains info about the key, whose passkeyCredIdHex determines if the key is from a passkey | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					export function isFromPasskey(keyMeta?: KeyMeta): boolean { | 
				
			||||
 | 
					  return !!keyMeta?.passkeyCredIdHex; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export async function createEndorserJwtForKey( | 
				
			||||
 | 
					  account: KeyMeta, | 
				
			||||
 | 
					  payload: object, | 
				
			||||
 | 
					) { | 
				
			||||
 | 
					  if (account?.identity) { | 
				
			||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | 
				
			||||
 | 
					    const identity: IIdentifier = JSON.parse(account.identity!); | 
				
			||||
 | 
					    const privateKeyHex = identity.keys[0].privateKeyHex; | 
				
			||||
 | 
					    const signer = await SimpleSigner(privateKeyHex as string); | 
				
			||||
 | 
					    return didJwt.createJWT(payload, { | 
				
			||||
 | 
					      issuer: account.did, | 
				
			||||
 | 
					      signer: signer, | 
				
			||||
 | 
					    }); | 
				
			||||
 | 
					  } else if (account?.passkeyCredIdHex) { | 
				
			||||
 | 
					    return createDidPeerJwt(account.did, account.passkeyCredIdHex, payload); | 
				
			||||
 | 
					  } else { | 
				
			||||
 | 
					    throw new Error("No identity data found to sign for DID " + account.did); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					/** | 
				
			||||
 | 
					 *  Copied out of did-jwt since it's deprecated in that library. | 
				
			||||
 | 
					 * | 
				
			||||
 | 
					 *  The SimpleSigner returns a configured function for signing data. | 
				
			||||
 | 
					 * | 
				
			||||
 | 
					 *  @example | 
				
			||||
 | 
					 *  const signer = SimpleSigner(import.meta.env.PRIVATE_KEY) | 
				
			||||
 | 
					 *  signer(data, (err, signature) => { | 
				
			||||
 | 
					 *    ... | 
				
			||||
 | 
					 *  }) | 
				
			||||
 | 
					 * | 
				
			||||
 | 
					 *  @param    {String}         hexPrivateKey    a hex encoded private key | 
				
			||||
 | 
					 *  @return   {Function}                     a configured signer function | 
				
			||||
 | 
					 */ | 
				
			||||
 | 
					function SimpleSigner(hexPrivateKey: string): didJwt.Signer { | 
				
			||||
 | 
					  const signer = didJwt.ES256KSigner(didJwt.hexToBytes(hexPrivateKey), true); | 
				
			||||
 | 
					  return async (data) => { | 
				
			||||
 | 
					    const signature = (await signer(data)) as string; | 
				
			||||
 | 
					    return fromJose(signature); | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// from did-jwt/util; see SimpleSigner above
 | 
				
			||||
 | 
					function fromJose(signature: string): { | 
				
			||||
 | 
					  r: string; | 
				
			||||
 | 
					  s: string; | 
				
			||||
 | 
					  recoveryParam?: number; | 
				
			||||
 | 
					} { | 
				
			||||
 | 
					  const signatureBytes: Uint8Array = didJwt.base64ToBytes(signature); | 
				
			||||
 | 
					  if (signatureBytes.length < 64 || signatureBytes.length > 65) { | 
				
			||||
 | 
					    throw new TypeError( | 
				
			||||
 | 
					      `Wrong size for signature. Expected 64 or 65 bytes, but got ${signatureBytes.length}`, | 
				
			||||
 | 
					    ); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					  const r = bytesToHex(signatureBytes.slice(0, 32)); | 
				
			||||
 | 
					  const s = bytesToHex(signatureBytes.slice(32, 64)); | 
				
			||||
 | 
					  const recoveryParam = | 
				
			||||
 | 
					    signatureBytes.length === 65 ? signatureBytes[64] : undefined; | 
				
			||||
 | 
					  return {r, s, recoveryParam}; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					// from did-jwt/util; see SimpleSigner above
 | 
				
			||||
 | 
					function bytesToHex(b: Uint8Array): string { | 
				
			||||
 | 
					  return u8a.toString(b, "base16"); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export function decodeEndorserJwt(jwt: string): JWTDecoded { | 
				
			||||
 | 
					  return didJwt.decodeJWT(jwt); | 
				
			||||
 | 
					} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue