From c652fb9f087d7cf0c682add0026a75f2e62e6e96 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Tue, 9 Jan 2024 14:20:47 +0100 Subject: [PATCH 01/14] feat(pactjs-generator): add emit-event to the parse tree --- .../src/contract/parsing/pactParser.ts | 1 + .../src/contract/parsing/utils/pactGrammar.ts | 1 + .../parsing/utils/tests/pactGrammar.test.ts | 13 +++++++++++++ 3 files changed, 15 insertions(+) diff --git a/packages/libs/pactjs-generator/src/contract/parsing/pactParser.ts b/packages/libs/pactjs-generator/src/contract/parsing/pactParser.ts index 7c058431d7..7900052c5c 100644 --- a/packages/libs/pactjs-generator/src/contract/parsing/pactParser.ts +++ b/packages/libs/pactjs-generator/src/contract/parsing/pactParser.ts @@ -36,6 +36,7 @@ export interface IFunction extends IMethod { bodyPointer?: number; requiredCapabilities?: string[]; withCapabilities?: string[]; + events?: string[]; functionCalls?: { internal: string[]; external: Array<{ namespace?: string; module: string; func: string }>; diff --git a/packages/libs/pactjs-generator/src/contract/parsing/utils/pactGrammar.ts b/packages/libs/pactjs-generator/src/contract/parsing/utils/pactGrammar.ts index 20e2218629..9abb82712d 100644 --- a/packages/libs/pactjs-generator/src/contract/parsing/utils/pactGrammar.ts +++ b/packages/libs/pactjs-generator/src/contract/parsing/utils/pactGrammar.ts @@ -86,6 +86,7 @@ const functionBody = seq( repeat( $('requiredCapabilities', seq(id('require-capability'), id('('), $(atom))), $('withCapabilities', seq(id('with-capability'), id('('), $(atom))), + $('events', seq(id('emit-event'), id('('), $(atom))), $( 'externalFnCalls', seq( diff --git a/packages/libs/pactjs-generator/src/contract/parsing/utils/tests/pactGrammar.test.ts b/packages/libs/pactjs-generator/src/contract/parsing/utils/tests/pactGrammar.test.ts index c4dd4db353..f2a7cf03aa 100644 --- a/packages/libs/pactjs-generator/src/contract/parsing/utils/tests/pactGrammar.test.ts +++ b/packages/libs/pactjs-generator/src/contract/parsing/utils/tests/pactGrammar.test.ts @@ -108,6 +108,19 @@ describe('pactGrammar', () => { expect(data.withCapabilities).toEqual(['CAP1', 'CAP2']); }); + it("should extract emit-event from the function's body", () => { + const pointer = getPointer( + '(defun test (a:integer b:boolean) @doc "test doc" (require-capability (CAP1)) (emit-event (EVENT_CAP)))', + ); + const result = defun(pointer); + const data = unwrapData(result); + if (data === FAILED) { + expect(data).not.toEqual(FAILED); + return; + } + expect(data.events).toEqual(['EVENT_CAP']); + }); + it("should extract external module function calls from the function's body", () => { const pointer = getPointer( '(defun test (a:integer b:boolean) @doc "test doc" (namespace1.mod1.func1 1) (mod2.func2 "test"))', From 3ed72cddf89adac01c805dceb857ae03cab4d5c3 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Tue, 9 Jan 2024 14:25:48 +0100 Subject: [PATCH 02/14] chore(pactjs-generator): changeset --- .changeset/pink-lobsters-retire.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pink-lobsters-retire.md diff --git a/.changeset/pink-lobsters-retire.md b/.changeset/pink-lobsters-retire.md new file mode 100644 index 0000000000..c33ccb06f8 --- /dev/null +++ b/.changeset/pink-lobsters-retire.md @@ -0,0 +1,5 @@ +--- +'@kadena/pactjs-generator': minor +--- + +Add events to the parse tree From 62aa9d676ddf5275cb493c6d679027e6d5bbc2bb Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Thu, 18 Jan 2024 17:34:05 +0100 Subject: [PATCH 03/14] WIP --- .../compatibility/kadenaGenKeypair.ts | 4 +- packages/libs/hd-wallet/src/utils/crypto.ts | 3 - .../libs/hd-wallet/src/utils/web-crypto.ts | 72 +++++++++++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 packages/libs/hd-wallet/src/utils/web-crypto.ts diff --git a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts index 51a4e3d9ca..625554d9a5 100644 --- a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts +++ b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts @@ -1,9 +1,11 @@ import type { EncryptedString } from '../../index.js'; import { kadenaDecrypt } from '../../index.js'; -import { HARDENED_OFFSET, harden } from '../../utils/crypto.js'; import { kadenaGenKeypair as kadenaGenKeypairOriginal } from '../kadena-crypto.js'; import { encryptLegacySecretKey } from './encryption.js'; +const HARDENED_OFFSET = 0x80000000; +const harden = (n: number) => HARDENED_OFFSET + n; + async function kadenaGenOneKeypair( password: string, rootKey: Uint8Array, diff --git a/packages/libs/hd-wallet/src/utils/crypto.ts b/packages/libs/hd-wallet/src/utils/crypto.ts index 8e3f85a5fb..bd5636c4bd 100644 --- a/packages/libs/hd-wallet/src/utils/crypto.ts +++ b/packages/libs/hd-wallet/src/utils/crypto.ts @@ -68,6 +68,3 @@ export function decrypt( return undefined; } } - -export const HARDENED_OFFSET = 0x80000000; -export const harden = (n: number) => HARDENED_OFFSET + n; diff --git a/packages/libs/hd-wallet/src/utils/web-crypto.ts b/packages/libs/hd-wallet/src/utils/web-crypto.ts new file mode 100644 index 0000000000..02a057f4a1 --- /dev/null +++ b/packages/libs/hd-wallet/src/utils/web-crypto.ts @@ -0,0 +1,72 @@ +export type BinaryLike = string | DataView | Uint8Array; + +// derive string key +async function deriveKey(password: string, salt: BinaryLike) { + const algo = { + name: 'PBKDF2', + hash: 'SHA-256', + salt: typeof salt === 'string' ? new TextEncoder().encode(salt) : salt, + iterations: 1000, + }; + return crypto.subtle.deriveKey( + algo, + await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(password), + { + name: algo.name, + }, + false, + ['deriveKey'], + ), + { + name: 'AES-GCM', + length: 256, + }, + false, + ['encrypt', 'decrypt'], + ); +} + +// Encrypt function +export async function encrypt( + text: ArrayBuffer, + password: string, + salt: BinaryLike, +) { + const algo = { + name: 'AES-GCM', + length: 256, + iv: crypto.getRandomValues(new Uint8Array(12)), + } as const; + return { + cipherText: await crypto.subtle.encrypt( + algo, + await deriveKey(password, salt), + // new TextEncoder().encode(text) + text, + ), + iv: algo.iv, + }; +} + +type Encrypted = Awaited>; + +// Decrypt function +export async function decrypt( + encrypted: Encrypted, + password: string, + salt: BinaryLike, +) { + const algo = { + name: 'AES-GCM', + length: 256, + iv: encrypted.iv, + }; + return await crypto.subtle + .decrypt(algo, await deriveKey(password, salt), encrypted.cipherText) + .catch(() => { + console.warn('Failed to decrypt seed'); + return null; + }); +} From 1c68747c111a2a90979f7a5fd950f086fb729f2d Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Thu, 18 Jan 2024 21:51:28 +0100 Subject: [PATCH 04/14] refactor(hd-wallet): use web-crypto --- .../src/SLIP10/kadenaGenKeypairFromSeed.ts | 20 +-- .../hd-wallet/src/SLIP10/kadenaGetPublic.ts | 12 +- .../hd-wallet/src/SLIP10/kadenaMnemonic.ts | 2 +- .../libs/hd-wallet/src/SLIP10/kadenaSign.ts | 54 +++++--- .../tests/kadenaGenKeypairFromSeed.test.ts | 37 ++++-- .../src/SLIP10/tests/kadenaGetPublic.test.ts | 24 +++- .../tests/kadenaSignWithKeyPair.test.ts | 10 +- .../SLIP10/tests/kadenaSignWithSeed.test.ts | 4 +- .../libs/hd-wallet/src/SLIP10/utils/sign.ts | 13 +- packages/libs/hd-wallet/src/utils/crypto.ts | 120 +++++++++--------- .../hd-wallet/src/utils/kadenaEncryption.ts | 64 +++++----- .../src/utils/tests/kadenaEncryption.test.ts | 115 +++++++++-------- .../libs/hd-wallet/src/utils/web-crypto.ts | 72 ----------- 13 files changed, 268 insertions(+), 279 deletions(-) delete mode 100644 packages/libs/hd-wallet/src/utils/web-crypto.ts diff --git a/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts b/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts index ab98c98f38..d9e925a550 100644 --- a/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts +++ b/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts @@ -1,14 +1,14 @@ -import type { BinaryLike } from 'crypto'; +import type { BinaryLike } from '../utils/crypto.js'; import type { EncryptedString } from '../utils/kadenaEncryption.js'; import { kadenaDecrypt, kadenaEncrypt } from '../utils/kadenaEncryption.js'; import { deriveKeyPair } from './utils/sign.js'; -function genKeypairFromSeed( +async function genKeypairFromSeed( password: BinaryLike, seedBuffer: Uint8Array, index: number, derivationPathTemplate: string, -): [string, EncryptedString] { +): Promise<[string, EncryptedString]> { const derivationPath = derivationPathTemplate.replace( '', index.toString(), @@ -16,7 +16,7 @@ function genKeypairFromSeed( const { publicKey, privateKey } = deriveKeyPair(seedBuffer, derivationPath); - const encryptedPrivateKey = kadenaEncrypt( + const encryptedPrivateKey = await kadenaEncrypt( password, Buffer.from(privateKey, 'hex'), ); @@ -29,14 +29,14 @@ export function kadenaGenKeypairFromSeed( seed: EncryptedString, index: number, derivationPathTemplate?: string, -): [string, EncryptedString]; +): Promise<[string, EncryptedString]>; export function kadenaGenKeypairFromSeed( password: BinaryLike, seed: EncryptedString, indexRange: [number, number], derivationPathTemplate?: string, -): Array<[string, EncryptedString]>; +): Promise>; /** * Generates a key pair from a seed buffer and an index or range of indices, and optionally encrypts the private key. @@ -48,17 +48,17 @@ export function kadenaGenKeypairFromSeed( * @returns {([string, string] | [string, string][])} - Depending on the input, either a tuple for a single key pair or an array of tuples for a range of key pairs, with the private key encrypted if a password is provided. * @throws {Error} Throws an error if the seed buffer is not provided, if the indices are invalid, or if encryption fails. */ -export function kadenaGenKeypairFromSeed( +export async function kadenaGenKeypairFromSeed( password: BinaryLike, seed: EncryptedString, indexOrRange: number | [number, number], derivationPathTemplate: string = `m'/44'/626'/'`, -): [string, EncryptedString] | Array<[string, EncryptedString]> { +): Promise<[string, EncryptedString] | Array<[string, EncryptedString]>> { if (typeof seed !== 'string' || seed === '') { throw new Error('No seed provided.'); } - const seedBuffer = kadenaDecrypt(password, seed); + const seedBuffer = new Uint8Array(await kadenaDecrypt(password, seed)); if (typeof indexOrRange === 'number') { return genKeypairFromSeed( @@ -77,7 +77,7 @@ export function kadenaGenKeypairFromSeed( const keyPairs: [string, EncryptedString][] = []; for (let index = startIndex; index <= endIndex; index++) { - const [publicKey, encryptedPrivateKey] = genKeypairFromSeed( + const [publicKey, encryptedPrivateKey] = await genKeypairFromSeed( password, seedBuffer, index, diff --git a/packages/libs/hd-wallet/src/SLIP10/kadenaGetPublic.ts b/packages/libs/hd-wallet/src/SLIP10/kadenaGetPublic.ts index 8e5d93eab9..664033c1c6 100644 --- a/packages/libs/hd-wallet/src/SLIP10/kadenaGetPublic.ts +++ b/packages/libs/hd-wallet/src/SLIP10/kadenaGetPublic.ts @@ -1,4 +1,4 @@ -import type { BinaryLike } from 'crypto'; +import type { BinaryLike } from '../utils/crypto.js'; import { kadenaDecrypt } from '../utils/kadenaEncryption.js'; import { deriveKeyPair } from './utils/sign.js'; @@ -22,14 +22,14 @@ export function kadenaGetPublic( seed: BinaryLike, index: number, derivationPathTemplate?: string, -): string; +): Promise; export function kadenaGetPublic( password: BinaryLike, seed: BinaryLike, indexRange: [number, number], derivationPathTemplate?: string, -): string[]; +): Promise; /** * Generates a key pair from a seed buffer and an index or range of indices, and optionally encrypts the private key. @@ -41,17 +41,17 @@ export function kadenaGetPublic( * @returns {([string, string] | [string, string][])} - Depending on the input, either a tuple for a single key pair or an array of tuples for a range of key pairs, with the private key encrypted if a password is provided. * @throws {Error} Throws an error if the seed buffer is not provided, if the indices are invalid, or if encryption fails. */ -export function kadenaGetPublic( +export async function kadenaGetPublic( password: BinaryLike, seed: BinaryLike, indexOrRange: number | [number, number], derivationPathTemplate: string = `m'/44'/626'/'`, -): string | string[] { +): Promise { if (typeof seed !== 'string' || seed === '') { throw new Error('No seed provided.'); } - const seedBuffer = kadenaDecrypt(password, seed); + const seedBuffer = new Uint8Array(await kadenaDecrypt(password, seed)); if (typeof indexOrRange === 'number') { return genPublicKeyFromSeed( diff --git a/packages/libs/hd-wallet/src/SLIP10/kadenaMnemonic.ts b/packages/libs/hd-wallet/src/SLIP10/kadenaMnemonic.ts index 10f63896ea..70fa82f5bb 100644 --- a/packages/libs/hd-wallet/src/SLIP10/kadenaMnemonic.ts +++ b/packages/libs/hd-wallet/src/SLIP10/kadenaMnemonic.ts @@ -1,6 +1,6 @@ import * as bip39 from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; -import type { BinaryLike } from 'crypto'; +import type { BinaryLike } from '../utils/crypto.js'; import { kadenaEncrypt } from '../utils/kadenaEncryption.js'; /** * Generates a mnemonic phrase using the BIP39 protocol with a specified wordlist. diff --git a/packages/libs/hd-wallet/src/SLIP10/kadenaSign.ts b/packages/libs/hd-wallet/src/SLIP10/kadenaSign.ts index 1ff39d447e..22466fffb8 100644 --- a/packages/libs/hd-wallet/src/SLIP10/kadenaSign.ts +++ b/packages/libs/hd-wallet/src/SLIP10/kadenaSign.ts @@ -1,5 +1,5 @@ import { verifySig } from '@kadena/cryptography-utils'; -import type { BinaryLike } from 'crypto'; +import type { BinaryLike } from '../utils/crypto.js'; import type { EncryptedString } from '../utils/kadenaEncryption.js'; import { kadenaDecrypt } from '../utils/kadenaEncryption.js'; import type { ISignatureWithPublicKey } from './utils/sign.js'; @@ -16,11 +16,18 @@ export function kadenaSignWithKeyPair( password: BinaryLike, publicKey: string, encryptedPrivateKey: EncryptedString, -): (hash: string) => ISignatureWithPublicKey { - return signWithKeyPair( - publicKey, - Buffer.from(kadenaDecrypt(password, encryptedPrivateKey)).toString('hex'), - ); +): (hash: string) => Promise { + const decryptedPrivateKey = kadenaDecrypt(password, encryptedPrivateKey); + decryptedPrivateKey.catch(() => { + console.error('Could not decrypt private key'); + }); + return async (hash: string) => + signWithKeyPair( + publicKey, + Buffer.from(await kadenaDecrypt(password, encryptedPrivateKey)).toString( + 'hex', + ), + )(hash); } export function kadenaSignWithSeed( @@ -28,14 +35,14 @@ export function kadenaSignWithSeed( seed: BinaryLike, index: number, derivationPathTemplate?: string, -): (hash: string) => ISignatureWithPublicKey; +): (hash: string) => Promise; export function kadenaSignWithSeed( password: BinaryLike, seed: BinaryLike, index: number[], derivationPathTemplate?: string, -): (hash: string) => ISignatureWithPublicKey[]; +): (hash: string) => Promise; /** * Signs a Kadena transaction with a seed and index. @@ -49,22 +56,29 @@ export function kadenaSignWithSeed( seed: BinaryLike, index: number | number[], derivationPathTemplate: string = `m'/44'/626'/'`, -): (hash: string) => ISignatureWithPublicKey | ISignatureWithPublicKey[] { +): ( + hash: string, +) => Promise { const decryptedSeed = kadenaDecrypt(password, seed); + decryptedSeed.catch(() => { + console.error('Could not decrypt private key'); + }); if (typeof index === 'number') { - return signWithSeed( - decryptedSeed, - derivationPathTemplate.replace('', index.toString()), - ); + return async (hash: string) => + signWithSeed( + await decryptedSeed, + derivationPathTemplate.replace('', index.toString()), + )(hash); } - const signers = index.map((i) => - signWithSeed( - decryptedSeed, - derivationPathTemplate.replace('', i.toString()), - ), + const signers = index.map( + (i) => async (hash: string) => + signWithSeed( + await decryptedSeed, + derivationPathTemplate.replace('', i.toString()), + )(hash), ); - return (hash: string) => signers.map((signer) => signer(hash)); + return (hash: string) => Promise.all(signers.map((signer) => signer(hash))); } /** @@ -84,7 +98,7 @@ export function kadenaVerify( const msgUint8Array = typeof message === 'string' ? Uint8Array.from(Buffer.from(message, 'hex')) - : new Uint8Array(message.buffer); + : new Uint8Array(message); const publicKeyUint8Array = Uint8Array.from(Buffer.from(publicKey, 'hex')); const signatureUint8Array = Uint8Array.from(Buffer.from(signature, 'hex')); diff --git a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGenKeypairFromSeed.test.ts b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGenKeypairFromSeed.test.ts index 91a80f3ced..8b686335ca 100644 --- a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGenKeypairFromSeed.test.ts +++ b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGenKeypairFromSeed.test.ts @@ -13,7 +13,7 @@ describe('kadenaGenKeypairFromSeed', () => { const mnemonic = kadenaGenMnemonic(); const password = 'password'; const seed = await kadenaMnemonicToSeed(password, mnemonic); - const [publicKey, encryptedPrivateKey] = kadenaGenKeypairFromSeed( + const [publicKey, encryptedPrivateKey] = await kadenaGenKeypairFromSeed( password, seed, 0, @@ -27,14 +27,18 @@ describe('kadenaGenKeypairFromSeed', () => { const mnemonic = kadenaGenMnemonic(); const password = 'password'; const seed = await kadenaMnemonicToSeed(password, mnemonic); - const keyPairs = kadenaGenKeypairFromSeed(password, seed, [0, 3]); + const keyPairs = await kadenaGenKeypairFromSeed(password, seed, [0, 3]); expect(keyPairs).toHaveLength(4); - keyPairs.forEach(([publicKey, privateKey]) => { - expect(publicKey).toHaveLength(64); - expect( - Buffer.from(kadenaDecrypt(password, privateKey)).toString('hex'), - ).toHaveLength(64); - }); + await Promise.all( + keyPairs.map(async ([publicKey, privateKey]) => { + expect(publicKey).toHaveLength(64); + expect( + Buffer.from(await kadenaDecrypt(password, privateKey)).toString( + 'hex', + ), + ).toHaveLength(64); + }), + ); }); it('should throw an error for out-of-bounds index values', async () => { @@ -43,17 +47,24 @@ describe('kadenaGenKeypairFromSeed', () => { const seed = await kadenaMnemonicToSeed(password, mnemonic); const outOfBoundsIndex = -1; - expect(() => { - kadenaGenKeypairFromSeed(password, seed, outOfBoundsIndex); - }).toThrowError('Invalid child index: -1'); + await expect(() => + kadenaGenKeypairFromSeed(password, seed, outOfBoundsIndex), + ).rejects.toThrowError('Invalid child index: -1'); }); it('returns an encrypted private key that can be decrypted with the password', async () => { const mnemonic = kadenaGenMnemonic(); const password = 'password'; const seed = await kadenaMnemonicToSeed(password, mnemonic); - const [, encryptedPrivateKey] = kadenaGenKeypairFromSeed(password, seed, 0); - const decryptedPrivateKey = kadenaDecrypt(password, encryptedPrivateKey); + const [, encryptedPrivateKey] = await kadenaGenKeypairFromSeed( + password, + seed, + 0, + ); + const decryptedPrivateKey = await kadenaDecrypt( + password, + encryptedPrivateKey, + ); expect(decryptedPrivateKey).toBeTruthy(); expect(Buffer.from(decryptedPrivateKey).toString('hex')).toHaveLength(64); }); diff --git a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGetPublic.test.ts b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGetPublic.test.ts index 0fb5e77f92..15c7ad44cd 100644 --- a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGetPublic.test.ts +++ b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaGetPublic.test.ts @@ -12,8 +12,8 @@ describe('kadenaGetPublic', () => { const password = 'password'; const seed = await kadenaMnemonicToSeed(password, mnemonic); - const publicKeyIndex0 = kadenaGetPublic(password, seed, 0); - const publicKeyIndex1 = kadenaGetPublic(password, seed, 1); + const publicKeyIndex0 = await kadenaGetPublic(password, seed, 0); + const publicKeyIndex1 = await kadenaGetPublic(password, seed, 1); expect(publicKeyIndex0).toHaveLength(64); expect(publicKeyIndex1).toHaveLength(64); @@ -26,8 +26,8 @@ describe('kadenaGetPublic', () => { const seed = await kadenaMnemonicToSeed(password, mnemonic); const indexes = [0, 1, 2, 3, 4]; - const publicKeys = indexes.map((index) => - kadenaGetPublic(password, seed, index), + const publicKeys = await Promise.all( + indexes.map((index) => kadenaGetPublic(password, seed, index)), ); publicKeys.forEach((publicKey) => { @@ -45,7 +45,7 @@ describe('kadenaGetPublic', () => { // this mnemonic is generated by Enkrypt wallet 'coyote utility final warfare thumb symbol mule scale final nominee behave crumble', ); - let publicKey = kadenaGetPublic( + let publicKey = await kadenaGetPublic( password, seed, 0, @@ -54,11 +54,21 @@ describe('kadenaGetPublic', () => { expect(publicKey).toBe( '43726c4a2e7b03fa5d23635307e5b130baf8b261e1081c099a2b43db1d4554cc', ); - publicKey = kadenaGetPublic(password, seed, 1, "m'/44'/626'//0'/0'"); + publicKey = await kadenaGetPublic( + password, + seed, + 1, + "m'/44'/626'//0'/0'", + ); expect(publicKey).toBe( '3f53dfad097fdf8501c32b275e109980ed7121866a63ca34bb035c4a2e41a265', ); - publicKey = kadenaGetPublic(password, seed, 2, "m'/44'/626'//0'/0'"); + publicKey = await kadenaGetPublic( + password, + seed, + 2, + "m'/44'/626'//0'/0'", + ); expect(publicKey).toBe( '3021bcfa703cc4fac007ab4c5050df5c0b8ca7d655ea80c84af9ea5e43ecf0ff', ); diff --git a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithKeyPair.test.ts b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithKeyPair.test.ts index 890bf64dd7..2d37cc00aa 100644 --- a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithKeyPair.test.ts +++ b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithKeyPair.test.ts @@ -12,13 +12,17 @@ describe('kadenaSignWithKeyPair', async () => { const mnemonic = kadenaGenMnemonic(); const seed = await kadenaMnemonicToSeed(password, mnemonic); - const [publicKey, privateKey] = kadenaGenKeypairFromSeed(password, seed, 0); + const [publicKey, privateKey] = await kadenaGenKeypairFromSeed( + password, + seed, + 0, + ); const txHash: string = 'tx-hash'; - it('should sign a transaction with a public and private key ans password', () => { + it('should sign a transaction with a public and private key ans password', async () => { const signer = kadenaSignWithKeyPair(password, publicKey, privateKey); - const signature = signer(txHash); + const signature = await signer(txHash); expect(signature).toBeTruthy(); expect(signature.sig.length > 0).toBeTruthy(); }); diff --git a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithSeed.test.ts b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithSeed.test.ts index 58b52399ba..1b72d6fdd9 100644 --- a/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithSeed.test.ts +++ b/packages/libs/hd-wallet/src/SLIP10/tests/kadenaSignWithSeed.test.ts @@ -13,9 +13,9 @@ describe('kadenaSignWithSeed', async () => { const index = 0; const hash = 'transaction-hash'; - it('should sign a transaction with a seed and index', () => { + it('should sign a transaction with a seed and index', async () => { const signer = kadenaSignWithSeed(password, seed, index); - const signature = signer(hash); + const signature = await signer(hash); expect(signature).toBeTruthy(); expect(signature.sig.length > 0).toBeTruthy(); }); diff --git a/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts b/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts index 5562c443fb..b56bfdf751 100644 --- a/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts +++ b/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts @@ -10,15 +10,18 @@ export interface ISignatureWithPublicKey { /** * Derive a key pair using a seed and an index. - * @param {Uint8Array} seed - The seed for key derivation. + * @param {ArrayBuffer} seed - The seed for key derivation. * @param {number} index - The index for key derivation. * @returns {{ privateKey: string; publicKey: string }} - Returns the derived private and public keys. */ export const deriveKeyPair = ( - seed: Uint8Array, + seed: ArrayBuffer, derivationPath: string, ): { privateKey: string; publicKey: string } => { - const key = HDKey.fromMasterSeed(seed).derive(derivationPath, true); + const key = HDKey.fromMasterSeed(new Uint8Array(seed)).derive( + derivationPath, + true, + ); return { privateKey: uint8ArrayToHex(key.privateKey), @@ -51,12 +54,12 @@ export const signWithKeyPair = /** * Generate a signer function using a seed and an index. - * @param {Uint8Array} seed - The seed for key derivation. + * @param {ArrayBuffer} seed - The seed for key derivation. * @param {number} index - The index for key derivation. * @returns {(tx: IUnsignedCommand) => { sigs: { sig: string }[] }} - Returns a function that can sign a transaction. */ export const signWithSeed = ( - seed: Uint8Array, + seed: ArrayBuffer, derivationPath: string, ): ((hash: string) => ISignatureWithPublicKey) => { const { publicKey, privateKey } = deriveKeyPair(seed, derivationPath); diff --git a/packages/libs/hd-wallet/src/utils/crypto.ts b/packages/libs/hd-wallet/src/utils/crypto.ts index bd5636c4bd..1fe3ccd4a8 100644 --- a/packages/libs/hd-wallet/src/utils/crypto.ts +++ b/packages/libs/hd-wallet/src/utils/crypto.ts @@ -1,70 +1,76 @@ -import { - createCipheriv, - createDecipheriv, - pbkdf2Sync, - randomBytes, -} from 'crypto'; +export type BinaryLike = string | ArrayBuffer; -type BinaryLike = string | NodeJS.ArrayBufferView; +export const randomBytes = (size: number) => + crypto.getRandomValues(new Uint8Array(size)).buffer as ArrayBuffer; -/** - * Derive a cryptographic key from the provided password. - * @param {string} password - User's password. - * @returns {Buffer} - Returns the derived cryptographic key. - */ -function deriveKey(password: BinaryLike, salt: BinaryLike): Buffer { - return pbkdf2Sync(password, salt, 1000, 32, 'sha256'); +// derive string key +async function deriveKey(password: BinaryLike, salt: BinaryLike) { + const algo = { + name: 'PBKDF2', + hash: 'SHA-256', + salt: typeof salt === 'string' ? new TextEncoder().encode(salt) : salt, + iterations: 1000, + }; + return crypto.subtle.deriveKey( + algo, + await crypto.subtle.importKey( + 'raw', + typeof password === 'string' + ? new TextEncoder().encode(password) + : password, + { + name: algo.name, + }, + false, + ['deriveKey'], + ), + { + name: 'AES-GCM', + length: 256, + }, + false, + ['encrypt', 'decrypt'], + ); } -/** - * Encrypt the provided text using AES-256-GCM algorithm. - * @param {Buffer} text - Text to encrypt. - * @param {string} password - User's password. - * @returns {{ cipherText: Buffer; iv: Buffer; tag: Buffer }} - Returns the encrypted text, initialization vector, and authentication tag. - */ -export function encrypt( - text: Buffer, +// Encrypt function +export async function encrypt( + text: BinaryLike, password: BinaryLike, salt: BinaryLike, -): { cipherText: Buffer; iv: Buffer; tag: Buffer } { - const key = deriveKey(password, salt); - const iv = randomBytes(12); - const cipher = createCipheriv('aes-256-gcm', key, iv); - const cipherText = Buffer.concat([cipher.update(text), cipher.final()]); - const tag = cipher.getAuthTag(); // Capture the authentication tag +) { + const algo = { + name: 'AES-GCM', + length: 256, + iv: randomBytes(12), + } as const; return { - cipherText, - iv, - tag, + cipherText: await crypto.subtle.encrypt( + algo, + await deriveKey(password, salt), + // new TextEncoder().encode(text) + typeof text === 'string' ? new TextEncoder().encode(text) : text, + ), + iv: algo.iv, }; } -/** - * Decrypt the provided encrypted text using AES-256-GCM algorithm. - * @param encrypted - Encrypted text, initialization vector, and authentication tag. - * @param password - User's password. - * @returns Returns the decrypted text or undefined if decryption fails. - * @internal - */ -export function decrypt( - encrypted: { - cipherText: Buffer; - iv: Buffer; - tag: Buffer; - }, +type Encrypted = Awaited>; + +// Decrypt function +export async function decrypt( + encrypted: Encrypted, password: BinaryLike, salt: BinaryLike, -): Buffer | undefined { - const key = deriveKey(password, salt); - const decipher = createDecipheriv('aes-256-gcm', key, encrypted.iv); - decipher.setAuthTag(encrypted.tag); // Set the authentication tag - try { - return Buffer.concat([ - decipher.update(encrypted.cipherText), - decipher.final(), - ]); - } catch (err) { - console.warn('Failed to decrypt'); - return undefined; - } +) { + const algo = { + name: 'AES-GCM', + length: 256, + iv: encrypted.iv, + }; + return await crypto.subtle.decrypt( + algo, + await deriveKey(password, salt), + encrypted.cipherText, + ); } diff --git a/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts b/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts index 9be28bab1d..4613d562da 100644 --- a/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts +++ b/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts @@ -1,35 +1,38 @@ -import type { BinaryLike } from 'crypto'; -import { randomBytes } from 'crypto'; -import { decrypt, encrypt } from './crypto.js'; +import type { BinaryLike } from './crypto.js'; +import { decrypt, encrypt, randomBytes } from './crypto.js'; export type EncryptedString = string & { _brand: 'EncryptedString' }; /** * Encrypts the message with a password . - * @param {Uint8Array} message - The message to be encrypted. + * @param {BinaryLike} message - The message to be encrypted. * @param {BinaryLike} password - password used for encryption. * @returns {string} The encrypted string */ -export function kadenaEncrypt< +export async function kadenaEncrypt< TEncode extends 'base64' | 'buffer' = 'base64', - TReturn = TEncode extends 'base64' ? EncryptedString : Uint8Array, + TReturn = TEncode extends 'base64' ? EncryptedString : ArrayBuffer, >( password: BinaryLike, - message: Uint8Array | string, + message: BinaryLike, encode: TEncode = 'base64' as TEncode, -): TReturn { +): Promise { // Using randomBytes for the salt is fine here because the salt is not secret but should be unique. const salt = randomBytes(16); - const { cipherText, iv, tag } = encrypt(Buffer.from(message), password, salt); + const { cipherText, iv } = await encrypt( + typeof message === 'string' ? Buffer.from(message) : message, + password, + salt, + ); const encrypted = Buffer.from( - [salt, iv, tag, cipherText].map((x) => x.toString('base64')).join('.'), + [salt, iv, cipherText] + .map((x) => Buffer.from(x).toString('base64')) + .join('.'), ); return ( - encode === 'base64' - ? (encrypted.toString('base64') as EncryptedString) - : new Uint8Array(encrypted) + encode === 'base64' ? encrypted.toString('base64') : encrypted ) as TReturn; } @@ -40,34 +43,35 @@ export function kadenaEncrypt< * * @param {string} encryptedData - The encrypted data as a Base64 encoded string. * @param {BinaryLike} password - The password used to encrypt the private key. - * @returns {Uint8Array} The decrypted private key. + * @returns {ArrayBuffer} The decrypted private key. * @throws {Error} Throws an error if decryption fails. */ -export function kadenaDecrypt( +export async function kadenaDecrypt( password: BinaryLike, - encryptedData: EncryptedString | BinaryLike, -): Uint8Array { + encryptedData: BinaryLike, +): Promise { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!encryptedData) { throw new Error('Encrypted data is empty'); } - const [saltBase64, ivBase64, tagBase64, encryptedBase64] = + const [saltBase64, ivBase64, encryptedBase64] = typeof encryptedData === 'string' ? Buffer.from(encryptedData, 'base64').toString().split('.') - : Buffer.from(encryptedData.buffer).toString().split('.'); + : Buffer.from(encryptedData).toString().split('.'); // Convert from Base64. const salt = Buffer.from(saltBase64, 'base64'); const iv = Buffer.from(ivBase64, 'base64'); - const tag = Buffer.from(tagBase64, 'base64'); const cipherText = Buffer.from(encryptedBase64, 'base64'); // decrypt and return the private key. - const decrypted = decrypt({ cipherText, iv, tag }, password, salt); - if (!decrypted) { - throw new Error('Decryption failed'); - } - return decrypted; + + const decrypted = await decrypt({ cipherText, iv }, password, salt).catch( + () => undefined, + ); + if (decrypted) return decrypted; + + throw new Error('Decryption failed'); } /** @@ -79,11 +83,11 @@ export function kadenaDecrypt( * @returns {string} - The newly encrypted private key as a Base64 encoded string. * @throws {Error} - Throws an error if the old password is empty, new password is incorrect empty passwords are empty, or if encryption with the new password fails. */ -export function kadenaChangePassword( +export async function kadenaChangePassword( password: BinaryLike, - encryptedData: EncryptedString | Uint8Array, + encryptedData: BinaryLike, newPassword: string, -): EncryptedString { +): Promise { if (typeof password !== 'string' || typeof newPassword !== 'string') { throw new Error('The old and new passwords must be strings.'); } @@ -99,9 +103,9 @@ export function kadenaChangePassword( ); } - let decryptedPrivateKey: Uint8Array; + let decryptedPrivateKey: ArrayBuffer; try { - decryptedPrivateKey = kadenaDecrypt(password, encryptedData); + decryptedPrivateKey = await kadenaDecrypt(password, encryptedData); } catch (error) { throw new Error( `Failed to decrypt the private key with the old password: ${error.message}`, diff --git a/packages/libs/hd-wallet/src/utils/tests/kadenaEncryption.test.ts b/packages/libs/hd-wallet/src/utils/tests/kadenaEncryption.test.ts index bffc197136..d717ab8a21 100644 --- a/packages/libs/hd-wallet/src/utils/tests/kadenaEncryption.test.ts +++ b/packages/libs/hd-wallet/src/utils/tests/kadenaEncryption.test.ts @@ -11,69 +11,75 @@ describe('kadenaDecrypt', () => { it('should correctly decrypt a previously encrypted string', async () => { const password = 'test-password'; const message = Buffer.from('test-message'); - const encryptedMessage = kadenaEncrypt(password, message); - const decryptedMessage = kadenaDecrypt(password, encryptedMessage); + const encryptedMessage = await kadenaEncrypt(password, message); + const decryptedMessage = await kadenaDecrypt(password, encryptedMessage); expect(Buffer.from(decryptedMessage).toString('utf-8')).toEqual( message.toString('utf-8'), ); - expect(message.toJSON()).not.toBe(encryptedMessage); - expect(encryptedMessage.toString()).not.toBe(decryptedMessage.toString()); + expect(message.toString()).not.toBe(encryptedMessage); + expect(encryptedMessage).not.toBe(Buffer.from(decryptedMessage).toString()); }); it('should throw an error when the incorrect password is provided', async () => { const correctPassword = 'correct-password'; const wrongPassword = 'wrong-password'; const message = Buffer.from('test-message'); - const encryptedMessage = kadenaEncrypt(correctPassword, message); - expect(() => { - kadenaDecrypt(wrongPassword, encryptedMessage); - }).toThrow('Decryption failed'); + const encryptedMessage = await kadenaEncrypt(correctPassword, message); + await expect(() => + kadenaDecrypt(wrongPassword, encryptedMessage), + ).rejects.toThrow('Decryption failed'); }); - it('should throw an error if the encrypted key is corrupted', () => { + it('should throw an error if the encrypted key is corrupted', async () => { const password = 'test-password'; const corruptedEncryptedPrivateKey = 'corrupted-data' as EncryptedString; - expect(() => { - kadenaDecrypt(password, corruptedEncryptedPrivateKey); - }).toThrow(); + await expect(() => + kadenaDecrypt(password, corruptedEncryptedPrivateKey), + ).rejects.toThrow(); }); - it('should throw an error if the encrypted data is empty', () => { + it('should throw an error if the encrypted data is empty', async () => { const password = 'test-password'; const emptyEncryptedMessage = '' as EncryptedString; - expect(() => { - kadenaDecrypt(password, emptyEncryptedMessage); - }).toThrow('Encrypted data is empty'); + await expect(() => + kadenaDecrypt(password, emptyEncryptedMessage), + ).rejects.toThrow('Encrypted data is empty'); }); it('should handle passwords with special characters', async () => { const specialCharPassword = 'p@ssw0rd!#%&'; const message = Buffer.from('test-message'); - const encryptedMessage = kadenaEncrypt(specialCharPassword, message); - expect( - kadenaDecrypt(specialCharPassword, encryptedMessage).toString(), - ).toBe(message.toString()); + const encryptedMessage = await kadenaEncrypt(specialCharPassword, message); + const decryptedMessage = await kadenaDecrypt( + specialCharPassword, + encryptedMessage, + ); + expect(Buffer.from(decryptedMessage).toString()).toBe(message.toString()); }); it('should handle extremely long passwords', async () => { const longPassword = 'p'.repeat(1000); const message = Buffer.from('test-message'); - const encryptedMessage = kadenaEncrypt(longPassword, message); - expect(kadenaDecrypt(longPassword, encryptedMessage).toString()).toBe( - message.toString(), + const encryptedMessage = await kadenaEncrypt(longPassword, message); + const decryptedMessage = await kadenaDecrypt( + longPassword, + encryptedMessage, ); + expect(Buffer.from(decryptedMessage).toString()).toBe(message.toString()); }); it('should handle unicode characters in passwords', async () => { const unicodePassword = '密码'; // 'password' in Chinese const message = Buffer.from('test-message'); - const encryptedMessage = kadenaEncrypt(unicodePassword, message); - - expect(kadenaDecrypt(unicodePassword, encryptedMessage).toString()).toBe( - message.toString(), + const encryptedMessage = await kadenaEncrypt(unicodePassword, message); + const decryptedMessage = await kadenaDecrypt( + unicodePassword, + encryptedMessage, ); + + expect(Buffer.from(decryptedMessage).toString()).toBe(message.toString()); }); }); @@ -84,12 +90,12 @@ describe('kadenaChangePassword', () => { const password = 'currentPassword123'; const newPassword = 'newPassword123'; - it('changes the password successfully and allows decryption with the new password', () => { + it('changes the password successfully and allows decryption with the new password', async () => { const firstPassword = 'firstPassword123'; const secondPassword = 'secondPassword123'; const message = Buffer.from('test-message'); - const encryptedMessage = kadenaEncrypt(firstPassword, message); - const newEncryptedMessage = kadenaChangePassword( + const encryptedMessage = await kadenaEncrypt(firstPassword, message); + const newEncryptedMessage = await kadenaChangePassword( firstPassword, encryptedMessage, secondPassword, @@ -99,33 +105,36 @@ describe('kadenaChangePassword', () => { ); const decryptedMessage = Buffer.from( - kadenaDecrypt(secondPassword, newEncryptedMessage), + await kadenaDecrypt(secondPassword, newEncryptedMessage), ); + expect(decryptedMessage.toString()).toBe(message.toString()); }); - it('throws an error when the password is incorrect', () => { - const encryptedPrivateKey = kadenaEncrypt(password, privateKey); + it('throws an error when the password is incorrect', async () => { + const encryptedPrivateKey = await kadenaEncrypt(password, privateKey); const incorrectPassword = 'wrongPassword'; const changePasswordAttempt = () => kadenaChangePassword(incorrectPassword, encryptedPrivateKey, newPassword); - expect(changePasswordAttempt).toThrow( + await expect(() => changePasswordAttempt()).rejects.toThrow( 'Failed to decrypt the private key with the old password: Decryption failed', ); }); - it('fails to decrypt with the old password after the password has been changed', () => { + it('fails to decrypt with the old password after the password has been changed', async () => { const firstPassword = 'firstPassword123'; const secondPassword = 'secondPassword123'; const message = Buffer.from('test-message'); - const encryptedMessage = kadenaEncrypt(firstPassword, message); - const newEncryptedMessage = kadenaChangePassword( + const encryptedMessage = await kadenaEncrypt(firstPassword, message); + const newEncryptedMessage = await kadenaChangePassword( firstPassword, encryptedMessage, secondPassword, ); - expect(() => kadenaDecrypt(firstPassword, newEncryptedMessage)).toThrow(); + await expect(() => + kadenaDecrypt(firstPassword, newEncryptedMessage), + ).rejects.toThrow(); }); const testUndefined = undefined as unknown as EncryptedString; @@ -135,27 +144,27 @@ describe('kadenaChangePassword', () => { }) as unknown as EncryptedString; const testMessage = 'test-message' as EncryptedString; - it('handles non-string inputs for private keys and passwords', () => { - expect(() => + it('handles non-string inputs for private keys and passwords', async () => { + await expect(() => kadenaChangePassword(password, testUndefined, newPassword), - ).toThrow(); - expect(() => + ).rejects.toThrow(); + await expect(() => kadenaChangePassword(testUndefined, testMessage, newPassword), - ).toThrow(); - expect(() => + ).rejects.toThrow(); + await expect(() => kadenaChangePassword(password, testMessage, testUndefined), - ).toThrow(); - expect(() => + ).rejects.toThrow(); + await expect(() => kadenaChangePassword(password, testNull, newPassword), - ).toThrow(); - expect(() => + ).rejects.toThrow(); + await expect(() => kadenaChangePassword(testNull, testUnint8Array, newPassword), - ).toThrow(); - expect(() => + ).rejects.toThrow(); + await expect(() => kadenaChangePassword(password, testMessage, testNull), - ).toThrow(); - expect(() => + ).rejects.toThrow(); + await expect(() => kadenaChangePassword(password, testMessage, password), - ).toThrow(); + ).rejects.toThrow(); }); }); diff --git a/packages/libs/hd-wallet/src/utils/web-crypto.ts b/packages/libs/hd-wallet/src/utils/web-crypto.ts deleted file mode 100644 index 02a057f4a1..0000000000 --- a/packages/libs/hd-wallet/src/utils/web-crypto.ts +++ /dev/null @@ -1,72 +0,0 @@ -export type BinaryLike = string | DataView | Uint8Array; - -// derive string key -async function deriveKey(password: string, salt: BinaryLike) { - const algo = { - name: 'PBKDF2', - hash: 'SHA-256', - salt: typeof salt === 'string' ? new TextEncoder().encode(salt) : salt, - iterations: 1000, - }; - return crypto.subtle.deriveKey( - algo, - await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(password), - { - name: algo.name, - }, - false, - ['deriveKey'], - ), - { - name: 'AES-GCM', - length: 256, - }, - false, - ['encrypt', 'decrypt'], - ); -} - -// Encrypt function -export async function encrypt( - text: ArrayBuffer, - password: string, - salt: BinaryLike, -) { - const algo = { - name: 'AES-GCM', - length: 256, - iv: crypto.getRandomValues(new Uint8Array(12)), - } as const; - return { - cipherText: await crypto.subtle.encrypt( - algo, - await deriveKey(password, salt), - // new TextEncoder().encode(text) - text, - ), - iv: algo.iv, - }; -} - -type Encrypted = Awaited>; - -// Decrypt function -export async function decrypt( - encrypted: Encrypted, - password: string, - salt: BinaryLike, -) { - const algo = { - name: 'AES-GCM', - length: 256, - iv: encrypted.iv, - }; - return await crypto.subtle - .decrypt(algo, await deriveKey(password, salt), encrypted.cipherText) - .catch(() => { - console.warn('Failed to decrypt seed'); - return null; - }); -} From aa5c10777347e8d7b479c140f8f42baecb71ec3d Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 11:21:01 +0100 Subject: [PATCH 05/14] refactor(hd-wallet): use new web-crypro --- .../src/SLIP10/kadenaGenKeypairFromSeed.ts | 8 ++++++ .../chainweaver/compatibility/encryption.ts | 6 ++--- .../compatibility/kadenaChangePassword.ts | 2 +- .../compatibility/kadenaGenKeypair.ts | 4 +-- .../kadenaGetPublicFromRootKey.ts | 2 +- .../chainweaver/compatibility/kadenaSign.ts | 2 +- .../compatibility/kadenaSignFromRootKey.ts | 2 +- .../src/chainweaver/tests/chainweaver.test.ts | 27 ++++++++++++------- packages/libs/hd-wallet/src/utils/crypto.ts | 2 +- .../hd-wallet/src/utils/kadenaEncryption.ts | 9 ++++--- 10 files changed, 40 insertions(+), 24 deletions(-) diff --git a/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts b/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts index d9e925a550..e4545fac01 100644 --- a/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts +++ b/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts @@ -24,6 +24,14 @@ async function genKeypairFromSeed( return [publicKey, encryptedPrivateKey]; } +/** + * + * @param password + * @param seed + * @param index + * @param derivationPathTemplate + * @alpha + */ export function kadenaGenKeypairFromSeed( password: BinaryLike, seed: EncryptedString, diff --git a/packages/libs/hd-wallet/src/chainweaver/compatibility/encryption.ts b/packages/libs/hd-wallet/src/chainweaver/compatibility/encryption.ts index 254eb59a3c..228934b9ef 100644 --- a/packages/libs/hd-wallet/src/chainweaver/compatibility/encryption.ts +++ b/packages/libs/hd-wallet/src/chainweaver/compatibility/encryption.ts @@ -1,12 +1,12 @@ import type { EncryptedString } from '../../index.js'; import { kadenaEncrypt } from '../../index.js'; -export function encryptLegacySecretKey( +export async function encryptLegacySecretKey( password: string, secretKey: Uint8Array, -): EncryptedString { +): Promise { const xpub = secretKey.slice(64, 96); - const encryptedSecret = kadenaEncrypt(password, secretKey); + const encryptedSecret = await kadenaEncrypt(password, secretKey); // Add public key to the encrypted secret const encrypted = Buffer.from(encryptedSecret, 'base64').toString(); const publicKey = Buffer.from(xpub).toString('base64'); diff --git a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaChangePassword.ts b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaChangePassword.ts index 83f0f4ff02..183907c103 100644 --- a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaChangePassword.ts +++ b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaChangePassword.ts @@ -9,7 +9,7 @@ export const kadenaChangePassword = async ( newPassword: string, ): Promise => { const newSecretKey = await originalKadenaChangePassword( - kadenaDecrypt(oldPassword, secretKey), + await kadenaDecrypt(oldPassword, secretKey), oldPassword, newPassword, ); diff --git a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts index 625554d9a5..f9a7681e7a 100644 --- a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts +++ b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGenKeypair.ts @@ -17,7 +17,7 @@ async function kadenaGenOneKeypair( const keyPair = await kadenaGenKeypairOriginal(password, rootKey, index); return { publicKey: Buffer.from(keyPair[1]).toString('hex'), - secretKey: encryptLegacySecretKey(password, keyPair[0]), + secretKey: await encryptLegacySecretKey(password, keyPair[0]), }; } @@ -50,7 +50,7 @@ export async function kadenaGenKeypair( rootKey: EncryptedString, indexOrRange: number | [start: number, end: number], ) { - const decrypted = kadenaDecrypt(password, rootKey); + const decrypted = await kadenaDecrypt(password, rootKey); if (typeof indexOrRange === 'number') { return await kadenaGenOneKeypair(password, decrypted, harden(indexOrRange)); } diff --git a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGetPublicFromRootKey.ts b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGetPublicFromRootKey.ts index 3f7e1d6086..34c5187986 100644 --- a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGetPublicFromRootKey.ts +++ b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaGetPublicFromRootKey.ts @@ -7,7 +7,7 @@ export async function kadenaGetPublicFromRootKey( rootKey: EncryptedString, index: number, ): Promise { - const decrypted = kadenaDecrypt(password, rootKey); + const decrypted = await kadenaDecrypt(password, rootKey); const [, publicKey] = await kadenaGenKeypair(password, decrypted, index); return publicKey; } diff --git a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSign.ts b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSign.ts index a99f9d15d2..1dcc9b1e61 100644 --- a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSign.ts +++ b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSign.ts @@ -10,6 +10,6 @@ export const kadenaSign = async ( return await originalKadenaSign( password, message, - kadenaDecrypt(password, secretKey), + await kadenaDecrypt(password, secretKey), ); }; diff --git a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSignFromRootKey.ts b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSignFromRootKey.ts index 530753df11..78edba5922 100644 --- a/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSignFromRootKey.ts +++ b/packages/libs/hd-wallet/src/chainweaver/compatibility/kadenaSignFromRootKey.ts @@ -18,6 +18,6 @@ export async function kadenaSignFromRootKey( index: number, ): Promise { const { secretKey } = await kadenaGenKeypair(password, rootKey, index); - const secret = kadenaDecrypt(password, secretKey); + const secret = await kadenaDecrypt(password, secretKey); return kadenaSign(password, message, secret); } diff --git a/packages/libs/hd-wallet/src/chainweaver/tests/chainweaver.test.ts b/packages/libs/hd-wallet/src/chainweaver/tests/chainweaver.test.ts index 93dfdebd5d..945b5c08a4 100644 --- a/packages/libs/hd-wallet/src/chainweaver/tests/chainweaver.test.ts +++ b/packages/libs/hd-wallet/src/chainweaver/tests/chainweaver.test.ts @@ -16,8 +16,15 @@ const MNEMONIC = function validateEncryptedValue(value: string) { const buffer = Buffer.from(value, 'base64').toString('utf8'); const parts = buffer.split('.'); - // parts: salt, iv, tag, data, public key (only for secretKeys, not rootKey) - return parts.length === 4 || parts.length === 5; + // parts: salt, iv, data + return parts.length === 3; +} + +function validateEncryptedKeyPair(value: string) { + const buffer = Buffer.from(value, 'base64').toString('utf8'); + const parts = buffer.split('.'); + // parts: salt, iv, data, public key (only for secretKeys, not rootKey) + return parts.length === 4; } describe('kadenaGenMnemonic', () => { @@ -67,8 +74,8 @@ describe('kadenaMnemonicToRootKeypair', () => { mnemonicSecond, ); - const rootKey1Decrypted = kadenaDecrypt(PASSWORD, rootKey1); - const rootKey2Decrypted = kadenaDecrypt(PASSWORD, rootKey2); + const rootKey1Decrypted = await kadenaDecrypt(PASSWORD, rootKey1); + const rootKey2Decrypted = await kadenaDecrypt(PASSWORD, rootKey2); expect(rootKey1Decrypted).not.toEqual(rootKey2Decrypted); }); @@ -80,8 +87,8 @@ describe('kadenaMnemonicToRootKeypair', () => { const rootKey1 = await kadenaMnemonicToRootKeypair('pass-one', MNEMONIC); const rootKey2 = await kadenaMnemonicToRootKeypair('pass-two', MNEMONIC); - const rootKey1Decrypted = kadenaDecrypt('pass-one', rootKey1); - const rootKey2Decrypted = kadenaDecrypt('pass-two', rootKey2); + const rootKey1Decrypted = await kadenaDecrypt('pass-one', rootKey1); + const rootKey2Decrypted = await kadenaDecrypt('pass-two', rootKey2); expect(rootKey1Decrypted).not.toEqual(rootKey2Decrypted); }); @@ -95,11 +102,11 @@ describe('kadenaGenKeypair', () => { rootKey, 1, ); - expect(validateEncryptedValue(secretKey)).toBe(true); + expect(validateEncryptedKeyPair(secretKey)).toBe(true); - const secretKeyDecrypted = kadenaDecrypt(PASSWORD, secretKey); + const secretKeyDecrypted = await kadenaDecrypt(PASSWORD, secretKey); - expect(secretKeyDecrypted.length).toBe(128); + expect(Buffer.from(secretKeyDecrypted).length).toBe(128); expect(publicKey.length).toBe(64); const publicKeyFromSecret = getPublicKeyFromLegacySecretKey(secretKey); @@ -112,7 +119,7 @@ describe('kadenaGenKeypair', () => { expect(keyPairs).toHaveLength(4); keyPairs.forEach(({ publicKey, secretKey }) => { - expect(validateEncryptedValue(secretKey)).toBe(true); + expect(validateEncryptedKeyPair(secretKey)).toBe(true); expect(publicKey.length).toBe(64); }); }); diff --git a/packages/libs/hd-wallet/src/utils/crypto.ts b/packages/libs/hd-wallet/src/utils/crypto.ts index 1fe3ccd4a8..2fde8c18f7 100644 --- a/packages/libs/hd-wallet/src/utils/crypto.ts +++ b/packages/libs/hd-wallet/src/utils/crypto.ts @@ -1,4 +1,4 @@ -export type BinaryLike = string | ArrayBuffer; +export type BinaryLike = string | ArrayBuffer | Uint8Array; export const randomBytes = (size: number) => crypto.getRandomValues(new Uint8Array(size)).buffer as ArrayBuffer; diff --git a/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts b/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts index 4613d562da..056c90fa39 100644 --- a/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts +++ b/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts @@ -11,7 +11,7 @@ export type EncryptedString = string & { _brand: 'EncryptedString' }; */ export async function kadenaEncrypt< TEncode extends 'base64' | 'buffer' = 'base64', - TReturn = TEncode extends 'base64' ? EncryptedString : ArrayBuffer, + TReturn = TEncode extends 'base64' ? EncryptedString : Uint8Array, >( password: BinaryLike, message: BinaryLike, @@ -43,13 +43,14 @@ export async function kadenaEncrypt< * * @param {string} encryptedData - The encrypted data as a Base64 encoded string. * @param {BinaryLike} password - The password used to encrypt the private key. - * @returns {ArrayBuffer} The decrypted private key. + * @returns {Uint8Array} The decrypted private key. * @throws {Error} Throws an error if decryption fails. + * @alpha */ export async function kadenaDecrypt( password: BinaryLike, encryptedData: BinaryLike, -): Promise { +): Promise { // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!encryptedData) { throw new Error('Encrypted data is empty'); @@ -69,7 +70,7 @@ export async function kadenaDecrypt( const decrypted = await decrypt({ cipherText, iv }, password, salt).catch( () => undefined, ); - if (decrypted) return decrypted; + if (decrypted) return new Uint8Array(decrypted); throw new Error('Decryption failed'); } From 765787757d976e1ce54dbef2640c15e6d06d9496 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 11:36:45 +0100 Subject: [PATCH 06/14] refactor(kadena-cli): use web-crypto --- packages/tools/kadena-cli/.gitignore | 1 + .../src/keys/commands/keysChangeWalletPassword.ts | 4 ++-- packages/tools/kadena-cli/src/keys/commands/keysDecrypt.ts | 2 +- .../tools/kadena-cli/src/keys/commands/keysPlainGenerate.ts | 2 +- packages/tools/kadena-cli/src/keys/utils/keySharedKeyGen.ts | 6 ++++-- packages/tools/kadena-cli/src/utils/test.util.ts | 2 +- packages/tools/kadena-cli/vitest.config.ts | 1 + packages/tools/kadena-cli/vitest.setup.js | 3 +++ 8 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 packages/tools/kadena-cli/vitest.setup.js diff --git a/packages/tools/kadena-cli/.gitignore b/packages/tools/kadena-cli/.gitignore index e22eb3a54f..9210966114 100644 --- a/packages/tools/kadena-cli/.gitignore +++ b/packages/tools/kadena-cli/.gitignore @@ -6,3 +6,4 @@ .kadena/ *.js +!/vitest.setup.js diff --git a/packages/tools/kadena-cli/src/keys/commands/keysChangeWalletPassword.ts b/packages/tools/kadena-cli/src/keys/commands/keysChangeWalletPassword.ts index a3b25d3b06..8bf6f96330 100644 --- a/packages/tools/kadena-cli/src/keys/commands/keysChangeWalletPassword.ts +++ b/packages/tools/kadena-cli/src/keys/commands/keysChangeWalletPassword.ts @@ -64,11 +64,11 @@ export const changeWalletPassword = async ( newPassword, ); } else { - const decryptedCurrentSeed = kadenaDecrypt( + const decryptedCurrentSeed = await kadenaDecrypt( currentPassword, walletContent as EncryptedString, ); - encryptedNewSeed = kadenaEncrypt(newPassword, decryptedCurrentSeed); + encryptedNewSeed = await kadenaEncrypt(newPassword, decryptedCurrentSeed); } await storageService.storeWallet( diff --git a/packages/tools/kadena-cli/src/keys/commands/keysDecrypt.ts b/packages/tools/kadena-cli/src/keys/commands/keysDecrypt.ts index c572f9f617..8166131204 100644 --- a/packages/tools/kadena-cli/src/keys/commands/keysDecrypt.ts +++ b/packages/tools/kadena-cli/src/keys/commands/keysDecrypt.ts @@ -15,7 +15,7 @@ export const decrypt = async ( password: string, keyMessage: EncryptedString, ): Promise> => { - const decryptedMessage = kadenaDecrypt(password, keyMessage); + const decryptedMessage = await kadenaDecrypt(password, keyMessage); const isLegacy = decryptedMessage.byteLength >= 128; diff --git a/packages/tools/kadena-cli/src/keys/commands/keysPlainGenerate.ts b/packages/tools/kadena-cli/src/keys/commands/keysPlainGenerate.ts index ca14a1d9b6..cfd2a981b9 100644 --- a/packages/tools/kadena-cli/src/keys/commands/keysPlainGenerate.ts +++ b/packages/tools/kadena-cli/src/keys/commands/keysPlainGenerate.ts @@ -40,7 +40,7 @@ async function generateLegacyKeyPairs( ): Promise { const keyPairs: storageService.IKeyPair[] = []; const password = ''; - const rootKey = kadenaEncrypt(password, randomBytes(128)); + const rootKey = await kadenaEncrypt(password, randomBytes(128)); for (let i = 0; i < amount; i++) { const { publicKey, secretKey } = await kadenaGenKeypair( diff --git a/packages/tools/kadena-cli/src/keys/utils/keySharedKeyGen.ts b/packages/tools/kadena-cli/src/keys/utils/keySharedKeyGen.ts index 2113c49c32..124031e181 100644 --- a/packages/tools/kadena-cli/src/keys/utils/keySharedKeyGen.ts +++ b/packages/tools/kadena-cli/src/keys/utils/keySharedKeyGen.ts @@ -113,7 +113,7 @@ async function generateSingleKeyPair( publicKey = _publicKey; secretKey = _secretKey; } else { - const [publicKeyString, secretKeyString] = kadenaGenKeypairFromSeed( + const [publicKeyString, secretKeyString] = await kadenaGenKeypairFromSeed( config.securityPassword, keyWallet, index, @@ -121,7 +121,9 @@ async function generateSingleKeyPair( publicKey = publicKeyString; secretKey = config.keyGenFromChoice === 'genPublicSecretKeyDec' - ? toHexStr(kadenaDecrypt(config.securityPassword, secretKeyString)) + ? toHexStr( + await kadenaDecrypt(config.securityPassword, secretKeyString), + ) : secretKeyString; } diff --git a/packages/tools/kadena-cli/src/utils/test.util.ts b/packages/tools/kadena-cli/src/utils/test.util.ts index 01015da4b9..4a00a1405b 100644 --- a/packages/tools/kadena-cli/src/utils/test.util.ts +++ b/packages/tools/kadena-cli/src/utils/test.util.ts @@ -94,5 +94,5 @@ export function isValidEncryptedValue(value: string | unknown): boolean { const buffer = Buffer.from(value, 'base64').toString('utf8'); const parts = buffer.split('.'); // parts: salt, iv, tag, data, public key (only for secretKeys, not rootKey) - return parts.length === 4 || parts.length === 5; + return parts.length === 3 || parts.length === 4; } diff --git a/packages/tools/kadena-cli/vitest.config.ts b/packages/tools/kadena-cli/vitest.config.ts index be3ccc4ef6..98bfc0b02d 100644 --- a/packages/tools/kadena-cli/vitest.config.ts +++ b/packages/tools/kadena-cli/vitest.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { include: ['src/**/*.test.ts'], + setupFiles: './vitest.setup.js', threads: false, // To prevent error in tests using jsdom environment: Module did not self-register: canvas.node }, }); diff --git a/packages/tools/kadena-cli/vitest.setup.js b/packages/tools/kadena-cli/vitest.setup.js new file mode 100644 index 0000000000..c3352b2df1 --- /dev/null +++ b/packages/tools/kadena-cli/vitest.setup.js @@ -0,0 +1,3 @@ +if (global.crypto === undefined) { + global.crypto = require('crypto'); +} From d3ab9ef65752ece53f7bc28ddae91e3e4eacc69d Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 11:55:10 +0100 Subject: [PATCH 07/14] test(kadena-cli): decrypt test --- packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts b/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts index b0cece36ba..933e08764e 100644 --- a/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts +++ b/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts @@ -1,18 +1,18 @@ -import type { EncryptedString } from '@kadena/hd-wallet'; +import { kadenaEncrypt, type EncryptedString } from '@kadena/hd-wallet'; import { assert, describe, expect, it } from 'vitest'; import { decrypt } from '../commands/keysDecrypt.js'; describe('decrypt command', () => { it('should decrypt a message correctly', async () => { const keyMessage = - 'STBjVU02cmFVV1UvNkUvei9wOGhOUT09Lm00RS8yWGlHeisraUZNMkEuNjhzMGk0UlBKcFRRc1ptOVhCMlFxQT09Lmk0NjEwbDVUYWd0TmVMMWhNM2xvTHIzNmMyam5CTDBDUUFyNXZjbldxWFJhOXRoa25MSnFUc3VmWEdoWnVNMkVRWFpmbEpYcnh2Y1cwK0p2NXpjeDlRPT0='; + 'R0hiSzI4aEZxa2ZJSnI1UGZxQTkwZz09LmxidlJXZzNsbndsQmIrVEkudkZ0bEduYVp6MzJuVTlvdWlZRUpFWG9DbmVQTWdxNi9vdWd0VnhWZFpKOHJMZ2t3eGZJUlNCU2txNDhDZ3pvKzgvbHdPWGtYZkVLUVAxK1pVOElCVERKMXp4dnlzSVkrN2FGWGduMHQ0MjQ9'; const password = '12345678'; const output = await decrypt(password, keyMessage as EncryptedString); assert(output.success); - expect(output.data.value).toContain( + expect(output.data.value).toEqual( '786aeb505f3dd734d2acd25846816dc0d5f975474a391f4190400c4f4ccb57aab50fd5612de8edaf35c3a01b0a9c564c2e216cfb512591639c5e901d55f2363f', ); }); From 8fca466c785b5b7c45a1b9a4ef0945b016ec92dd Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 11:58:45 +0100 Subject: [PATCH 08/14] chore(hd-wallet): changeset --- .changeset/quick-trees-own.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/quick-trees-own.md diff --git a/.changeset/quick-trees-own.md b/.changeset/quick-trees-own.md new file mode 100644 index 0000000000..996d6ce7cc --- /dev/null +++ b/.changeset/quick-trees-own.md @@ -0,0 +1,6 @@ +--- +'@kadena/kadena-cli': minor +'@kadena/hd-wallet': minor +--- + +refactor hd-wallet to use web-crypto api for cross platform compability From e63f1fd104e19eb7b12e3bd24e369ac71bb7cb2c Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 12:00:48 +0100 Subject: [PATCH 09/14] fix:typos --- .changeset/quick-trees-own.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-trees-own.md b/.changeset/quick-trees-own.md index 996d6ce7cc..a18b754398 100644 --- a/.changeset/quick-trees-own.md +++ b/.changeset/quick-trees-own.md @@ -3,4 +3,4 @@ '@kadena/hd-wallet': minor --- -refactor hd-wallet to use web-crypto api for cross platform compability +refactor hd-wallet to use web-crypto api for cross platform compatibility From fc3a3ac93563086289dbe05d9c89179a61455c88 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 12:37:58 +0100 Subject: [PATCH 10/14] fix(hd-wallet): crypto --- packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts | 2 +- packages/libs/hd-wallet/src/SLIP10/kadenaKeyPairsFromRandom.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts b/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts index e4545fac01..4834959dfc 100644 --- a/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts +++ b/packages/libs/hd-wallet/src/SLIP10/kadenaGenKeypairFromSeed.ts @@ -66,7 +66,7 @@ export async function kadenaGenKeypairFromSeed( throw new Error('No seed provided.'); } - const seedBuffer = new Uint8Array(await kadenaDecrypt(password, seed)); + const seedBuffer = await kadenaDecrypt(password, seed); if (typeof indexOrRange === 'number') { return genKeypairFromSeed( diff --git a/packages/libs/hd-wallet/src/SLIP10/kadenaKeyPairsFromRandom.ts b/packages/libs/hd-wallet/src/SLIP10/kadenaKeyPairsFromRandom.ts index 3535069bef..1e42a84203 100644 --- a/packages/libs/hd-wallet/src/SLIP10/kadenaKeyPairsFromRandom.ts +++ b/packages/libs/hd-wallet/src/SLIP10/kadenaKeyPairsFromRandom.ts @@ -1,4 +1,4 @@ -import { randomBytes } from 'crypto'; +import { randomBytes } from '../utils/crypto.js'; import { deriveKeyPair } from './utils/sign.js'; /** * Generates random key pairs without updating the internal state. From 958da15843b7a409e0aff6731e6de5d44bbdb95d Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 12:41:23 +0100 Subject: [PATCH 11/14] stype(kadena-cli): format --- packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts b/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts index 933e08764e..6ad8ae8212 100644 --- a/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts +++ b/packages/tools/kadena-cli/src/keys/tests/keyDecrypt.test.ts @@ -1,4 +1,4 @@ -import { kadenaEncrypt, type EncryptedString } from '@kadena/hd-wallet'; +import type { EncryptedString } from '@kadena/hd-wallet'; import { assert, describe, expect, it } from 'vitest'; import { decrypt } from '../commands/keysDecrypt.js'; From cec764445bb57ac078a8ed36316c322182a76792 Mon Sep 17 00:00:00 2001 From: Javad Khalilian Date: Mon, 22 Jan 2024 12:59:15 +0100 Subject: [PATCH 12/14] refactor(hd-wallet): all ArrayBuffer to Uint8Array --- .../libs/hd-wallet/src/SLIP10/utils/sign.ts | 13 ++++------ packages/libs/hd-wallet/src/utils/crypto.ts | 24 +++++++++++-------- .../hd-wallet/src/utils/kadenaEncryption.ts | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts b/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts index b56bfdf751..5562c443fb 100644 --- a/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts +++ b/packages/libs/hd-wallet/src/SLIP10/utils/sign.ts @@ -10,18 +10,15 @@ export interface ISignatureWithPublicKey { /** * Derive a key pair using a seed and an index. - * @param {ArrayBuffer} seed - The seed for key derivation. + * @param {Uint8Array} seed - The seed for key derivation. * @param {number} index - The index for key derivation. * @returns {{ privateKey: string; publicKey: string }} - Returns the derived private and public keys. */ export const deriveKeyPair = ( - seed: ArrayBuffer, + seed: Uint8Array, derivationPath: string, ): { privateKey: string; publicKey: string } => { - const key = HDKey.fromMasterSeed(new Uint8Array(seed)).derive( - derivationPath, - true, - ); + const key = HDKey.fromMasterSeed(seed).derive(derivationPath, true); return { privateKey: uint8ArrayToHex(key.privateKey), @@ -54,12 +51,12 @@ export const signWithKeyPair = /** * Generate a signer function using a seed and an index. - * @param {ArrayBuffer} seed - The seed for key derivation. + * @param {Uint8Array} seed - The seed for key derivation. * @param {number} index - The index for key derivation. * @returns {(tx: IUnsignedCommand) => { sigs: { sig: string }[] }} - Returns a function that can sign a transaction. */ export const signWithSeed = ( - seed: ArrayBuffer, + seed: Uint8Array, derivationPath: string, ): ((hash: string) => ISignatureWithPublicKey) => { const { publicKey, privateKey } = deriveKeyPair(seed, derivationPath); diff --git a/packages/libs/hd-wallet/src/utils/crypto.ts b/packages/libs/hd-wallet/src/utils/crypto.ts index 2fde8c18f7..680314327d 100644 --- a/packages/libs/hd-wallet/src/utils/crypto.ts +++ b/packages/libs/hd-wallet/src/utils/crypto.ts @@ -1,7 +1,7 @@ export type BinaryLike = string | ArrayBuffer | Uint8Array; export const randomBytes = (size: number) => - crypto.getRandomValues(new Uint8Array(size)).buffer as ArrayBuffer; + crypto.getRandomValues(new Uint8Array(size)); // derive string key async function deriveKey(password: BinaryLike, salt: BinaryLike) { @@ -45,11 +45,13 @@ export async function encrypt( iv: randomBytes(12), } as const; return { - cipherText: await crypto.subtle.encrypt( - algo, - await deriveKey(password, salt), - // new TextEncoder().encode(text) - typeof text === 'string' ? new TextEncoder().encode(text) : text, + cipherText: new Uint8Array( + await crypto.subtle.encrypt( + algo, + await deriveKey(password, salt), + // new TextEncoder().encode(text) + typeof text === 'string' ? new TextEncoder().encode(text) : text, + ), ), iv: algo.iv, }; @@ -68,9 +70,11 @@ export async function decrypt( length: 256, iv: encrypted.iv, }; - return await crypto.subtle.decrypt( - algo, - await deriveKey(password, salt), - encrypted.cipherText, + return new Uint8Array( + await crypto.subtle.decrypt( + algo, + await deriveKey(password, salt), + encrypted.cipherText, + ), ); } diff --git a/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts b/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts index 056c90fa39..8682f92902 100644 --- a/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts +++ b/packages/libs/hd-wallet/src/utils/kadenaEncryption.ts @@ -104,7 +104,7 @@ export async function kadenaChangePassword( ); } - let decryptedPrivateKey: ArrayBuffer; + let decryptedPrivateKey: Uint8Array; try { decryptedPrivateKey = await kadenaDecrypt(password, encryptedData); } catch (error) { From a4b1730bcb0d0938de07ef9367ee27637739bacd Mon Sep 17 00:00:00 2001 From: MRVDH Date: Fri, 19 Jan 2024 16:17:17 +0100 Subject: [PATCH 13/14] [@kadena/graph] Add missing query params to Prisma calls --- packages/apps/graph/src/graph/objects/block.ts | 6 +++++- .../apps/graph/src/graph/query/completed-block-heights.ts | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/apps/graph/src/graph/objects/block.ts b/packages/apps/graph/src/graph/objects/block.ts index 391a3cc6b1..e64298c059 100644 --- a/packages/apps/graph/src/graph/objects/block.ts +++ b/packages/apps/graph/src/graph/objects/block.ts @@ -54,6 +54,9 @@ export default builder.prismaNode('Block', { where: { blockHash: parent.hash, }, + select: { + key: true, + }, }) )?.map((x) => x.key), predicate: parent.predicate as Guard['predicate'], @@ -72,9 +75,10 @@ export default builder.prismaNode('Block', { select: { parentBlockHash: true, }, - async resolve(__query, parent) { + async resolve(query, parent) { try { return await prismaClient.block.findUnique({ + ...query, where: { hash: parent.parentBlockHash, }, diff --git a/packages/apps/graph/src/graph/query/completed-block-heights.ts b/packages/apps/graph/src/graph/query/completed-block-heights.ts index cb402b2edd..01752040fe 100644 --- a/packages/apps/graph/src/graph/query/completed-block-heights.ts +++ b/packages/apps/graph/src/graph/query/completed-block-heights.ts @@ -24,7 +24,7 @@ builder.queryField('completedBlockHeights', (t) => (args.heightCount as number) * // heightCount has a default value so cannot be null. Bug in pothos. 4, // In the worst case resolve scenario, it executes 4 queries. }), - async resolve(__query, __parent, args) { + async resolve(query, __parent, args) { try { if (args.completedHeights) { const completedHeights = (await prismaClient.$queryRaw` @@ -39,6 +39,7 @@ builder.queryField('completedBlockHeights', (t) => if (completedHeights.length > 0) { return prismaClient.block.findMany({ + ...query, where: { AND: [ { @@ -77,6 +78,7 @@ builder.queryField('completedBlockHeights', (t) => `) as { height: number }[]; return await prismaClient.block.findMany({ + ...query, where: { AND: [ { From efa7b79fab07db9140df4b5c6070cd4f8041bb27 Mon Sep 17 00:00:00 2001 From: MRVDH Date: Fri, 19 Jan 2024 16:19:41 +0100 Subject: [PATCH 14/14] changeset --- .changeset/long-brooms-switch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/long-brooms-switch.md diff --git a/.changeset/long-brooms-switch.md b/.changeset/long-brooms-switch.md new file mode 100644 index 0000000000..d155dfed53 --- /dev/null +++ b/.changeset/long-brooms-switch.md @@ -0,0 +1,5 @@ +--- +'@kadena/graph': patch +--- + +Add missing query params to Prisma calls