From 88b9fc6ddc37552b71829e2c3ba7910aabd248a7 Mon Sep 17 00:00:00 2001 From: Tom Wang Date: Wed, 12 Jun 2024 00:09:41 +0800 Subject: [PATCH 1/3] refactor: replace Buffer with Uint8Array --- packages/base/src/utils.ts | 2 +- packages/base/src/values.ts | 2 +- packages/base/tests/utils.test.js | 3 +- .../codec/examples/custom-codec-utf8string.ts | 5 +- packages/codec/examples/rpc-character.ts | 4 +- .../common-scripts/examples/pw_lock/lock.ts | 12 +- packages/common-scripts/package.json | 3 +- packages/common-scripts/src/common.ts | 6 +- packages/common-scripts/tests/p2pkh.test.ts | 6 +- packages/common-scripts/tests/sudt.test.ts | 4 +- packages/debugger/src/executor.ts | 3 +- packages/hd/package.json | 1 + packages/hd/src/extended_key.ts | 40 +-- packages/hd/src/key.ts | 33 +- packages/hd/src/keychain.ts | 63 ++-- packages/hd/src/keystore.ts | 91 +++--- packages/hd/src/mnemonic/index.ts | 45 +-- packages/hd/tests/key.test.ts | 27 +- packages/hd/tests/keychain.test.ts | 288 ++++++++---------- packages/hd/tests/keystore.test.ts | 7 +- packages/joyid/tests/script-info.test.ts | 2 +- pnpm-lock.yaml | 31 +- 22 files changed, 318 insertions(+), 360 deletions(-) diff --git a/packages/base/src/utils.ts b/packages/base/src/utils.ts index 7c269e4a2..8bfc2be44 100644 --- a/packages/base/src/utils.ts +++ b/packages/base/src/utils.ts @@ -70,7 +70,7 @@ function computeScriptHash(script: Script): string { return ckbHash(blockchain.Script.pack(script)); } -function hashCode(buffer: Buffer): number { +function hashCode(buffer: Uint8Array): number { return xxHash32(buffer, 0); } diff --git a/packages/base/src/values.ts b/packages/base/src/values.ts index 480a6622c..e85154016 100644 --- a/packages/base/src/values.ts +++ b/packages/base/src/values.ts @@ -16,7 +16,7 @@ class Value { } hashCode(): number { - return xxHash32(Buffer.from(this.buffer), 0); + return xxHash32(this.buffer, 0); } hash(): Hash { diff --git a/packages/base/tests/utils.test.js b/packages/base/tests/utils.test.js index 963170f19..44e80c8ec 100644 --- a/packages/base/tests/utils.test.js +++ b/packages/base/tests/utils.test.js @@ -1,4 +1,5 @@ const test = require("ava"); +const { bytes } = require("@ckb-lumos/codec"); const { CKBHasher, @@ -63,7 +64,7 @@ test("computeScriptHash", (t) => { }); test("hashCode, should return same hash if same input", (t) => { - const buffer = Buffer.from("1234ab", "hex"); + const buffer = bytes.bytify("0x1234ab"); t.is(hashCode(buffer), hashCode(buffer)); }); diff --git a/packages/codec/examples/custom-codec-utf8string.ts b/packages/codec/examples/custom-codec-utf8string.ts index ad046b771..eaf8ccca7 100644 --- a/packages/codec/examples/custom-codec-utf8string.ts +++ b/packages/codec/examples/custom-codec-utf8string.ts @@ -1,9 +1,8 @@ import { molecule, bytes } from "../src"; -import { Buffer } from "buffer"; // https://github.com/feross/buffer const UTF8String = molecule.byteVecOf({ - pack: (str) => Buffer.from(str, "utf8"), - unpack: (buf) => Buffer.from(bytes.bytify(buf)).toString("utf8"), + pack: (str) => new TextEncoder().encode(str), + unpack: (buf) => new TextDecoder().decode(bytes.bytify(buf)), }); const packed = UTF8String.pack("hello world, 你好世界"); diff --git a/packages/codec/examples/rpc-character.ts b/packages/codec/examples/rpc-character.ts index 0d1a60fb8..bb035d19c 100644 --- a/packages/codec/examples/rpc-character.ts +++ b/packages/codec/examples/rpc-character.ts @@ -65,8 +65,8 @@ const SwordMaterial = byteOf({ }); const UTF8String = byteVecOf({ - pack: (str) => Uint8Array.from(Buffer.from(str, "utf8")), - unpack: (buf) => Buffer.from(bytes.bytify(buf)).toString("utf8"), + pack: (str) => new TextEncoder().encode(str), + unpack: (buf) => new TextDecoder().decode(bytes.bytify(buf)), }); /***** molecule binding *****/ diff --git a/packages/common-scripts/examples/pw_lock/lock.ts b/packages/common-scripts/examples/pw_lock/lock.ts index b6822aff2..8af8ff749 100644 --- a/packages/common-scripts/examples/pw_lock/lock.ts +++ b/packages/common-scripts/examples/pw_lock/lock.ts @@ -18,11 +18,13 @@ import { TransactionSkeletonType, createTransactionFromSkeleton, } from "@ckb-lumos/helpers"; +import { bytes } from "@ckb-lumos/codec"; +import { keccak256 } from "@ckb-lumos/crypto" import { getConfig, Config, initializeConfig } from "@ckb-lumos/config-manager"; import { Set } from "immutable"; -import keccak, { Keccak } from "keccak"; const { ScriptValue } = values; +const { bytify, hexify } = bytes; // https://github.com/lay2dev/pw-lock/commit/b447c2bb3f855e933e36212b45af4dec92adf705 pw-lock is a lock script which uses secp256k1_keccak256 algorithm. @@ -238,21 +240,21 @@ async function setupInputCell( // It's a secp256k1_keccak256 sighash all lock script, so we need a keccak256 hash method. class Keccak256Hasher { - private hasher: Keccak; + private hasher: ReturnType; constructor() { - this.hasher = keccak("keccak256"); + this.hasher = keccak256.create(); } update(data: string | ArrayBuffer | Reader): this { const reader = new Reader(data); - const array: Buffer = Buffer.from(reader.serializeJson().slice(2), "hex"); + const array = bytify(reader.serializeJson()); this.hasher.update(array); return this; } digestReader(): Reader { - const hex = "0x" + this.hasher.digest("hex").toString(); + const hex = hexify(this.hasher.digest()) return new Reader(hex); } diff --git a/packages/common-scripts/package.json b/packages/common-scripts/package.json index 656abea91..c54dddbf5 100644 --- a/packages/common-scripts/package.json +++ b/packages/common-scripts/package.json @@ -22,6 +22,7 @@ "@ckb-lumos/base": "0.23.0", "@ckb-lumos/bi": "0.23.0", "@ckb-lumos/codec": "0.23.0", + "@ckb-lumos/crypto": "0.23.0", "@ckb-lumos/config-manager": "0.23.0", "@ckb-lumos/helpers": "0.23.0", "@ckb-lumos/rpc": "0.23.0", @@ -58,9 +59,7 @@ "@ckb-lumos/crypto": "0.23.0", "@ckb-lumos/debugger": "0.23.0", "@ckb-lumos/hd": "0.23.0", - "@types/keccak": "^3.0.1", "@unisat/wallet-sdk": "^1.1.2", - "keccak": "^3.0.1", "tweetnacl": "^1.0.3" }, "publishConfig": { diff --git a/packages/common-scripts/src/common.ts b/packages/common-scripts/src/common.ts index 3ec93aff9..96606398b 100644 --- a/packages/common-scripts/src/common.ts +++ b/packages/common-scripts/src/common.ts @@ -207,7 +207,7 @@ function generateLockScriptInfos({ config = undefined }: Options = {}): void { }; const configHashCode: number = utils.hashCode( - Buffer.from(JSON.stringify(config)) + new TextEncoder().encode(JSON.stringify(config)) ); if (lockScriptInfos.infos.length === 0) { @@ -931,8 +931,8 @@ function getTransactionSize(txSkeleton: TransactionSkeletonType): number { function getTransactionSizeByTx(tx: Transaction): number { const serializedTx = blockchain.Transaction.pack(tx); - // 4 is serialized offset bytesize - const size = serializedTx.byteLength + 4; + const offset = 4; // 4 is serialized offset bytesize + const size = serializedTx.byteLength + offset; return size; } diff --git a/packages/common-scripts/tests/p2pkh.test.ts b/packages/common-scripts/tests/p2pkh.test.ts index 813201ba3..5541dd02b 100644 --- a/packages/common-scripts/tests/p2pkh.test.ts +++ b/packages/common-scripts/tests/p2pkh.test.ts @@ -1,6 +1,6 @@ import test from "ava"; import { Script, utils } from "@ckb-lumos/base"; -import { default as createKeccak } from "keccak"; +import { keccak256 } from "@ckb-lumos/crypto"; import { createP2PKHMessageGroup } from "../src/p2pkh"; import { txObject, txSkeletonFromJson } from "./helper"; import p2pkhJson from "./p2pkh.json"; @@ -35,13 +35,13 @@ test("pw lock [g1]", (t) => { SIGNATURE_PLACEHOLDER ); - const keccak = createKeccak("keccak256"); + const keccak = keccak256.create(); const signLock = p2pkhJson["PW_LOCK_[G1]"].SIGN_LOCK as Script; const messageGroup = createP2PKHMessageGroup(tx, [signLock], { hasher: { update: (message) => { - keccak.update(Buffer.from(new Uint8Array(message))); + keccak.update(new Uint8Array(message)); }, digest: () => keccak.digest(), }, diff --git a/packages/common-scripts/tests/sudt.test.ts b/packages/common-scripts/tests/sudt.test.ts index 2b5bd2c15..c4baa90fb 100644 --- a/packages/common-scripts/tests/sudt.test.ts +++ b/packages/common-scripts/tests/sudt.test.ts @@ -667,9 +667,9 @@ test("transfer secp => secp, change to acp and has previous output, split change test("pack and unpack sudt amount", (t) => { const unpacked = BI.from(0x1234); - const packed = Buffer.alloc(16); + const packed = new Uint8Array(16); // little endian of 0x1234 - packed.write("3412", "hex"); + packed.set([0x34, 0x12]); t.true(bytes.equal(packAmount(unpacked), packed)); t.true(unpackAmount(packed).eq(unpacked)); diff --git a/packages/debugger/src/executor.ts b/packages/debugger/src/executor.ts index 4865d4cf0..4c0e43f0f 100644 --- a/packages/debugger/src/executor.ts +++ b/packages/debugger/src/executor.ts @@ -1,6 +1,7 @@ import { DataLoader, ExecuteResult, Executor } from "./types"; import { TransactionSkeletonType } from "@ckb-lumos/helpers"; import { randomBytes } from "@ckb-lumos/crypto"; +import { bytes } from "@ckb-lumos/codec"; import { spawnSync } from "child_process"; import { Hash } from "@ckb-lumos/base"; import * as fs from "fs"; @@ -52,7 +53,7 @@ export class CKBDebugger implements Executor { private saveTmpTxFile(txSkeleton: TransactionSkeletonType): string { const debuggerData = parseDebuggerData(txSkeleton, this.loader); // eslint-disable-next-line @typescript-eslint/no-magic-numbers - const randomHex = Buffer.from(randomBytes(18)).toString("hex"); + const randomHex = bytes.hexify(randomBytes(18)).slice(2); const tempFileName = `lumos-debugger-data-${randomHex}`; const tmpTxPath = path.join(os.tmpdir(), `${tempFileName}.json`); fs.writeFileSync(tmpTxPath, JSON.stringify(debuggerData)); diff --git a/packages/hd/package.json b/packages/hd/package.json index ff8832e8f..5e6dab3f1 100644 --- a/packages/hd/package.json +++ b/packages/hd/package.json @@ -21,6 +21,7 @@ "dependencies": { "@ckb-lumos/base": "0.23.0", "@ckb-lumos/bi": "0.23.0", + "@ckb-lumos/codec": "0.23.0", "@ckb-lumos/crypto": "0.23.0", "bn.js": "^5.1.3", "elliptic": "^6.5.4", diff --git a/packages/hd/src/extended_key.ts b/packages/hd/src/extended_key.ts index 1ca2d6908..8ac0f803a 100644 --- a/packages/hd/src/extended_key.ts +++ b/packages/hd/src/extended_key.ts @@ -1,8 +1,12 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers */ import Keychain from "./keychain"; -import key, { privateToPublic } from "./key"; +import { bytes } from "@ckb-lumos/codec"; import { utils, HexString } from "@ckb-lumos/base"; +import { privateToPublic, publicKeyToBlake160 } from "./key"; import { assertPublicKey, assertChainCode, assertPrivateKey } from "./helper"; +const { bytify, hexify } = bytes; + export enum AddressType { Receiving = 0, Change = 1, @@ -55,7 +59,7 @@ export class AccountExtendedPublicKey extends ExtendedPublicKey { publicKeyInfo(type: AddressType, index: number): PublicKeyInfo { const publicKey: string = this.getPublicKey(type, index); - const blake160: string = key.publicKeyToBlake160(publicKey); + const blake160: string = publicKeyToBlake160(publicKey); return { publicKey, blake160, @@ -63,11 +67,11 @@ export class AccountExtendedPublicKey extends ExtendedPublicKey { }; } - public static pathForReceiving(index: number) { + public static pathForReceiving(index: number): string { return AccountExtendedPublicKey.pathFor(AddressType.Receiving, index); } - public static pathForChange(index: number) { + public static pathForChange(index: number): string { return AccountExtendedPublicKey.pathFor(AddressType.Change, index); } @@ -77,14 +81,14 @@ export class AccountExtendedPublicKey extends ExtendedPublicKey { private getPublicKey(type = AddressType.Receiving, index: number): HexString { const keychain = Keychain.fromPublicKey( - Buffer.from(this.publicKey.slice(2), "hex"), - Buffer.from(this.chainCode.slice(2), "hex"), + bytify(this.publicKey), + bytify(this.chainCode), AccountExtendedPublicKey.ckbAccountPath ) .deriveChild(type, false) .deriveChild(index, false); - return "0x" + keychain.publicKey.toString("hex"); + return hexify(keychain.publicKey); } } @@ -117,24 +121,24 @@ export class ExtendedPrivateKey { toAccountExtendedPublicKey(): AccountExtendedPublicKey { const masterKeychain = new Keychain( - Buffer.from(this.privateKey.slice(2), "hex"), - Buffer.from(this.chainCode.slice(2), "hex") + bytify(this.privateKey), + bytify(this.chainCode) ); const accountKeychain = masterKeychain.derivePath( AccountExtendedPublicKey.ckbAccountPath ); return new AccountExtendedPublicKey( - "0x" + accountKeychain.publicKey.toString("hex"), - "0x" + accountKeychain.chainCode.toString("hex") + hexify(accountKeychain.publicKey), + hexify(accountKeychain.chainCode) ); } - static fromSeed(seed: Buffer): ExtendedPrivateKey { + static fromSeed(seed: Uint8Array): ExtendedPrivateKey { const keychain = Keychain.fromSeed(seed); return new ExtendedPrivateKey( - "0x" + keychain.privateKey.toString("hex"), - "0x" + keychain.chainCode.toString("hex") + hexify(keychain.privateKey), + hexify(keychain.chainCode) ); } @@ -145,8 +149,8 @@ export class ExtendedPrivateKey { privateKeyInfoByPath(path: string): PrivateKeyInfo { const keychain = new Keychain( - Buffer.from(this.privateKey.slice(2), "hex"), - Buffer.from(this.chainCode.slice(2), "hex") + bytify(this.privateKey), + bytify(this.chainCode) ).derivePath(path); return this.privateKeyInfoFromKeychain(keychain, path); @@ -157,8 +161,8 @@ export class ExtendedPrivateKey { path: string ): PrivateKeyInfo { return { - privateKey: "0x" + keychain.privateKey.toString("hex"), - publicKey: "0x" + keychain.publicKey.toString("hex"), + privateKey: hexify(keychain.privateKey), + publicKey: hexify(keychain.publicKey), path: path, }; } diff --git a/packages/hd/src/key.ts b/packages/hd/src/key.ts index 2aa62b654..91da9acd5 100644 --- a/packages/hd/src/key.ts +++ b/packages/hd/src/key.ts @@ -1,7 +1,10 @@ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import { bytes } from "@ckb-lumos/codec"; import { HexString, utils } from "@ckb-lumos/base"; import { ec as EC, SignatureInput } from "elliptic"; import { assertPrivateKey, assertPublicKey } from "./helper"; +const { bytify } = bytes; const ec = new EC("secp256k1"); export function signRecoverable( @@ -31,40 +34,40 @@ export function recoverFromSignature( utils.assertHexString("message", message); utils.assertHexString("signature", signature); - const msgBuffer = Buffer.from(message.slice(2), "hex"); - const sigBuffer = Buffer.from(signature.slice(2), "hex"); + const msg = bytify(message); + const sig = bytify(signature); const sign: SignatureInput = { - r: sigBuffer.slice(0, 32), - s: sigBuffer.slice(32, 64), - recoveryParam: sigBuffer[64], + r: sig.slice(0, 32), + s: sig.slice(32, 64), + recoveryParam: sig[64], }; - const point = ec.recoverPubKey(msgBuffer, sign, sign.recoveryParam!); + const point = ec.recoverPubKey(msg, sign, sign.recoveryParam!); const publicKey = "0x" + point.encode("hex", true).toLowerCase(); return publicKey; } -export function privateToPublic(privateKey: Buffer): Buffer; export function privateToPublic(privateKey: HexString): HexString; +export function privateToPublic(privateKey: Uint8Array): Uint8Array; export function privateToPublic( - privateKey: Buffer | HexString -): Buffer | HexString { - let pkBuffer = privateKey; + privateKey: Uint8Array | HexString +): Uint8Array | HexString { + let pk = privateKey; if (typeof privateKey === "string") { assertPrivateKey(privateKey); - pkBuffer = Buffer.from(privateKey.slice(2), "hex"); + pk = bytify(privateKey); } - if (pkBuffer.length !== 32) { + if (pk.length !== 32) { throw new Error("Private key must be 32 bytes!"); } - const publickey = ec.keyFromPrivate(pkBuffer).getPublic(true, "hex"); + const publickey = "0x" + ec.keyFromPrivate(pk).getPublic(true, "hex"); if (typeof privateKey === "string") { - return "0x" + publickey; + return publickey; } - return Buffer.from(publickey, "hex"); + return bytify(publickey); } export function publicKeyToBlake160(publicKey: HexString): HexString { diff --git a/packages/hd/src/keychain.ts b/packages/hd/src/keychain.ts index 5e147034d..bc5e7bb84 100644 --- a/packages/hd/src/keychain.ts +++ b/packages/hd/src/keychain.ts @@ -1,25 +1,29 @@ /* eslint-disable @typescript-eslint/no-magic-numbers */ import BN from "bn.js"; import { ec as EC } from "elliptic"; +import { bytes } from "@ckb-lumos/codec"; +import { Uint32BE } from "@ckb-lumos/codec/lib/number"; import { hmac, sha256, sha512, ripemd160 } from "@ckb-lumos/crypto"; import { privateToPublic } from "./key"; +const { concat } = bytes; + const ec = new EC("secp256k1"); -const EMPTY_BUFFER = Buffer.from(""); +const EMPTY_BUFFER = Uint8Array.from([]); // BIP32 Keychain. Not a full implementation. export default class Keychain { - privateKey: Buffer = EMPTY_BUFFER; - publicKey: Buffer = EMPTY_BUFFER; - chainCode: Buffer = EMPTY_BUFFER; + privateKey: Uint8Array = EMPTY_BUFFER; + publicKey: Uint8Array = EMPTY_BUFFER; + chainCode: Uint8Array = EMPTY_BUFFER; index = 0; depth = 0; - identifier: Buffer = EMPTY_BUFFER; + identifier: Uint8Array = EMPTY_BUFFER; fingerprint = 0; parentFingerprint = 0; - constructor(privateKey: Buffer, chainCode: Buffer) { + constructor(privateKey: Uint8Array, chainCode: Uint8Array) { this.privateKey = privateKey; this.chainCode = chainCode; @@ -30,13 +34,11 @@ export default class Keychain { calculateFingerprint(): void { this.identifier = this.hash160(this.publicKey); - this.fingerprint = this.identifier.slice(0, 4).readUInt32BE(0); + this.fingerprint = Uint32BE.unpack(this.identifier.slice(0, 4)); } - public static fromSeed(seed: Buffer): Keychain { - const i = Buffer.from( - hmac(sha512, Buffer.from("Bitcoin seed", "utf8"), seed) - ); + public static fromSeed(seed: Uint8Array): Keychain { + const i = hmac(sha512, new TextEncoder().encode("Bitcoin seed"), seed); const keychain = new Keychain(i.slice(0, 32), i.slice(32)); keychain.calculateFingerprint(); return keychain; @@ -45,8 +47,8 @@ export default class Keychain { // Create a child keychain with extended public key and path. // Children of this keychain should not have any hardened paths. public static fromPublicKey( - publicKey: Buffer, - chainCode: Buffer, + publicKey: Uint8Array, + chainCode: Uint8Array, path: string ): Keychain { const keychain = new Keychain(EMPTY_BUFFER, chainCode); @@ -61,20 +63,21 @@ export default class Keychain { } public deriveChild(index: number, hardened: boolean): Keychain { - let data: Buffer; + let data: Uint8Array; - const indexBuffer: Buffer = Buffer.allocUnsafe(4); + const ui8a: Uint8Array = new Uint8Array(4); + const view = new DataView(ui8a.buffer); if (hardened) { - const pk = Buffer.concat([Buffer.alloc(1, 0), this.privateKey]); - indexBuffer.writeUInt32BE(index + 0x80000000, 0); - data = Buffer.concat([pk, indexBuffer]); + const pk = concat([0], this.privateKey); + view.setUint32(0, index + 0x80000000); + data = concat(pk, ui8a); } else { - indexBuffer.writeUInt32BE(index, 0); - data = Buffer.concat([this.publicKey, indexBuffer]); + view.setUint32(0, index); + data = concat(this.publicKey, ui8a); } - const i = Buffer.from(hmac(sha512, this.chainCode, data)); + const i = hmac(sha512, this.chainCode, data); const il = i.slice(0, 32); const ir = i.slice(32); @@ -121,27 +124,33 @@ export default class Keychain { return this.privateKey === EMPTY_BUFFER; } - hash160(data: Buffer): Buffer { - return Buffer.from(ripemd160(sha256(data))); + hash160(data: Uint8Array): Uint8Array { + return ripemd160(sha256(data)); } - private static privateKeyAdd(privateKey: Buffer, factor: Buffer): Buffer { + private static privateKeyAdd( + privateKey: Uint8Array, + factor: Uint8Array + ): Uint8Array { const result = new BN(factor); result.iadd(new BN(privateKey)); if (result.cmp(ec.curve.n) >= 0) { result.isub(ec.curve.n); } - return result.toArrayLike(Buffer, "be", 32); + return Uint8Array.from(result.toArray("be", 32)); } - private static publicKeyAdd(publicKey: Buffer, factor: Buffer): Buffer { + private static publicKeyAdd( + publicKey: Uint8Array, + factor: Uint8Array + ): Uint8Array { const x = new BN(publicKey.slice(1)).toRed(ec.curve.red); let y = x.redSqr().redIMul(x).redIAdd(ec.curve.b).redSqrt(); if ((publicKey[0] === 0x03) !== y.isOdd()) { y = y.redNeg(); } const point = ec.curve.g.mul(new BN(factor)).add({ x, y }); - return Buffer.from(point.encode(true, true)); + return Uint8Array.from(point.encode(true, true)); } } diff --git a/packages/hd/src/keystore.ts b/packages/hd/src/keystore.ts index 244eaabe3..179d11c7c 100644 --- a/packages/hd/src/keystore.ts +++ b/packages/hd/src/keystore.ts @@ -1,8 +1,11 @@ import { v4 as uuid } from "uuid"; -import { ExtendedPrivateKey } from "./extended_key"; -import { ctr, keccak256, randomBytes } from "@ckb-lumos/crypto"; +import { bytes } from "@ckb-lumos/codec"; import { HexString } from "@ckb-lumos/base"; +import { ctr, keccak256, randomBytes } from "@ckb-lumos/crypto"; import { syncScrypt } from "scrypt-js"; +import { ExtendedPrivateKey } from "./extended_key"; + +const { bytify, concat, hexify } = bytes; export type HexStringWithoutPrefix = string; @@ -100,11 +103,11 @@ export default class Keystore { // Create an empty keystore object that contains empty private key static createEmpty(): Keystore { const saltSize = 32; - const salt: Buffer = Buffer.from(randomBytes(saltSize)); - const iv: Buffer = Buffer.from(randomBytes(16)); + const salt: Uint8Array = randomBytes(saltSize); + const iv: Uint8Array = randomBytes(16); const kdfparams: KdfParams = { dklen: 32, - salt: salt.toString("hex"), + salt: hexify(salt).slice(2), n: DEFAULT_SCRYPT_PARAM_N, r: DEFAULT_SCRIPT_PARAM_r, p: DEFAULT_SCRIPT_PARAM_p, @@ -113,7 +116,7 @@ export default class Keystore { { ciphertext: "", cipherparams: { - iv: iv.toString("hex"), + iv: hexify(iv).slice(2), }, cipher: CIPHER, kdf: "scrypt", @@ -127,47 +130,42 @@ export default class Keystore { static create( extendedPrivateKey: ExtendedPrivateKey, password: string, - options: { salt?: Buffer; iv?: Buffer } = {} + options: { salt?: Uint8Array; iv?: Uint8Array } = {} ): Keystore { const saltSize = 32; const ivSize = 16; - const salt: Buffer = options.salt || Buffer.from(randomBytes(saltSize)); - const iv: Buffer = options.iv || Buffer.from(randomBytes(ivSize)); + const salt: Uint8Array = options.salt || randomBytes(saltSize); + const iv: Uint8Array = options.iv || randomBytes(ivSize); const kdfparams: KdfParams = { dklen: 32, - salt: salt.toString("hex"), + salt: hexify(salt).slice(2), n: DEFAULT_SCRYPT_PARAM_N, r: DEFAULT_SCRIPT_PARAM_r, p: DEFAULT_SCRIPT_PARAM_p, }; - const derivedKey: Buffer = Buffer.from( - syncScrypt( - Buffer.from(password), - salt, - kdfparams.n, - kdfparams.r, - kdfparams.p, - kdfparams.dklen - ) + const derivedKey = syncScrypt( + new TextEncoder().encode(password), + salt, + kdfparams.n, + kdfparams.r, + kdfparams.p, + kdfparams.dklen ); // DO NOT remove the Uint8Array.from call below. // Without calling Uint8Array.from to make a copy of iv, // iv will be set to 0000...00000 after calling cipher.encrypt(plaintext) // and decrypting the ciphertext will fail - /* eslint-disable @typescript-eslint/no-magic-numbers */ + /* eslint-disable-next-line @typescript-eslint/no-magic-numbers */ const cipher = ctr(derivedKey.slice(0, 16), Uint8Array.from(iv)); - const plaintext = Buffer.from( - extendedPrivateKey.serialize().slice(2), - "hex" - ); - const ciphertext = Buffer.from(cipher.encrypt(plaintext)); + const plaintext = bytify(extendedPrivateKey.serialize()); + const ciphertext = cipher.encrypt(plaintext); return new Keystore( { - ciphertext: ciphertext.toString("hex"), + ciphertext: hexify(ciphertext).slice(2), cipherparams: { - iv: iv.toString("hex"), + iv: hexify(iv).slice(2), }, cipher: CIPHER, kdf: "scrypt", @@ -186,17 +184,17 @@ export default class Keystore { // Decrypt and return serialized extended private key. decrypt(password: string): HexString { const derivedKey = this.derivedKey(password); - const ciphertext = Buffer.from(this.crypto.ciphertext, "hex"); + const ciphertext = bytify("0x" + this.crypto.ciphertext); if (Keystore.mac(derivedKey, ciphertext) !== this.crypto.mac) { throw new IncorrectPassword(); } - /* eslint-disable @typescript-eslint/no-magic-numbers */ + /* eslint-disable-next-line @typescript-eslint/no-magic-numbers */ const cipher = ctr( derivedKey.slice(0, 16), - Buffer.from(this.crypto.cipherparams.iv, "hex") + bytify("0x" + this.crypto.cipherparams.iv) ); - return "0x" + Buffer.from(cipher.decrypt(ciphertext)).toString("hex"); + return hexify(cipher.decrypt(ciphertext)); } extendedPrivateKey(password: string): ExtendedPrivateKey { @@ -205,31 +203,30 @@ export default class Keystore { checkPassword(password: string): boolean { const derivedKey = this.derivedKey(password); - const ciphertext = Buffer.from(this.crypto.ciphertext, "hex"); + const ciphertext = bytify("0x" + this.crypto.ciphertext); return Keystore.mac(derivedKey, ciphertext) === this.crypto.mac; } - derivedKey(password: string): Buffer { + derivedKey(password: string): Uint8Array { const { kdfparams } = this.crypto; - return Buffer.from( - syncScrypt( - Buffer.from(password), - Buffer.from(kdfparams.salt, "hex"), - kdfparams.n, - kdfparams.r, - kdfparams.p, - kdfparams.dklen - ) + return syncScrypt( + new TextEncoder().encode(password), + bytify("0x" + kdfparams.salt), + kdfparams.n, + kdfparams.r, + kdfparams.p, + kdfparams.dklen ); } - static mac(derivedKey: Buffer, ciphertext: Buffer): HexStringWithoutPrefix { + static mac( + derivedKey: Uint8Array, + ciphertext: Uint8Array + ): HexStringWithoutPrefix { // https://github.com/ethereumjs/ethereumjs-wallet/blob/d57582443fbac2b63956e6d5c4193aa8ce925b3d/src/index.ts#L615-L617 // eslint-disable-next-line @typescript-eslint/no-magic-numbers - const hash = keccak256( - Buffer.concat([derivedKey.subarray(16, 32), ciphertext]) - ); - return Buffer.from(hash).toString("hex"); + const hash = keccak256(concat(derivedKey.subarray(16, 32), ciphertext)); + return hexify(hash).slice(2); } static scryptOptions(kdfparams: KdfParams): ScryptOptions { diff --git a/packages/hd/src/mnemonic/index.ts b/packages/hd/src/mnemonic/index.ts index 1df4e4f24..7f679ae47 100644 --- a/packages/hd/src/mnemonic/index.ts +++ b/packages/hd/src/mnemonic/index.ts @@ -6,9 +6,12 @@ import { pbkdf2Async, randomBytes, } from "@ckb-lumos/crypto"; +import { bytes } from "@ckb-lumos/codec"; import { HexString } from "@ckb-lumos/base"; import wordList from "./word_list"; +const { bytify, hexify } = bytes; + const RADIX = 2048; const PBKDF2_ROUNDS = 2048; const KEY_LEN = 64; @@ -35,16 +38,16 @@ if (wordList.length !== RADIX) { ); } -function bytesToBinary(bytes: Buffer): string { +function bytesToBinary(bytes: Uint8Array): string { return bytes.reduce((binary, byte) => { return binary + byte.toString(2).padStart(8, "0"); }, ""); } -function deriveChecksumBits(entropyBuffer: Buffer): string { +function deriveChecksumBits(entropyBuffer: Uint8Array): string { const ENT = entropyBuffer.length * 8; const CS = ENT / 32; - const hash = Buffer.from(sha256(entropyBuffer)); + const hash = sha256(entropyBuffer); return bytesToBinary(hash).slice(0, CS); } @@ -52,24 +55,25 @@ function salt(password = ""): string { return `mnemonic${password}`; } -export function mnemonicToSeedSync(mnemonic = "", password = ""): Buffer { - const mnemonicBuffer = Buffer.from(mnemonic.normalize("NFKD"), "utf8"); - const saltBuffer = Buffer.from(salt(password.normalize("NFKD")), "utf8"); - return Buffer.from( - pbkdf2(sha512, mnemonicBuffer, saltBuffer, { - c: PBKDF2_ROUNDS, - dkLen: KEY_LEN, - }) - ); +export function mnemonicToSeedSync(mnemonic = "", password = ""): Uint8Array { + const mnemonicBuffer = new TextEncoder().encode(mnemonic.normalize("NFKD")); + const saltBuffer = new TextEncoder().encode(salt(password.normalize("NFKD"))); + return pbkdf2(sha512, mnemonicBuffer, saltBuffer, { + c: PBKDF2_ROUNDS, + dkLen: KEY_LEN, + }); } -export function mnemonicToSeed(mnemonic = "", password = ""): Promise { - const mnemonicBuffer = Buffer.from(mnemonic.normalize("NFKD"), "utf8"); - const saltBuffer = Buffer.from(salt(password.normalize("NFKD")), "utf8"); +export function mnemonicToSeed( + mnemonic = "", + password = "" +): Promise { + const mnemonicBuffer = new TextEncoder().encode(mnemonic.normalize("NFKD")); + const saltBuffer = new TextEncoder().encode(salt(password.normalize("NFKD"))); return pbkdf2Async(sha512, mnemonicBuffer, saltBuffer, { c: PBKDF2_ROUNDS, dkLen: KEY_LEN, - }).then(Buffer.from); + }); } export function mnemonicToEntropy(mnemonic = ""): HexString { @@ -110,17 +114,17 @@ export function mnemonicToEntropy(mnemonic = ""): HexString { throw new Error(ENTROPY_NOT_DIVISIBLE); } - const entropy = Buffer.from(entropyBytes); + const entropy = Uint8Array.from(entropyBytes); const newChecksum = deriveChecksumBits(entropy); if (newChecksum !== checksumBits) { throw new Error(INVALID_CHECKSUM); } - return "0x" + entropy.toString("hex"); + return hexify(entropy); } export function entropyToMnemonic(entropyStr: HexString): string { - const entropy = Buffer.from(entropyStr.slice(2), "hex"); + const entropy = bytify(entropyStr); if (entropy.length < MIN_ENTROPY_SIZE) { throw new TypeError(ENTROPY_TOO_SHORT); @@ -157,8 +161,7 @@ export function validateMnemonic(mnemonic: string): boolean { // Generate 12 words mnemonic code export function generateMnemonic(): string { const entropySize = 16; - const entropy: HexString = - "0x" + Buffer.from(randomBytes(entropySize)).toString("hex"); + const entropy = hexify(randomBytes(entropySize)); return entropyToMnemonic(entropy); } diff --git a/packages/hd/tests/key.test.ts b/packages/hd/tests/key.test.ts index 5df4115d6..e3df20d18 100644 --- a/packages/hd/tests/key.test.ts +++ b/packages/hd/tests/key.test.ts @@ -1,5 +1,6 @@ import test from "ava"; import { key } from "../src"; +import { bytes } from "@ckb-lumos/codec"; const { signRecoverable, recoverFromSignature, @@ -29,14 +30,12 @@ test("recoverFromMessage", (t) => { t.is(publicKey, signInfo.publicKey); }); -test("privateToPublic, derive public key from private key, Buffer", (t) => { - const privateKey = Buffer.from( - "bb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016", - "hex" +test("privateToPublic, derive public key from private key, Uint8Array", (t) => { + const privateKey = bytes.bytify( + "0xbb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016" ); - const publicKey = Buffer.from( - "03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3", - "hex" + const publicKey = bytes.bytify( + "0x03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3" ); t.deepEqual(privateToPublic(privateKey), publicKey); }); @@ -51,23 +50,17 @@ test("privateToPublic, derive public key from private key, HexString", (t) => { test("privateToPublic, derive public key from private key wrong length", (t) => { t.throws(() => { - privateToPublic(Buffer.from("")); + privateToPublic(Uint8Array.from([])); }); t.throws(() => { privateToPublic( - Buffer.from( - "39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016", - "hex" + bytes.bytify( + "0x39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016" ) ); }); t.throws(() => { - privateToPublic( - Buffer.from( - "0xbb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016", - "hex" - ) - ); + privateToPublic(bytes.bytify("0x")); }); }); diff --git a/packages/hd/tests/keychain.test.ts b/packages/hd/tests/keychain.test.ts index 30d043470..faf477c48 100644 --- a/packages/hd/tests/keychain.test.ts +++ b/packages/hd/tests/keychain.test.ts @@ -1,28 +1,27 @@ import test from "ava"; import { Keychain } from "../src"; +import { bytes } from "@ckb-lumos/codec"; + +const { bytify, hexify } = bytes; // https://en.bitcoin.it/wiki/BIP_0032_TestVectors -const shortSeed = Buffer.from("000102030405060708090a0b0c0d0e0f", "hex"); -const longSeed = Buffer.from( - "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", - "hex" +const shortSeed = bytify("0x000102030405060708090a0b0c0d0e0f"); +const longSeed = bytify( + "0xfffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" ); test("create master keychain from seed", (t) => { const master = Keychain.fromSeed(shortSeed); t.is( - master.privateKey.toString("hex"), - "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35" - ); - t.is( - master.identifier.toString("hex"), - "3442193e1bb70916e914552172cd4e2dbc9df811" + hexify(master.privateKey), + "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35" ); + t.is(hexify(master.identifier), "0x3442193e1bb70916e914552172cd4e2dbc9df811"); t.is(master.fingerprint, 876747070); t.is( - master.chainCode.toString("hex"), - "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" + hexify(master.chainCode), + "0x873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" ); t.is(master.index, 0); t.is(master.depth, 0); @@ -34,17 +33,14 @@ test("derive children hardened", (t) => { const child = master.deriveChild(0, true); t.is( - child.privateKey.toString("hex"), - "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea" - ); - t.is( - child.identifier.toString("hex"), - "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7" + hexify(child.privateKey), + "0xedb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea" ); + t.is(hexify(child.identifier), "0x5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7"); t.is(child.fingerprint, 1545328200); t.is( - child.chainCode.toString("hex"), - "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141" + hexify(child.chainCode), + "0x47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141" ); t.is(child.index, 0); t.is(child.depth, 1); @@ -53,24 +49,21 @@ test("derive children hardened", (t) => { test("derive path", (t) => { const master = Keychain.fromSeed(shortSeed); t.is( - master.derivePath(`m/0'`).privateKey.toString("hex"), - "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea" + hexify(master.derivePath(`m/0'`).privateKey), + "0xedb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea" ); const child = master.derivePath(`m/0'/1/2'`); t.is( - child.privateKey.toString("hex"), - "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca" - ); - t.is( - child.identifier.toString("hex"), - "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72" + hexify(child.privateKey), + "0xcbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca" ); + t.is(hexify(child.identifier), "0xee7ab90cde56a8c0e2bb086ac49748b8db9dce72"); t.is(child.fingerprint, 4001020172); t.is( - child.chainCode.toString("hex"), - "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f" + hexify(child.chainCode), + "0x04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f" ); t.is(child.index, 2); t.is(child.depth, 3); @@ -80,17 +73,14 @@ test("create master keychain from long seed", (t) => { const master = Keychain.fromSeed(longSeed); t.is( - master.privateKey.toString("hex"), - "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e" - ); - t.is( - master.identifier.toString("hex"), - "bd16bee53961a47d6ad888e29545434a89bdfe95" + hexify(master.privateKey), + "0x4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e" ); + t.is(hexify(master.identifier), "0xbd16bee53961a47d6ad888e29545434a89bdfe95"); t.is(master.fingerprint, 3172384485); t.is( - master.chainCode.toString("hex"), - "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689" + hexify(master.chainCode), + "0x60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689" ); t.is(master.index, 0); t.is(master.depth, 0); @@ -100,57 +90,48 @@ test("create master keychain from long seed", (t) => { test("derive path large index", (t) => { const master = Keychain.fromSeed(longSeed); t.is( - master.derivePath(`m`).privateKey.toString("hex"), - "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e" + hexify(master.derivePath(`m`).privateKey), + "0x4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e" ); let child = master.derivePath(`0/2147483647'`); t.is( - child.privateKey.toString("hex"), - "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93" - ); - t.is( - child.identifier.toString("hex"), - "d8ab493736da02f11ed682f88339e720fb0379d1" + hexify(child.privateKey), + "0x877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93" ); + t.is(hexify(child.identifier), "0xd8ab493736da02f11ed682f88339e720fb0379d1"); t.is(child.fingerprint, 3635104055); t.is( - child.chainCode.toString("hex"), - "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9" + hexify(child.chainCode), + "0xbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9" ); t.is(child.index, 2147483647); t.is(child.depth, 2); child = child.deriveChild(1, false); t.is( - child.privateKey.toString("hex"), - "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7" - ); - t.is( - child.identifier.toString("hex"), - "78412e3a2296a40de124307b6485bd19833e2e34" + hexify(child.privateKey), + "0x704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7" ); + t.is(hexify(child.identifier), "0x78412e3a2296a40de124307b6485bd19833e2e34"); t.is(child.fingerprint, 2017537594); t.is( - child.chainCode.toString("hex"), - "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb" + hexify(child.chainCode), + "0xf366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb" ); t.is(child.index, 1); t.is(child.depth, 3); child = child.deriveChild(2147483646, true); t.is( - child.privateKey.toString("hex"), - "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d" - ); - t.is( - child.identifier.toString("hex"), - "31a507b815593dfc51ffc7245ae7e5aee304246e" + hexify(child.privateKey), + "0xf1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d" ); + t.is(hexify(child.identifier), "0x31a507b815593dfc51ffc7245ae7e5aee304246e"); t.is(child.fingerprint, 832899000); t.is( - child.chainCode.toString("hex"), - "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29" + hexify(child.chainCode), + "0x637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29" ); t.is(child.index, 2147483646); t.is(child.depth, 4); @@ -160,17 +141,14 @@ test("derive children no hardened", (t) => { const master = Keychain.fromSeed(longSeed); const child = master.deriveChild(0, false); t.is( - child.privateKey.toString("hex"), - "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e" - ); - t.is( - child.identifier.toString("hex"), - "5a61ff8eb7aaca3010db97ebda76121610b78096" + hexify(child.privateKey), + "0xabe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e" ); + t.is(hexify(child.identifier), "0x5a61ff8eb7aaca3010db97ebda76121610b78096"); t.is(child.fingerprint, 1516371854); t.is( - child.chainCode.toString("hex"), - "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c" + hexify(child.chainCode), + "0xf0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c" ); t.is(child.index, 0); t.is(child.depth, 1); @@ -178,36 +156,31 @@ test("derive children no hardened", (t) => { test("create child keychain from public key", (t) => { const child = Keychain.fromPublicKey( - Buffer.from( - "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", - "hex" + bytify( + "0x0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2" ), - Buffer.from( - "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", - "hex" + bytify( + "0x04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f" ), `m/0'/1/2'` ); - t.is( - child.identifier.toString("hex"), - "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72" - ); + t.is(hexify(child.identifier), "0xee7ab90cde56a8c0e2bb086ac49748b8db9dce72"); t.is(child.fingerprint, 4001020172); t.is(child.index, 2); t.is(child.depth, 3); const grandchild = child.deriveChild(2, false); t.is( - grandchild.publicKey.toString("hex"), - "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29" + hexify(grandchild.publicKey), + "0x02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29" ); t.is( - grandchild.chainCode.toString("hex"), - "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd" + hexify(grandchild.chainCode), + "0xcfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd" ); t.is( - grandchild.identifier.toString("hex"), - "d880d7d893848509a62d8fb74e32148dac68412f" + hexify(grandchild.identifier), + "0xd880d7d893848509a62d8fb74e32148dac68412f" ); t.is(grandchild.fingerprint, 3632322520); t.is(grandchild.index, 2); @@ -218,157 +191,146 @@ test("derive ckb keys", (t) => { const master = Keychain.fromSeed(shortSeed); const extendedKey = master.derivePath(`m/44'/309'/0'`); t.is( - extendedKey.privateKey.toString("hex"), - "bb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016" + hexify(extendedKey.privateKey), + "0xbb39d218506b30ca69b0f3112427877d983dd3cd2cabc742ab723e2964d98016" ); t.is( - extendedKey.publicKey.toString("hex"), - "03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3" + hexify(extendedKey.publicKey), + "0x03e5b310636a0f6e7dcdfffa98f28d7ed70df858bb47acf13db830bfde3510b3f3" ); t.is( - extendedKey.chainCode.toString("hex"), - "37e85a19f54f0a242a35599abac64a71aacc21e3a5860dd024377ffc7e6827d8" + hexify(extendedKey.chainCode), + "0x37e85a19f54f0a242a35599abac64a71aacc21e3a5860dd024377ffc7e6827d8" ); const addressKey = extendedKey.deriveChild(0, false).deriveChild(0, false); t.is( - addressKey.privateKey.toString("hex"), - "fcba4708f1f07ddc00fc77422d7a70c72b3456f5fef3b2f68368cdee4e6fb498" + hexify(addressKey.privateKey), + "0xfcba4708f1f07ddc00fc77422d7a70c72b3456f5fef3b2f68368cdee4e6fb498" ); t.is( - addressKey.publicKey.toString("hex"), - "0331b3c0225388c5010e3507beb28ecf409c022ef6f358f02b139cbae082f5a2a3" + hexify(addressKey.publicKey), + "0x0331b3c0225388c5010e3507beb28ecf409c022ef6f358f02b139cbae082f5a2a3" ); t.is( - addressKey.chainCode.toString("hex"), - "c4b7aef857b625bbb0497267ed51151d090f81737f4f22a0ac3673483b927090" + hexify(addressKey.chainCode), + "0xc4b7aef857b625bbb0497267ed51151d090f81737f4f22a0ac3673483b927090" ); }); test("derive ckb keys another seed", (t) => { const master = Keychain.fromSeed( // From mnemonic `tank planet champion pottery together intact quick police asset flower sudden question` - Buffer.from( - "1371018cfad5990f5e451bf586d59c3820a8671162d8700533549b0df61a63330e5cd5099a5d3938f833d51e4572104868bfac7cfe5b4063b1509a995652bc08", - "hex" + bytify( + "0x1371018cfad5990f5e451bf586d59c3820a8671162d8700533549b0df61a63330e5cd5099a5d3938f833d51e4572104868bfac7cfe5b4063b1509a995652bc08" ) ); t.is( - master.privateKey.toString("hex"), - "37d25afe073a6ba17badc2df8e91fc0de59ed88bcad6b9a0c2210f325fafca61" + hexify(master.privateKey), + "0x37d25afe073a6ba17badc2df8e91fc0de59ed88bcad6b9a0c2210f325fafca61" ); t.is( - master.derivePath(`m/44'/309'/0'`).privateKey.toString("hex"), - "2925f5dfcbee3b6ad29100a37ed36cbe92d51069779cc96164182c779c5dc20e" + hexify(master.derivePath(`m/44'/309'/0'`).privateKey), + "0x2925f5dfcbee3b6ad29100a37ed36cbe92d51069779cc96164182c779c5dc20e" ); t.is( - master - .derivePath(`m/44'/309'/0'`) - .deriveChild(0, false) - .privateKey.toString("hex"), - "047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" + hexify(master.derivePath(`m/44'/309'/0'`).deriveChild(0, false).privateKey), + "0x047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" ); t.is( - master.derivePath(`m/44'/309'/0'/0`).privateKey.toString("hex"), - "047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" + hexify(master.derivePath(`m/44'/309'/0'/0`).privateKey), + "0x047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" ); t.is( - master - .derivePath(`m/44'/309'/0'`) - .deriveChild(0, false) - .deriveChild(0, false) - .privateKey.toString("hex"), - "848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" + hexify( + master + .derivePath(`m/44'/309'/0'`) + .deriveChild(0, false) + .deriveChild(0, false).privateKey + ), + "0x848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" ); t.is( - master.derivePath(`m/44'/309'/0'/0/0`).privateKey.toString("hex"), - "848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" + hexify(master.derivePath(`m/44'/309'/0'/0/0`).privateKey), + "0x848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" ); }); test("derive ckb keys from master extended key", (t) => { - const privateKey = Buffer.from( - "37d25afe073a6ba17badc2df8e91fc0de59ed88bcad6b9a0c2210f325fafca61", - "hex" + const privateKey = bytify( + "0x37d25afe073a6ba17badc2df8e91fc0de59ed88bcad6b9a0c2210f325fafca61" ); - const chainCode = Buffer.from( - "5f772d1e3cfee5821911aefa5e8f79d20d4cf6678378d744efd08b66b2633b80", - "hex" + const chainCode = bytify( + "0x5f772d1e3cfee5821911aefa5e8f79d20d4cf6678378d744efd08b66b2633b80" ); const master = new Keychain(privateKey, chainCode); t.is( - master.publicKey.toString("hex"), - "020720a7a11a9ac4f0330e2b9537f594388ea4f1cd660301f40b5a70e0bc231065" + hexify(master.publicKey), + "0x020720a7a11a9ac4f0330e2b9537f594388ea4f1cd660301f40b5a70e0bc231065" ); t.is( - master.derivePath(`m/44'/309'/0'`).privateKey.toString("hex"), - "2925f5dfcbee3b6ad29100a37ed36cbe92d51069779cc96164182c779c5dc20e" + hexify(master.derivePath(`m/44'/309'/0'`).privateKey), + "0x2925f5dfcbee3b6ad29100a37ed36cbe92d51069779cc96164182c779c5dc20e" ); t.is( - master - .derivePath(`m/44'/309'/0'`) - .deriveChild(0, false) - .privateKey.toString("hex"), - "047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" + hexify(master.derivePath(`m/44'/309'/0'`).deriveChild(0, false).privateKey), + "0x047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" ); t.is( - master.derivePath(`m/44'/309'/0'/0`).privateKey.toString("hex"), - "047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" + hexify(master.derivePath(`m/44'/309'/0'/0`).privateKey), + "0x047fae4f38b3204f93a6b39d6dbcfbf5901f2b09f6afec21cbef6033d01801f1" ); t.is( - master - .derivePath(`m/44'/309'/0'`) - .deriveChild(0, false) - .deriveChild(0, false) - .privateKey.toString("hex"), - "848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" + hexify( + master + .derivePath(`m/44'/309'/0'`) + .deriveChild(0, false) + .deriveChild(0, false).privateKey + ), + "0x848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" ); t.is( - master.derivePath(`m/44'/309'/0'/0/0`).privateKey.toString("hex"), - "848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" + hexify(master.derivePath(`m/44'/309'/0'/0/0`).privateKey), + "0x848422863825f69e66dc7f48a3302459ec845395370c23578817456ad6b04b14" ); }); test("private key add", (t) => { - const privateKey = Buffer.from( - "9e919c96ac5a4caea7ba0ea1f7dd7bca5dca8a11e66ed633690c71e483a6e3c9", - "hex" + const privateKey = bytify( + "0x9e919c96ac5a4caea7ba0ea1f7dd7bca5dca8a11e66ed633690c71e483a6e3c9" ); - const toAdd = Buffer.from( - "36e92e33659808bf06c3e4302b657f39ca285f6bb5393019bb4e2f7b96e3f914", - "hex" + const toAdd = bytify( + "0x36e92e33659808bf06c3e4302b657f39ca285f6bb5393019bb4e2f7b96e3f914" ); // @ts-ignore: Private method const sum = Keychain.privateKeyAdd(privateKey, toAdd); t.is( - sum.toString("hex"), - "d57acaca11f2556dae7df2d22342fb0427f2e97d9ba8064d245aa1601a8adcdd" + hexify(sum), + "0xd57acaca11f2556dae7df2d22342fb0427f2e97d9ba8064d245aa1601a8adcdd" ); }); test("public key add", (t) => { - const publicKey = Buffer.from( - "03556b2c7e03b12845a973a6555b49fe44b0836fbf3587709fa73bb040ba181b21", - "hex" + const publicKey = bytify( + "0x03556b2c7e03b12845a973a6555b49fe44b0836fbf3587709fa73bb040ba181b21" ); - const toAdd = Buffer.from( - "953fd6b91b51605d32a28ab478f39ab53c90103b93bd688330b118c460e9c667", - "hex" + const toAdd = bytify( + "0x953fd6b91b51605d32a28ab478f39ab53c90103b93bd688330b118c460e9c667" ); // @ts-ignore: Private method const sum = Keychain.publicKeyAdd(publicKey, toAdd); t.is( - sum.toString("hex"), - "03db6eab66f918e434bae0e24fd73de1a2b293a2af9bd3ad53123996fa94494f37" + hexify(sum), + "0x03db6eab66f918e434bae0e24fd73de1a2b293a2af9bd3ad53123996fa94494f37" ); }); diff --git a/packages/hd/tests/keystore.test.ts b/packages/hd/tests/keystore.test.ts index da274bd5a..da3b69b5c 100644 --- a/packages/hd/tests/keystore.test.ts +++ b/packages/hd/tests/keystore.test.ts @@ -1,6 +1,9 @@ import test from "ava"; +import { bytes } from "@ckb-lumos/codec"; import { ExtendedPrivateKey, Keystore, IncorrectPassword } from "../src"; +const { hexify } = bytes; + const fixture = { privateKey: "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", @@ -81,8 +84,8 @@ test("load test vector keystore", (t) => { "0x7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" ); t.deepEqual( - keystore.derivedKey("testpassword").toString("hex"), - "fac192ceb5fd772906bea3e118a69e8bbb5cc24229e20d8766fd298291bba6bd" + hexify(keystore.derivedKey("testpassword")), + "0xfac192ceb5fd772906bea3e118a69e8bbb5cc24229e20d8766fd298291bba6bd" ); }); diff --git a/packages/joyid/tests/script-info.test.ts b/packages/joyid/tests/script-info.test.ts index 74d15aede..4bfc98cf8 100644 --- a/packages/joyid/tests/script-info.test.ts +++ b/packages/joyid/tests/script-info.test.ts @@ -144,7 +144,7 @@ function mockAggregator( ): JoyIDScriptInfoConfig["aggregator"] { return { generateSubkeyUnlockSmt: async () => ({ - unlock_entry: Buffer.from(bytes.bytify(entry)).toString("hex"), + unlock_entry: bytes.hexify(bytes.bytify(entry)).slice(2), block_number: 0n, }), }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45d23e6f6..333d2d44e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -239,6 +239,9 @@ importers: '@ckb-lumos/config-manager': specifier: 0.23.0 version: link:../config-manager + '@ckb-lumos/crypto': + specifier: 0.23.0 + version: link:../crypto '@ckb-lumos/helpers': specifier: 0.23.0 version: link:../helpers @@ -258,24 +261,15 @@ importers: specifier: ^4.3.0 version: 4.3.5 devDependencies: - '@ckb-lumos/crypto': - specifier: 0.23.0 - version: link:../crypto '@ckb-lumos/debugger': specifier: 0.23.0 version: link:../debugger '@ckb-lumos/hd': specifier: 0.23.0 version: link:../hd - '@types/keccak': - specifier: ^3.0.1 - version: 3.0.4 '@unisat/wallet-sdk': specifier: ^1.1.2 version: 1.5.3 - keccak: - specifier: ^3.0.1 - version: 3.0.4 tweetnacl: specifier: ^1.0.3 version: 1.0.3 @@ -465,6 +459,9 @@ importers: '@ckb-lumos/bi': specifier: 0.23.0 version: link:../bi + '@ckb-lumos/codec': + specifier: 0.23.0 + version: link:../codec '@ckb-lumos/crypto': specifier: 0.23.0 version: link:../crypto @@ -4930,12 +4927,6 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/keccak@3.0.4: - resolution: {integrity: sha512-hdnkmbie7tE0yXnQQvlIOqCyjEsoXDVEZ3ACqO+F305XgUOW4Z9ElWdogCXXRAW/khnZ7GxM0t/BGB5bORKt/g==} - dependencies: - '@types/node': 20.12.8 - dev: true - /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -11297,16 +11288,6 @@ packages: commander: 8.3.0 dev: true - /keccak@3.0.4: - resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} - engines: {node: '>=10.0.0'} - requiresBuild: true - dependencies: - node-addon-api: 2.0.2 - node-gyp-build: 4.8.1 - readable-stream: 3.6.2 - dev: true - /keyv@3.0.0: resolution: {integrity: sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==} dependencies: From 9da663dffe3dfc66b9eb9e5e28e097a8b4243bc6 Mon Sep 17 00:00:00 2001 From: Tom Wang Date: Wed, 12 Jun 2024 11:01:46 +0800 Subject: [PATCH 2/3] refactor: replace scrypt-js with @noble/hashes/scrypt --- packages/hd/package.json | 1 - packages/hd/src/keystore.ts | 29 +++++++++-------- packages/hd/tests/keystore.test.ts | 50 ++++++++++++++++++++++++------ pnpm-lock.yaml | 7 ----- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/packages/hd/package.json b/packages/hd/package.json index 5e6dab3f1..bcd092af7 100644 --- a/packages/hd/package.json +++ b/packages/hd/package.json @@ -25,7 +25,6 @@ "@ckb-lumos/crypto": "0.23.0", "bn.js": "^5.1.3", "elliptic": "^6.5.4", - "scrypt-js": "^3.0.1", "uuid": "^8.3.0" }, "repository": { diff --git a/packages/hd/src/keystore.ts b/packages/hd/src/keystore.ts index 179d11c7c..bbf3244a9 100644 --- a/packages/hd/src/keystore.ts +++ b/packages/hd/src/keystore.ts @@ -1,8 +1,7 @@ import { v4 as uuid } from "uuid"; import { bytes } from "@ckb-lumos/codec"; import { HexString } from "@ckb-lumos/base"; -import { ctr, keccak256, randomBytes } from "@ckb-lumos/crypto"; -import { syncScrypt } from "scrypt-js"; +import { ctr, scrypt, keccak256, randomBytes } from "@ckb-lumos/crypto"; import { ExtendedPrivateKey } from "./extended_key"; const { bytify, concat, hexify } = bytes; @@ -143,14 +142,12 @@ export default class Keystore { r: DEFAULT_SCRIPT_PARAM_r, p: DEFAULT_SCRIPT_PARAM_p, }; - const derivedKey = syncScrypt( - new TextEncoder().encode(password), - salt, - kdfparams.n, - kdfparams.r, - kdfparams.p, - kdfparams.dklen - ); + const derivedKey = scrypt(new TextEncoder().encode(password), salt, { + N: kdfparams.n, + r: kdfparams.r, + p: kdfparams.p, + dkLen: kdfparams.dklen, + }); // DO NOT remove the Uint8Array.from call below. // Without calling Uint8Array.from to make a copy of iv, @@ -209,13 +206,15 @@ export default class Keystore { derivedKey(password: string): Uint8Array { const { kdfparams } = this.crypto; - return syncScrypt( + return scrypt( new TextEncoder().encode(password), bytify("0x" + kdfparams.salt), - kdfparams.n, - kdfparams.r, - kdfparams.p, - kdfparams.dklen + { + N: kdfparams.n, + r: kdfparams.r, + p: kdfparams.p, + dkLen: kdfparams.dklen, + } ); } diff --git a/packages/hd/tests/keystore.test.ts b/packages/hd/tests/keystore.test.ts index da3b69b5c..88c47d2de 100644 --- a/packages/hd/tests/keystore.test.ts +++ b/packages/hd/tests/keystore.test.ts @@ -55,24 +55,56 @@ test("load and check password, loads private key", (t) => { * MAC: 2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097 * Cipher key: fac192ceb5fd772906bea3e118a69e8b */ +// This case does NOT work with @crpyto/hashes/scrypt because N is not less than 2^(128 * r / 8) +// const json = { +// crypto: { +// cipher: "aes-128-ctr", +// cipherparams: { +// iv: "83dbcc02d8ccb40e466191a123791e0e", +// }, +// ciphertext: +// "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", +// kdf: "scrypt", +// kdfparams: { +// dklen: 32, +// n: 262144, // 2^18 Scrypt: N must be larger than 1, a power of 2, less than 2^(128 * r / 8) and less than 2^32 +// r: 1, +// p: 8, +// salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19", +// }, +// mac: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097", +// }, +// id: "3198bc9c-6672-5ab3-d995-4942343ae5b6", +// version: 3, +// }; test("load test vector keystore", (t) => { + // const privateKey = "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"; + // const chainCode = "0x873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"; + // const keystore = KeyStore.create( + // new ExtendedPrivateKey(privateKey, chainCode), + // 'testpassword', + // { + // iv: bytify("0x8f7097ee22add66106055bf3e241fece"), + // salt: bytify("0x481ead5b0311079d2adcd74f32e3db3bdc5f447bc2217ad8164d2d7ead19079f") + // } + // ); const json = { crypto: { cipher: "aes-128-ctr", cipherparams: { - iv: "83dbcc02d8ccb40e466191a123791e0e", + iv: "8f7097ee22add66106055bf3e241fece", }, ciphertext: - "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "28b4e6295dca3079d2e0d661517dd18ee7fe0b6a816ef7398d1828e10dfff57480107d30e1e4a73d30a7e0c46584f8f7f66ca38c13fb6fddc22abfe07f3ab0fb", kdf: "scrypt", kdfparams: { dklen: 32, - n: 262144, - p: 8, - r: 1, - salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19", + n: 262144, // 2^18 + r: 8, + p: 1, + salt: "481ead5b0311079d2adcd74f32e3db3bdc5f447bc2217ad8164d2d7ead19079f", }, - mac: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097", + mac: "87e41a279a32c206579738a051801b3532749682a34fe914e3f487622b0c7b2a", }, id: "3198bc9c-6672-5ab3-d995-4942343ae5b6", version: 3, @@ -81,11 +113,11 @@ test("load test vector keystore", (t) => { t.true(keystore.checkPassword("testpassword")); t.deepEqual( keystore.decrypt("testpassword"), - "0x7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" + "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" ); t.deepEqual( hexify(keystore.derivedKey("testpassword")), - "0xfac192ceb5fd772906bea3e118a69e8bbb5cc24229e20d8766fd298291bba6bd" + "0xdc0b555db92835c5a417a6c7229a0c3a228c91ff1b22580ee2c368a8aed33140" ); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 333d2d44e..972600268 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -471,9 +471,6 @@ importers: elliptic: specifier: ^6.5.4 version: 6.5.5 - scrypt-js: - specifier: ^3.0.1 - version: 3.0.1 uuid: specifier: ^8.3.0 version: 8.3.2 @@ -14777,10 +14774,6 @@ packages: ajv-formats: 2.1.1(ajv@8.13.0) ajv-keywords: 5.1.0(ajv@8.13.0) - /scrypt-js@3.0.1: - resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - dev: false - /search-insights@2.13.0: resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==} dev: false From 2f94343db787ba2d8327c3d94f885324746f3bd4 Mon Sep 17 00:00:00 2001 From: Tom Wang Date: Thu, 13 Jun 2024 09:59:44 +0800 Subject: [PATCH 3/3] test: fix load keystore test case --- packages/hd/tests/keystore.test.ts | 98 +++++++++++++++++------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/packages/hd/tests/keystore.test.ts b/packages/hd/tests/keystore.test.ts index 88c47d2de..6e2a79e50 100644 --- a/packages/hd/tests/keystore.test.ts +++ b/packages/hd/tests/keystore.test.ts @@ -55,69 +55,83 @@ test("load and check password, loads private key", (t) => { * MAC: 2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097 * Cipher key: fac192ceb5fd772906bea3e118a69e8b */ -// This case does NOT work with @crpyto/hashes/scrypt because N is not less than 2^(128 * r / 8) -// const json = { -// crypto: { -// cipher: "aes-128-ctr", -// cipherparams: { -// iv: "83dbcc02d8ccb40e466191a123791e0e", +// This case does NOT work with @crpyto/hashes/scrypt because N (kdfparams.n) is greater than 2^(128 * r / 8) +// Error message: 'Scrypt: N must be larger than 1, a power of 2, less than 2^(128 * r / 8) and less than 2^32' +// test("load test vector keystore", (t) => { +// const json = { +// crypto: { +// cipher: "aes-128-ctr", +// cipherparams: { +// iv: "83dbcc02d8ccb40e466191a123791e0e", +// }, +// ciphertext: +// "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", +// kdf: "scrypt", +// kdfparams: { +// dklen: 32, +// n: 262144, // 2^18, but 2^(128 * r / 8) is 2^16 +// p: 8, +// r: 1, +// salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19", +// }, +// mac: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097", // }, -// ciphertext: -// "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", -// kdf: "scrypt", -// kdfparams: { -// dklen: 32, -// n: 262144, // 2^18 Scrypt: N must be larger than 1, a power of 2, less than 2^(128 * r / 8) and less than 2^32 -// r: 1, -// p: 8, -// salt: "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19", -// }, -// mac: "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097", -// }, -// id: "3198bc9c-6672-5ab3-d995-4942343ae5b6", -// version: 3, -// }; +// id: "3198bc9c-6672-5ab3-d995-4942343ae5b6", +// version: 3, +// }; +// const keystore = Keystore.fromJson(JSON.stringify(json)); +// t.true(keystore.checkPassword("testpassword")); +// t.deepEqual( +// keystore.decrypt("testpassword"), +// "0x7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" +// ); +// t.deepEqual( +// hexify(keystore.derivedKey("testpassword")), +// "0xfac192ceb5fd772906bea3e118a69e8bbb5cc24229e20d8766fd298291bba6bd" +// ); +// }); + +/** + * test vector: + * https://github.com/web3/web3.js/blob/4.x/packages/web3-eth-accounts/src/account.ts#L489-L516 + * Address: cda9a91875fc35c8ac1320e098e584495d66e47c + * UUID: c0cb0a94-4702-4492-b6e6-eb2ac404344a + * Password: 123 + * Secret: 67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6 + * Derived key: d3d187f93f0d3c5e45d35881f7740713e3dd9488f9a4e25344799e58e1cfc76d + * MAC: efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd + */ test("load test vector keystore", (t) => { - // const privateKey = "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"; - // const chainCode = "0x873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"; - // const keystore = KeyStore.create( - // new ExtendedPrivateKey(privateKey, chainCode), - // 'testpassword', - // { - // iv: bytify("0x8f7097ee22add66106055bf3e241fece"), - // salt: bytify("0x481ead5b0311079d2adcd74f32e3db3bdc5f447bc2217ad8164d2d7ead19079f") - // } - // ); const json = { crypto: { cipher: "aes-128-ctr", cipherparams: { - iv: "8f7097ee22add66106055bf3e241fece", + iv: "bfb43120ae00e9de110f8325143a2709", }, ciphertext: - "28b4e6295dca3079d2e0d661517dd18ee7fe0b6a816ef7398d1828e10dfff57480107d30e1e4a73d30a7e0c46584f8f7f66ca38c13fb6fddc22abfe07f3ab0fb", + "cb3e13e3281ff3861a3f0257fad4c9a51b0eb046f9c7821825c46b210f040b8f", kdf: "scrypt", kdfparams: { dklen: 32, - n: 262144, // 2^18 + n: 8192, // 2^13 r: 8, p: 1, - salt: "481ead5b0311079d2adcd74f32e3db3bdc5f447bc2217ad8164d2d7ead19079f", + salt: "210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd", }, - mac: "87e41a279a32c206579738a051801b3532749682a34fe914e3f487622b0c7b2a", + mac: "efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd", }, - id: "3198bc9c-6672-5ab3-d995-4942343ae5b6", + id: "c0cb0a94-4702-4492-b6e6-eb2ac404344a", version: 3, }; const keystore = Keystore.fromJson(JSON.stringify(json)); - t.true(keystore.checkPassword("testpassword")); + t.true(keystore.checkPassword("123")); t.deepEqual( - keystore.decrypt("testpassword"), - "0xe8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" + keystore.decrypt("123"), + "0x67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6" ); t.deepEqual( - hexify(keystore.derivedKey("testpassword")), - "0xdc0b555db92835c5a417a6c7229a0c3a228c91ff1b22580ee2c368a8aed33140" + hexify(keystore.derivedKey("123")), + "0xd3d187f93f0d3c5e45d35881f7740713e3dd9488f9a4e25344799e58e1cfc76d" ); });