From ced0f47b59425d653487ff164a1336dcc85f8496 Mon Sep 17 00:00:00 2001 From: Nayyir Jutha Date: Wed, 8 Jan 2025 12:28:54 -0500 Subject: [PATCH] [NayNay] Verify signatures - added new verify command to verify signatures - updated result of sign command to be stringified json making it easier to utilize verify command immediately after signing - added tests --- package.json | 2 +- src/cli.ts | 2 + src/common/load-entropy.ts | 2 +- src/faucet/helpers/signer.ts | 10 ++--- src/sign/command.ts | 18 ++------- src/sign/interaction.ts | 32 ++-------------- src/sign/main.ts | 43 +++------------------- src/sign/types.ts | 13 ++++--- src/sign/utils.ts | 22 +---------- src/verify/command.ts | 23 ++++++++++++ src/verify/main.ts | 15 ++++++++ tests/verify.test.ts | 30 +++++++++++++++ yarn.lock | 71 +++++++++++++++++++++++++++++++----- 13 files changed, 161 insertions(+), 122 deletions(-) create mode 100644 src/verify/command.ts create mode 100644 src/verify/main.ts create mode 100644 tests/verify.test.ts diff --git a/package.json b/package.json index fe01309d..3559b74b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ }, "homepage": "https://github.com/entropyxyz/cli#readme", "dependencies": { - "@entropyxyz/sdk": "0.4.0", + "@entropyxyz/sdk": "0.4.1-2", "ajv": "^8.17.1", "commander": "^12.1.0", "env-paths": "^3.0.0", diff --git a/src/cli.ts b/src/cli.ts index d9f55578..a432a5c9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,6 +12,7 @@ import { entropyTransferCommand as transfer } from './transfer/command' import { entropySignCommand as sign } from './sign/command' import { entropyBalanceCommand as balance } from './balance/command' import { entropyProgramCommand as program } from './program/command' +import { entropyVerifyCommand as verify } from './verify/command' const packageVersion = 'v' + require('../package.json').version const coreVersion = process.env.ENTROPY_CORE_VERSION.split('-')[1] @@ -29,6 +30,7 @@ cli .addCommand(balance()) .addCommand(transfer()) .addCommand(program()) + .addCommand(verify()) .option('-v, --version', 'Displays the current running version of Entropy CLI') .option('-cv, --core-version', 'Displays the current running version of the Entropy Protocol') diff --git a/src/common/load-entropy.ts b/src/common/load-entropy.ts index 230490dd..fdd77f89 100644 --- a/src/common/load-entropy.ts +++ b/src/common/load-entropy.ts @@ -117,7 +117,7 @@ async function loadEntropy (opts: LoadEntropyOpts): Promise { const storedConfig = await opts.config.get() if (!storedConfig) throw Error('no config!!') // TEMP: want to see if we hit this! - let account = resolveAccount(storedConfig, opts.account) + let account = resolveAccount(storedConfig, opts.account || storedConfig.selectedAccount) const endpoint = resolveEndpoint(storedConfig, opts.endpoint) // NOTE: while it would be nice to parse opts --account, --endpoint with Commander // the argParser for these Options does not have access to the --config option, diff --git a/src/faucet/helpers/signer.ts b/src/faucet/helpers/signer.ts index dd86e47a..d468d3ef 100644 --- a/src/faucet/helpers/signer.ts +++ b/src/faucet/helpers/signer.ts @@ -4,6 +4,7 @@ import { Registry, SignerPayloadJSON } from "@polkadot/types/types"; import { u8aToHex } from "@polkadot/util"; import { blake2AsHex, decodeAddress, encodeAddress, signatureVerify } from "@polkadot/util-crypto"; import { stripHexPrefix } from "../../common/utils"; +import { HexString } from "@polkadot/util/types"; let id = 0 export default class FaucetSigner implements Signer { @@ -40,18 +41,17 @@ export default class FaucetSigner implements Signer { amount: this.amount } - const signature = await this.#entropy.sign({ - sigRequestHash: u8aToHex(raw), + const { signature } = await this.#entropy.sign({ + hexMessage: u8aToHex(raw), // @ts-ignore hash: { custom: 0 }, // NOTE: this is the custom hashing algo used for the faucet program. auxiliaryData: [auxData], signatureVerifyingKey: this.chosenVerifyingKey }) - let sigHex = u8aToHex(signature); // the 02 prefix is needed for signature type edcsa (00 = ed25519, 01 = sr25519, 02 = ecdsa) // ref: https://github.com/polkadot-js/tools/issues/175#issuecomment-767496439 - sigHex = `0x02${stripHexPrefix(sigHex)}` + const sigHex = `0x02${stripHexPrefix(signature)}` const hashedKey = blake2AsHex(this.chosenVerifyingKey) const faucetAddress = encodeAddress(hashedKey) @@ -62,7 +62,7 @@ export default class FaucetSigner implements Signer { const signatureValidation = signatureVerify(u8aToHex(raw), sigHex, hexPublicKey) if (signatureValidation.isValid) { - return { id: id++, signature: sigHex } + return { id: id++, signature: sigHex as HexString } } else { throw new Error('FaucetSignerError: Signature is not valid') } diff --git a/src/sign/command.ts b/src/sign/command.ts index 3dc19fd3..4959738a 100644 --- a/src/sign/command.ts +++ b/src/sign/command.ts @@ -3,6 +3,7 @@ import { Command, /* Option */ } from 'commander' import { EntropySign } from './main' import { accountOption, configOption, endpointOption, cliWrite } from '../common/utils-cli' import { loadEntropyCli } from '../common/load-entropy' +import { stringify } from 'src/common/utils' export function entropySignCommand () { const signCommand = new Command('sign') @@ -11,23 +12,12 @@ export function entropySignCommand () { .addOption(accountOption()) .addOption(configOption()) .addOption(endpointOption()) - // .addOption( - // new Option( - // '-r, --raw', - // 'Signs the provided message using the Raw Signing method. Output is a signature (string)' - // ) - // ) .action(async (msg, opts) => { const entropy = await loadEntropyCli(opts) const SigningService = new EntropySign(entropy, opts.endpoint) - // TO-DO: Add ability for raw signing here, maybe? new raw option can be used for the conditional - /** - * if (opts.raw) { - * implement raw sign here - * } - */ - const { verifyingKey, signature } = await SigningService.signMessageWithAdapters({ msg }) - cliWrite({ verifyingKey, signature }) + + const signatureData = await SigningService.signMessageWithAdapters({ msg }) + cliWrite(stringify(signatureData, 0)) process.exit(0) }) return signCommand diff --git a/src/sign/interaction.ts b/src/sign/interaction.ts index e2ac5ebe..4fbddc98 100644 --- a/src/sign/interaction.ts +++ b/src/sign/interaction.ts @@ -8,43 +8,19 @@ import { EntropyTuiOptions } from '../types' export async function entropySign (entropy: Entropy, opts: EntropyTuiOptions) { const signingService = new EntropySign(entropy, opts.endpoint) - // const { interactionChoice } = await inquirer.prompt(interactionChoiceQuestions) - // switch (interactionChoice) { - // case 'Raw Sign': { - // const { msg, msgPath } = await getMsgFromUser(inquirer) - // const { hashingAlgorithm, auxiliaryDataFile } = await inquirer.prompt(rawSignParamsQuestions) - // let hash = hashingAlgorithm - // const auxiliaryData = JSON.parse(readFileSync(auxiliaryDataFile).toString()) - // if (JSON.parse(hashingAlgorithm)) { - // hash = JSON.parse(hashingAlgorithm) - // } - - // const { signature, verifyingKey } = await Sign.rawSignMessage({ msg, msgPath, hashingAlgorithm: hash, auxiliaryData }) - // print('msg to be signed:', msg) - // print('verifying key:', verifyingKey) - // print('signature:', signature) - // return - // } - // case 'Sign With Adapter': { try { const { msg } = await getMsgFromUser(inquirer) - const { signature, verifyingKey } = await signingService.signMessageWithAdapters({ msg }) - print('msg to be signed:', msg) + const { message, signature, verifyingKey, hashType } = await signingService.signMessageWithAdapters({ msg }) + print('msg signed:', message) print('verifying key:', verifyingKey) + print('hashing algorithm:', hashType) print('signature:', signature) return } catch (error) { if (!entropy.signingManager.verifyingKey) { - console.error('Please register your Entropy account before signing'); + print.error('Please register your Entropy account before signing') return 'exit' } throw error } - // return - // } - // case 'Exit to Main Menu': - // return 'exit' - // default: - // throw new Error('Unrecognizable action') - // } } diff --git a/src/sign/main.ts b/src/sign/main.ts index e2210043..86c443e6 100644 --- a/src/sign/main.ts +++ b/src/sign/main.ts @@ -1,7 +1,6 @@ import Entropy from "@entropyxyz/sdk" -import { u8aToHex } from '@polkadot/util' import { EntropyBase } from "../common/entropy-base"; -import { SignResult } from "./types"; +import { SignatureData } from "./types"; import { FLOW_CONTEXT } from "./constants"; import { stringToHex } from "./utils"; @@ -10,39 +9,11 @@ export class EntropySign extends EntropyBase { super({ entropy, endpoint, flowContext: FLOW_CONTEXT }) } - // async rawSign (entropy: Entropy, payload: RawSignPayload) { - // return entropy.sign(payload) - // } - - // async rawSignMessage ({ msg, msgPath, auxiliaryData, hashingAlgorithm }): Promise { - // const message = getMsgFromInputOrFile(msg, msgPath) - - // try { - // this.logger.log(`Msg to be signed: ${msg}`, 'SIGN_MSG') - // this.logger.log( `Verifying Key used: ${this.entropy.signingManager.verifyingKey}`) - // const signature = await rawSign( - // this.entropy, - // { - // sigRequestHash: stringAsHex(message), - // hash: hashingAlgorithm, - // auxiliaryData - // } - // ) - // const signatureHexString = u8aToHex(signature) - // this.logger.log(`Signature: ${signatureHexString}`) - - // return { signature: signatureHexString, verifyingKey: this.entropy.signingManager.verifyingKey } - // } catch (error) { - // this.logger.error('Error signing message', error) - // throw error - // } - // } - - async signMessageWithAdapters ({ msg }: { msg: string }): Promise { + async signMessageWithAdapters ({ msg }: { msg: string }): Promise { try { this.logger.log(`Msg to be signed: ${msg}`, 'SIGN_MSG') this.logger.log( `Verifying Key used: ${this.entropy.signingManager.verifyingKey}`) - const signature: any = await this.entropy.signWithAdaptersInOrder({ + const signatureData: any = await this.entropy.signWithAdaptersInOrder({ msg: { msg: stringToHex(msg) }, @@ -52,13 +23,9 @@ export class EntropySign extends EntropyBase { // auxillaryData }) - const signatureHexString = u8aToHex(signature) - this.logger.log(`Signature: ${signatureHexString}`) + this.logger.log(`Signature: ${signatureData.signature}`) - return { - signature: signatureHexString, - verifyingKey: this.entropy.signingManager.verifyingKey - } + return signatureData } catch (error) { this.logger.error('Error signing message', error) throw error diff --git a/src/sign/types.ts b/src/sign/types.ts index 4e222b82..a0be7c29 100644 --- a/src/sign/types.ts +++ b/src/sign/types.ts @@ -1,11 +1,14 @@ -export interface SignResult { - signature: string - verifyingKey: string -} - export interface RawSignPayload { sigRequestHash: string hash: any auxiliaryData: any signatureVerifyingKey?: string } + +// TODO: export type from sdk +export interface SignatureData { + signature: string + verifyingKey: string + hashType: string + message: string // hex string as bytes? +} \ No newline at end of file diff --git a/src/sign/utils.ts b/src/sign/utils.ts index 0b63020d..4f0bdf44 100644 --- a/src/sign/utils.ts +++ b/src/sign/utils.ts @@ -51,31 +51,11 @@ export const rawSignParamsQuestions = [ ] export async function getMsgFromUser (inquirer) { - // let msg: string - // let msgPath: string - // const { messageAction } = await inquirer.prompt(messageActionQuestions) - // switch (messageAction) { - // case 'Text Input': { const { userInput } = await inquirer.prompt(userInputQuestions) const msg = userInput - // break - // } - // Msg input from a file requires more design - // case 'From a File': { - // const { pathToFile } = await inquirer.prompt(filePathInputQuestions) - // // TODO: relative/absolute path? encoding? - // msgPath = pathToFile - // break - // } - // default: { - // const error = new Error('SigningError: Unsupported User Input Action') - // this.logger.error('Error signing with adapter', error) - // return - // } - // } + return { msg, - // msgPath }; } diff --git a/src/verify/command.ts b/src/verify/command.ts new file mode 100644 index 00000000..f85b014d --- /dev/null +++ b/src/verify/command.ts @@ -0,0 +1,23 @@ +import { Command, /* Option */ } from 'commander' + +import { EntropyVerify } from './main' +import { accountOption, configOption, endpointOption, cliWrite } from '../common/utils-cli' +import { loadEntropyCli } from '../common/load-entropy' + +export function entropyVerifyCommand () { + const verifyCommand = new Command('verify') + .description('Verify a signture. Output is a boolean') + .argument('', 'Signature data returned from signing method') + .addOption(accountOption()) + .addOption(configOption()) + .addOption(endpointOption()) + .action(async (signatureData, opts) => { + const entropy = await loadEntropyCli(opts) + const VerifyService = new EntropyVerify(entropy, opts.endpoint) + + const isVerified = await VerifyService.verify(JSON.parse(signatureData)) + cliWrite({ isSignatureVerified: isVerified }) + process.exit(0) + }) + return verifyCommand +} diff --git a/src/verify/main.ts b/src/verify/main.ts new file mode 100644 index 00000000..1b103913 --- /dev/null +++ b/src/verify/main.ts @@ -0,0 +1,15 @@ +import Entropy from "@entropyxyz/sdk"; +import { EntropyBase } from "src/common/entropy-base"; +import { SignatureData } from "src/sign/types"; + +const FLOW_CONTEXT = 'ENTROPY_VERIFY' + +export class EntropyVerify extends EntropyBase { + constructor (entropy: Entropy, endpoint: string) { + super({ entropy, endpoint, flowContext: FLOW_CONTEXT }) + } + + async verify (signatureData: SignatureData): Promise { + return this.entropy.verify(signatureData) + } +} \ No newline at end of file diff --git a/tests/verify.test.ts b/tests/verify.test.ts new file mode 100644 index 00000000..02e97458 --- /dev/null +++ b/tests/verify.test.ts @@ -0,0 +1,30 @@ +import test from 'tape' + +import { EntropySign } from '../src/sign/main' +import { EntropyVerify } from '../src/verify/main' +import { setupTest, eveSeed } from './testing-utils' + +const endpoint = 'ws://127.0.0.1:9944' + +test('Verify Signature', async (t) => { + const { run, entropy } = await setupTest(t, { seed: eveSeed }) + const signService = new EntropySign(entropy, endpoint) + const verifyService = new EntropyVerify(entropy, endpoint) + + await run('register', entropy.register()) + const result = await run( + 'sign', + signService.signMessageWithAdapters({ msg: "heyo!" }) + ) + + t.true(result?.signature?.length > 32, 'signature has some body!') + + const isVerified = await run( + 'verify signature', + verifyService.verify(result) + ) + + t.true(isVerified, 'signature is verified') + + t.end() +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c4156bc4..ede3d1ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + "@colors/colors@1.6.0", "@colors/colors@^1.6.0": version "1.6.0" resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz" @@ -26,10 +31,10 @@ resolved "https://registry.npmjs.org/@entropyxyz/entropy-protocol-web/-/entropy-protocol-web-0.2.0.tgz" integrity sha512-lLa/lLNJnwH1R8fJvLlUn1kw7d4Rbnt9LjhUC69HKxkU69J+bw/EY6fAjBnpVbgNmqCnYpf/DBLtMyOayZeNDQ== -"@entropyxyz/sdk@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@entropyxyz/sdk/-/sdk-0.4.0.tgz#92ae53b19fe9630584cc1a4a40122b8a5142b003" - integrity sha512-WL6GzMROqBDuyC5KDQxRV68hG3MWjgsifcXD0zoL75tgSi4kJGfMgPvwHtO+kCg9aYKEaLFVdpyaOU4bDkl41w== +"@entropyxyz/sdk@0.4.1-2": + version "0.4.1-2" + resolved "https://registry.yarnpkg.com/@entropyxyz/sdk/-/sdk-0.4.1-2.tgz#95d700b8084185bfd5e766798e1618f0683445c5" + integrity sha512-Q4bbDq4IIBw23AKwv1Y/k7EPyy0DmkSIOrG9eKKEC6AVvy5zP1rjrI6HIaWi72CgtwmOqDdNwQM/S14T5RxQSg== dependencies: "@entropyxyz/entropy-protocol-nodejs" "^0.2.0" "@entropyxyz/entropy-protocol-web" "^0.2.0" @@ -37,6 +42,7 @@ "@types/lodash.clonedeep" "^4.5.9" "@types/node" "^20.12.12" debug "^4.3.4" + ethers "^6.13.4" hpke-js "^1.2.7" lodash.clonedeep "^4.5.0" uuid "^9.0.1" @@ -382,6 +388,13 @@ resolved "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz" integrity sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg== +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/curves@1.3.0": version "1.3.0" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz" @@ -396,6 +409,11 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@1.3.3": version "1.3.3" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz" @@ -989,6 +1007,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + "@types/semver@^7.5.0": version "7.5.8" resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz" @@ -1115,6 +1140,11 @@ acorn@^8.9.0: resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" @@ -1878,6 +1908,19 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +ethers@^6.13.4: + version "6.13.5" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.5.tgz#8c1d6ac988ac08abc3c1d8fabbd4b8b602851ac4" + integrity sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "22.7.5" + aes-js "4.0.0-beta.5" + tslib "2.7.0" + ws "8.17.1" + eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" @@ -3719,6 +3762,11 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" @@ -3845,6 +3893,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" @@ -3983,16 +4036,16 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@8.17.1, ws@^8.8.1: + version "8.17.1" + resolved "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + ws@^8.16.0: version "8.18.0" resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -ws@^8.8.1: - version "8.17.1" - resolved "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz" - integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== - x25519@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/x25519/-/x25519-0.1.0.tgz"