From 79478a0d536ba477042c194679caf55d0857e81e Mon Sep 17 00:00:00 2001 From: edis Date: Wed, 15 Jan 2025 17:23:54 +0100 Subject: [PATCH] feat: implement SDJwt.service --- packages/snap/package.json | 1 + packages/snap/snap.manifest.json | 2 +- packages/snap/src/SDJwt.service.ts | 109 +++++++++++++++++++++++++++++ packages/snap/src/index.ts | 3 + pnpm-lock.yaml | 29 +++----- 5 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 packages/snap/src/SDJwt.service.ts diff --git a/packages/snap/package.json b/packages/snap/package.json index 767dd28b2..c6fda366a 100644 --- a/packages/snap/package.json +++ b/packages/snap/package.json @@ -73,6 +73,7 @@ "@metamask/utils": "9.3.0", "@sd-jwt/core": "^0.7.2", "@sd-jwt/crypto-nodejs": "^0.7.2", + "@sd-jwt/sd-jwt-vc": "^0.8.0", "@sd-jwt/types": "^0.7.2", "@veramo/core": "6.0.0", "@veramo/credential-eip712": "6.0.0", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index c7e93af7e..056b2c3ac 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -26,7 +26,7 @@ "./files/circuits/credentialAtomicQuerySigV2/circuit_final.zkey", "./files/circuits/credentialAtomicQuerySigV2/verification_key.json" ], - "shasum": "3JymcCbtQUTtucVLSbE0pEXzzXiF6kvqaYlyxvbc6gc=" + "shasum": "nHpRp4CVHapl7TO0A6ImnRO0u3T/y4sPghawmRrfAFo=" }, "initialPermissions": { "endowment:ethereum-provider": {}, diff --git a/packages/snap/src/SDJwt.service.ts b/packages/snap/src/SDJwt.service.ts new file mode 100644 index 000000000..bacfd1392 --- /dev/null +++ b/packages/snap/src/SDJwt.service.ts @@ -0,0 +1,109 @@ +import { SDJwtInstance } from '@sd-jwt/core'; +import WalletService from './Wallet.service'; +import { digest, generateSalt } from '@sd-jwt/crypto-nodejs'; +import { ec as EC } from 'elliptic'; +import crypto from 'node:crypto'; + +type SdJwtPayload = Record; + +class SDJwtService { + static signer: any; + static verifier: any; + static instance: SDJwtInstance; + + /** + * Helper function to encode in Base64 URL-safe format. + */ + private static toBase64Url(buffer: Buffer): string { + return buffer + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); + } + + /** + * Initializes the SDJwtService. + * + * This method sets up the SDJwtService by retrieving the wallet from the WalletService, + * extracting the private key, and creating the key pair. It also sets up the signer and + * verifier functions for the SDJwtService instance. + * + * @throws {Error} If the wallet cannot be retrieved or keys are missing. + * + * @returns {Promise} A promise that resolves when the initialization is complete. + */ + static async init(): Promise { + try { + const wallet = WalletService.get(); + + if (!wallet) { + throw new Error('Failed to retrieve keys'); + } + + const privateKeyHex = wallet.privateKey.slice(2); // Remove '0x' prefix + + const ec = new EC('secp256k1'); + const keyPair = ec.keyFromPrivate(privateKeyHex, 'hex'); + const publicKey = keyPair.getPublic(); + + if (!keyPair || !publicKey) { + throw new Error('Keys are missing from WalletService'); + } + + SDJwtService.signer = async (data: string): Promise => { + const hash = crypto.createHash('sha256').update(data).digest(); + const signature = keyPair.sign(hash); + return SDJwtService.toBase64Url( + Buffer.concat([ + Buffer.from(signature.r.toArray('be', 32)), + Buffer.from(signature.s.toArray('be', 32)), + ]) + ); + }; + + SDJwtService.verifier = async ( + data: string, + signatureBase64Url: string + ): Promise => { + const hash = crypto.createHash('sha256').update(data).digest(); + const signatureBuffer = Buffer.from(signatureBase64Url, 'base64url'); + const r = signatureBuffer.subarray(0, 32); + const s = signatureBuffer.subarray(32); + return keyPair.verify(hash, { r, s }); + }; + + SDJwtService.instance = new SDJwtInstance({ + signer: SDJwtService.signer + ? SDJwtService.signer + : async () => { + throw new Error('Signer not initialized'); + }, + verifier: SDJwtService.verifier + ? SDJwtService.verifier + : async () => { + throw new Error('Verifier not initialized'); + }, + signAlg: 'EdDSA', + hasher: digest, + hashAlg: 'SHA-256', + saltGenerator: generateSalt, + }); + } catch (e) { + console.error('Failed to initialize SDJwtService', e); + } + } + + /** + * Get the global SDJwtInstance + * @returns SDJwtInstance + */ + static getInstance(): SDJwtInstance { + if (!SDJwtService.instance) { + throw new Error('---> SDJwtService is not initialized'); + } + return SDJwtService.instance; + } +} + +export default SDJwtService; diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index 1acf91ffe..89ef01ad5 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -11,6 +11,7 @@ import UIService from './UI.service'; import WalletService from './Wallet.service'; import StorageService from './storage/Storage.service'; import VeramoService from './veramo/Veramo.service'; +import SDJwtService from './SDJwt.service'; export const onRpcRequest: OnRpcRequestHandler = async ({ request, @@ -33,6 +34,8 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ await WalletService.init(); + await SDJwtService.init(); + await VeramoService.init(); await EthereumService.init(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39d602a69..29fd13fc8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -855,6 +855,9 @@ importers: '@sd-jwt/crypto-nodejs': specifier: ^0.7.2 version: 0.7.2 + '@sd-jwt/sd-jwt-vc': + specifier: ^0.8.0 + version: 0.8.0 '@sd-jwt/types': specifier: ^0.7.2 version: 0.7.2 @@ -10128,6 +10131,7 @@ packages: eciesjs@0.3.18: resolution: {integrity: sha512-RQhegEtLSyIiGJmFTZfvCTHER/fymipXFVx6OwSRYD6hOuy+6Kjpk0dGvIfP9kxn/smBpxQy71uxpGO406ITCw==} + deprecated: Please upgrade to v0.4+ ed25519-signature-2018-context@1.1.0: resolution: {integrity: sha512-ppDWYMNwwp9bploq0fS4l048vHIq41nWsAbPq6H4mNVx9G/GxW3fwg4Ln0mqctP13MoEpREK7Biz8TbVVdYXqA==} @@ -26869,7 +26873,7 @@ snapshots: chalk: 4.1.2 execa: 5.1.1 metro: 0.80.12(bufferutil@4.0.8) - metro-config: 0.80.12(bufferutil@4.0.8) + metro-config: 0.80.12(bufferutil@4.0.8)(utf-8-validate@6.0.3) metro-core: 0.80.12 node-fetch: 2.7.0(encoding@0.1.13) querystring: 0.2.1 @@ -28654,8 +28658,8 @@ snapshots: '@veramo-community/lds-ecdsa-secp256k1-recovery2020@https://codeload.github.com/uport-project/EcdsaSecp256k1RecoverySignature2020/tar.gz/ab0db52de6f4e6663ef271a48009ba26e688ef9b': dependencies: '@bitauth/libauth': 1.19.1 - '@digitalcredentials/jsonld': 5.2.2(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3))(react-native@0.74.1(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(web-streams-polyfill@4.0.0) - '@digitalcredentials/jsonld-signatures': 9.4.0(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3))(react-native@0.74.1(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(web-streams-polyfill@4.0.0) + '@digitalcredentials/jsonld': 5.2.2(react-native@0.74.1(@babel/core@7.23.2)(@babel/preset-env@7.25.8(@babel/core@7.23.2))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)) + '@digitalcredentials/jsonld-signatures': 9.4.0(react-native@0.74.1(@babel/core@7.23.2)(@babel/preset-env@7.25.8(@babel/core@7.23.2))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)) '@ethersproject/transactions': 5.7.0 '@trust/keyto': 1.0.1 base64url: 3.0.1 @@ -28822,7 +28826,7 @@ snapshots: dependencies: '@digitalcredentials/ed25519-signature-2020': 4.0.0(expo-crypto@13.0.2(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)))(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3))(msrcrypto@1.5.8)(react-native-securerandom@1.0.1(react-native@0.74.1(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))) '@digitalcredentials/ed25519-verification-key-2020': 4.0.0 - '@digitalcredentials/jsonld': 6.0.0(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3))(react-native@0.74.1(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(web-streams-polyfill@4.0.0) + '@digitalcredentials/jsonld': 6.0.0(react-native@0.74.1(@babel/core@7.23.2)(@babel/preset-env@7.25.8(@babel/core@7.23.2))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)) '@digitalcredentials/jsonld-signatures': 10.0.1(expo-crypto@13.0.2(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)))(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3))(msrcrypto@1.5.8)(react-native-securerandom@1.0.1(react-native@0.74.1(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))) '@digitalcredentials/vc': 7.0.0(expo-crypto@13.0.2(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)))(expo@51.0.2(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3))(msrcrypto@1.5.8)(react-native-securerandom@1.0.1(react-native@0.74.1(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))) '@transmute/credentials-context': 0.7.0-unstable.81 @@ -35998,21 +36002,6 @@ snapshots: flow-enums-runtime: 0.0.6 metro-core: 0.80.12 - metro-config@0.80.12(bufferutil@4.0.8): - dependencies: - connect: 3.7.0 - cosmiconfig: 5.2.1 - flow-enums-runtime: 0.0.6 - jest-validate: 29.7.0 - metro: 0.80.12(bufferutil@4.0.8) - metro-cache: 0.80.12 - metro-core: 0.80.12 - metro-runtime: 0.80.12 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - metro-config@0.80.12(bufferutil@4.0.8)(utf-8-validate@6.0.3): dependencies: connect: 3.7.0 @@ -36170,7 +36159,7 @@ snapshots: metro-babel-transformer: 0.80.12 metro-cache: 0.80.12 metro-cache-key: 0.80.12 - metro-config: 0.80.12(bufferutil@4.0.8) + metro-config: 0.80.12(bufferutil@4.0.8)(utf-8-validate@6.0.3) metro-core: 0.80.12 metro-file-map: 0.80.12 metro-resolver: 0.80.12