diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/validate-bls-session-sig.ts b/packages/lit-node-client-nodejs/src/lib/helpers/validate-bls-session-sig.ts new file mode 100644 index 0000000000..3201e29d87 --- /dev/null +++ b/packages/lit-node-client-nodejs/src/lib/helpers/validate-bls-session-sig.ts @@ -0,0 +1,40 @@ +import { AuthSig } from '@lit-protocol/types'; +import { uint8arrayToString } from '@lit-protocol/uint8arrays'; +import { ethers } from 'ethers'; + +const LIT_SESSION_SIGNED_MESSAGE_PREFIX = 'lit_session:'; + +/** + * Verifies a BLS session signature. + * + * @param {Function} verifier - A wasm function that takes a public key, message, and signature to verify. NOTE: `public_key` is snake cased because it's a wasm parameter + * @param {string} networkPubKey - The public key of the network. + * @param {AuthSig} authSig + * @typedef {Object} AuthSig + * @property {string} sig - The signature in string format. + * @property {string} signedMessage - The message that was signed. + */ +export const blsSessionSigVerify = ( + // TODO: refactor type with merger of PR 'https://github.com/LIT-Protocol/js-sdk/pull/503` + verifier: (public_key: any, message: any, signature: any) => void, + networkPubKey: string, + authSig: AuthSig +): void => { + let sigJson = JSON.parse(authSig.sig); + // we do not nessesarly need to use ethers here but was a quick way + // to get verification working. + const eip191Hash = ethers.utils.hashMessage(authSig.signedMessage); + const prefixedStr = + LIT_SESSION_SIGNED_MESSAGE_PREFIX + eip191Hash.replace('0x', ''); + const prefixedEncoded = ethers.utils.toUtf8Bytes(prefixedStr); + const shaHashed = ethers.utils.base64.encode( + ethers.utils.sha256(prefixedEncoded) + ); + const signatureBytes = Buffer.from(sigJson.ProofOfPossession, `hex`); + + verifier( + networkPubKey, + shaHashed, + uint8arrayToString(signatureBytes, `base64`) + ); +}; diff --git a/packages/lit-node-client-nodejs/src/lib/helpers/validate-bls-session-sigs.spec.ts b/packages/lit-node-client-nodejs/src/lib/helpers/validate-bls-session-sigs.spec.ts new file mode 100644 index 0000000000..f1563339e8 --- /dev/null +++ b/packages/lit-node-client-nodejs/src/lib/helpers/validate-bls-session-sigs.spec.ts @@ -0,0 +1,41 @@ +import { blsSessionSigVerify } from './validate-bls-session-sig'; + +describe('BlsSessionSigVerify', () => { + const authSig = { + sig: '{"ProofOfPossession":"ae925162cecb2f572fa76b93372dbbaee0133e89987c33d3210e0d62ca2dd5bf080dbdabb0155e61e770be1a2a629861073acc58fbc16cb6b700088d2aff114c42337c6123c8d15eeee63b522ea7d9c8f44390d3cb7b26e8d4935a283fe72a5d"}', + algo: 'LIT_BLS', + derivedVia: 'lit.bls', + signedMessage: + 'litprotocol.com wants you to sign in with your Ethereum account:\n' + + '0xf087a967D9eA9445D9182692C2944DcC0Af57341\n' + + '\n' + + "Lit Protocol PKP session signature I further authorize the stated URI to perform the following actions on my behalf: I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'. I further authorize the stated URI to perform the following actions on my behalf: (1) 'Threshold': 'Execution' for 'lit-litaction://*'. (2) 'Threshold': 'Signing' for 'lit-pkp://*'. (3) 'Auth': 'Auth' for 'lit-resolvedauthcontext://*'.\n" + + '\n' + + 'URI: lit:session:efebafcc9063827a49dffdb11c36b2d64a33330631ac7f5825e2960946bcc8ff\n' + + 'Version: 1\n' + + 'Chain ID: 1\n' + + 'Nonce: 0x1f623ab8dfe6bbd3b3dc22c7a041deb697c14817bce471b1bd1d86a25d5a319c\n' + + 'Issued At: 2024-06-11T15:55:23Z\n' + + 'Expiration Time: 2024-06-12T15:55:47.655Z\n' + + 'Resources:\n' + + '- urn:recap:eyJhdHQiOnsibGl0LWxpdGFjdGlvbjovLyoiOnsiVGhyZXNob2xkL0V4ZWN1dGlvbiI6W3t9XX0sImxpdC1wa3A6Ly8qIjp7IlRocmVzaG9sZC9TaWduaW5nIjpbe31dfSwibGl0LXJlc29sdmVkYXV0aGNvbnRleHQ6Ly8qIjp7IkF1dGgvQXV0aCI6W3siYXV0aF9jb250ZXh0Ijp7ImFjdGlvbklwZnNJZHMiOlsiUW1ZM3F1bjlxWDNmVUJIVmZyQTlmM3Y5UnB5eVBvOFJIRXVFTjFYWVBxMVByQSJdLCJhdXRoTWV0aG9kQ29udGV4dHMiOlt7ImFwcElkIjoibGl0IiwiYXV0aE1ldGhvZFR5cGUiOjEsImV4cGlyYXRpb24iOjE3MTgyMDc3MzgsInVzZWRGb3JTaWduU2Vzc2lvbktleVJlcXVlc3QiOnRydWUsInVzZXJJZCI6IjB4NjEwM2U1MGUyQzA0OWM5MjgxNEE1Mjc1YURDZDlBNzE2NjY3OTUxZSJ9XSwiYXV0aFNpZ0FkZHJlc3MiOm51bGwsImN1c3RvbUF1dGhSZXNvdXJjZSI6InRydWUiLCJyZXNvdXJjZXMiOltdfX1dfX0sInByZiI6W119', + address: '0xf087a967D9eA9445D9182692C2944DcC0Af57341', + }; + + let networkPubKey = + 'a43499a4b786da2dd28af9f209eb152ff6f646b34b68a02954967271e17fb4c511fd67b81e067f690c6f38acab70585d'; + + it(`should verify valid bls signatrue`, () => { + expect( + blsSessionSigVerify( + (public_key: any, message: any, signature: any) => { + expect(typeof public_key).toBe('string'); + expect(typeof message).toBe('string'); + expect(typeof signature).toBe('string'); + }, + networkPubKey, + authSig + ) + ).toBeUndefined(); + }); +}); diff --git a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts index 5ff3ff91b6..b36ea0f048 100644 --- a/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts +++ b/packages/lit-node-client-nodejs/src/lib/lit-node-client-nodejs.ts @@ -126,6 +126,7 @@ import { normalizeArray } from './helpers/normalize-array'; import { parsePkpSignResponse } from './helpers/parse-pkp-sign-response'; import { getBlsSignatures } from './helpers/get-bls-signatures'; import { processLitActionResponseStrategy } from './helpers/process-lit-action-response-strategy'; +import { blsSessionSigVerify } from './helpers/validate-bls-session-sig'; export class LitNodeClientNodeJs extends LitCore @@ -512,17 +513,38 @@ export class LitNodeClientNodeJs resourceAbilityRequests: LitResourceAbilityRequest[]; }): Promise => { const authSigSiweMessage = new SiweMessage(authSig.signedMessage); - - try { - await authSigSiweMessage.validate(authSig.sig); - } catch (e) { - console.debug('Need retry because verify failed', e); - return true; + // We will either have `ed25519` or `LIT_BLS` as we have deviated from the specification of SIWE and use BLS signatures in some cases + // Here we need to check the `algo` of the SIWE to confirm we can validate the signature as if we attempt to validate the BLS signature here + // it will fail. If the algo is not defined we can assume that it was an EOA wallet signing the message so we can use SIWE. + if (authSig.algo === `ed25519` || authSig.algo === undefined) { + try { + await authSigSiweMessage.validate(authSig.sig); + } catch (e) { + log(`Error while verifying ECDSA signature: `, e); + return true; + } + } else if (authSig.algo === `LIT_BLS`) { + try { + blsSessionSigVerify( + blsSdk.verify_signature, + this.networkPubKey!, + authSig + ); + } catch (e) { + log(`Error while verifying bls signature: `, e); + return true; + } + } else { + throwError({ + message: `Unsupported signature algo for session signature. Expected ed25519 or LIT_BLS recieved ${authSig.algo}`, + errorKind: LIT_ERROR.SIGNATURE_VALIDATION_ERROR.kind, + errorCode: LIT_ERROR.SIGNATURE_VALIDATION_ERROR.code, + }); } // make sure the sig is for the correct session key if (authSigSiweMessage.uri !== sessionKeyUri) { - console.debug('Need retry because uri does not match'); + log('Need retry because uri does not match'); return true; } @@ -531,7 +553,7 @@ export class LitNodeClientNodeJs !authSigSiweMessage.resources || authSigSiweMessage.resources.length === 0 ) { - console.debug('Need retry because empty resources'); + log('Need retry because empty resources'); return true; } @@ -550,7 +572,7 @@ export class LitNodeClientNodeJs resourceAbilityRequest.ability ) ) { - console.debug('Need retry because capabilities do not match', { + log('Need retry because capabilities do not match', { authSigSessionCapabilityObject, resourceAbilityRequest, }); @@ -1881,6 +1903,7 @@ export class LitNodeClientNodeJs log(`[signSessionKey] signatureShares:`, signatureShares); + // TODO: refactor type with merger of PR 'https://github.com/LIT-Protocol/js-sdk/pull/503` const blsCombinedSignature = blsSdk.combine_signature_shares( signatureShares.map((s) => JSON.stringify(s)) );