Skip to content

Commit

Permalink
refactor: replace node crypto methods with noble methods
Browse files Browse the repository at this point in the history
  • Loading branch information
twhy committed Jun 6, 2024
1 parent d4c2cba commit 8f8b18f
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 109 deletions.
3 changes: 2 additions & 1 deletion packages/crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
]
},
"dependencies": {
"@noble/hashes": "^1.4.0"
"@noble/hashes": "^1.4.0",
"@noble/ciphers": "^0.5.3"
},
"publishConfig": {
"access": "public"
Expand Down
8 changes: 8 additions & 0 deletions packages/crypto/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
export { ctr } from "@noble/ciphers/aes";
export { hmac } from "@noble/hashes/hmac";
export { sha256 } from "@noble/hashes/sha256";
export { sha512 } from "@noble/hashes/sha512";
export { keccak_256 } from "@noble/hashes/sha3";
export { ripemd160 } from "@noble/hashes/ripemd160";
export { scrypt, ScryptOpts } from "@noble/hashes/scrypt";
export { pbkdf2, pbkdf2Async } from "@noble/hashes/pbkdf2";
export { randomBytes } from "@noble/hashes/utils";
7 changes: 0 additions & 7 deletions packages/hd-cache/tests/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,6 @@ const cacheManager = CacheManager.fromMnemonic(
}
);

test.before(() => {
// @ts-ignore: Unreachable code error
BigInt = () => {
throw new Error("can not find bigint");
};
});

test("derive threshold", async (t) => {
const cacheManager = CacheManager.fromMnemonic(
indexer,
Expand Down
1 change: 0 additions & 1 deletion packages/hd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"bn.js": "^5.1.3",
"elliptic": "^6.5.4",
"scrypt-js": "^3.0.1",
"sha3": "^2.1.3",
"uuid": "^8.3.0"
},
"repository": {
Expand Down
31 changes: 15 additions & 16 deletions packages/hd/src/keychain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import crypto from "crypto";
import { ec as EC } from "elliptic";
/* eslint-disable @typescript-eslint/no-magic-numbers */
import BN from "bn.js";
import { ec as EC } from "elliptic";
import { hmac, sha256, sha512, ripemd160 } from "@ckb-lumos/crypto";
import { privateToPublic } from "./key";

const ec = new EC("secp256k1");
Expand All @@ -12,11 +13,11 @@ export default class Keychain {
privateKey: Buffer = EMPTY_BUFFER;
publicKey: Buffer = EMPTY_BUFFER;
chainCode: Buffer = EMPTY_BUFFER;
index: number = 0;
depth: number = 0;
index = 0;
depth = 0;
identifier: Buffer = EMPTY_BUFFER;
fingerprint: number = 0;
parentFingerprint: number = 0;
fingerprint = 0;
parentFingerprint = 0;

constructor(privateKey: Buffer, chainCode: Buffer) {
this.privateKey = privateKey;
Expand All @@ -33,10 +34,9 @@ export default class Keychain {
}

public static fromSeed(seed: Buffer): Keychain {
const i = crypto
.createHmac("sha512", Buffer.from("Bitcoin seed", "utf8"))
.update(seed)
.digest();
const i = Buffer.from(
hmac(sha512, Buffer.from("Bitcoin seed", "utf8"), seed)
);
const keychain = new Keychain(i.slice(0, 32), i.slice(32));
keychain.calculateFingerprint();
return keychain;
Expand All @@ -47,7 +47,7 @@ export default class Keychain {
public static fromPublicKey(
publicKey: Buffer,
chainCode: Buffer,
path: String
path: string
): Keychain {
const keychain = new Keychain(EMPTY_BUFFER, chainCode);
keychain.publicKey = publicKey;
Expand All @@ -74,7 +74,7 @@ export default class Keychain {
data = Buffer.concat([this.publicKey, indexBuffer]);
}

const i = crypto.createHmac("sha512", this.chainCode).update(data).digest();
const i = Buffer.from(hmac(sha512, this.chainCode, data));
const il = i.slice(0, 32);
const ir = i.slice(32);

Expand All @@ -101,7 +101,7 @@ export default class Keychain {
if (master.includes(path)) {
return this;
}

// eslint-disable-next-line @typescript-eslint/no-this-alias
let bip32: Keychain = this;

let entries = path.split("/");
Expand All @@ -117,13 +117,12 @@ export default class Keychain {
return bip32;
}

isNeutered(): Boolean {
isNeutered(): boolean {
return this.privateKey === EMPTY_BUFFER;
}

hash160(data: Buffer): Buffer {
const sha256 = crypto.createHash("sha256").update(data).digest();
return crypto.createHash("ripemd160").update(sha256).digest();
return Buffer.from(ripemd160(sha256(data)));
}

private static privateKeyAdd(privateKey: Buffer, factor: Buffer): Buffer {
Expand Down
60 changes: 22 additions & 38 deletions packages/hd/src/keystore.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import {
Cipher,
ScryptOptions,
createCipheriv,
createDecipheriv,
} from "crypto";
import { Keccak } from "sha3";
import { v4 as uuid } from "uuid";
import { ExtendedPrivateKey } from "./extended_key";
import { randomBytes } from "@ckb-lumos/crypto";
import { ctr, keccak_256, randomBytes, ScryptOpts } from "@ckb-lumos/crypto";
import { HexString } from "@ckb-lumos/base";
import { syncScrypt } from "scrypt-js";

Expand Down Expand Up @@ -151,19 +144,17 @@ export default class Keystore {
)
);

const cipher: Cipher = createCipheriv(CIPHER, derivedKey.slice(0, 16), iv);
if (!cipher) {
throw new UnsupportedCipher();
}

// size of 0x prefix
const hexPrefixSize = 2;
const ciphertext: Buffer = Buffer.concat([
cipher.update(
Buffer.from(extendedPrivateKey.serialize().slice(hexPrefixSize), "hex")
),
cipher.final(),
]);
// 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 */
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));

return new Keystore(
{
Expand Down Expand Up @@ -192,17 +183,13 @@ export default class Keystore {
if (Keystore.mac(derivedKey, ciphertext) !== this.crypto.mac) {
throw new IncorrectPassword();
}
const decipher = createDecipheriv(
this.crypto.cipher,

/* eslint-disable @typescript-eslint/no-magic-numbers */
const cipher = ctr(
derivedKey.slice(0, 16),
Buffer.from(this.crypto.cipherparams.iv, "hex")
);
return (
"0x" +
Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString(
"hex"
)
);
return "0x" + Buffer.from(cipher.decrypt(ciphertext)).toString("hex");
}

extendedPrivateKey(password: string): ExtendedPrivateKey {
Expand Down Expand Up @@ -230,18 +217,15 @@ export default class Keystore {
}

static mac(derivedKey: Buffer, ciphertext: Buffer): HexStringWithoutPrefix {
const keccakSize = 256;

return (
new Keccak(keccakSize)
// https://github.com/ethereumjs/ethereumjs-wallet/blob/d57582443fbac2b63956e6d5c4193aa8ce925b3d/src/index.ts#L615-L617
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
.update(Buffer.concat([derivedKey.subarray(16, 32), ciphertext]))
.digest("hex")
// https://github.com/ethereumjs/ethereumjs-wallet/blob/d57582443fbac2b63956e6d5c4193aa8ce925b3d/src/index.ts#L615-L617
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const hash = keccak_256(
Buffer.concat([derivedKey.subarray(16, 32), ciphertext])
);
return Buffer.from(hash).toString("hex");
}

static scryptOptions(kdfparams: KdfParams): ScryptOptions {
static scryptOptions(kdfparams: KdfParams): ScryptOpts {
return {
N: kdfparams.n,
r: kdfparams.r,
Expand Down
49 changes: 19 additions & 30 deletions packages/hd/src/mnemonic/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
import { pbkdf2, pbkdf2Sync, createHash } from "crypto";
import { randomBytes } from "@ckb-lumos/crypto";
import {
sha256,
sha512,
pbkdf2,
pbkdf2Async,
randomBytes,
} from "@ckb-lumos/crypto";
import { HexString } from "@ckb-lumos/base";
import wordList from "./word_list";

Expand Down Expand Up @@ -39,7 +44,7 @@ function bytesToBinary(bytes: Buffer): string {
function deriveChecksumBits(entropyBuffer: Buffer): string {
const ENT = entropyBuffer.length * 8;
const CS = ENT / 32;
const hash = createHash("sha256").update(entropyBuffer).digest();
const hash = Buffer.from(sha256(entropyBuffer));

Check warning on line 47 in packages/hd/src/mnemonic/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/hd/src/mnemonic/index.ts#L47

Added line #L47 was not covered by tests
return bytesToBinary(hash).slice(0, CS);
}

Expand All @@ -50,37 +55,21 @@ function salt(password = ""): string {
export function mnemonicToSeedSync(mnemonic = "", password = ""): Buffer {
const mnemonicBuffer = Buffer.from(mnemonic.normalize("NFKD"), "utf8");
const saltBuffer = Buffer.from(salt(password.normalize("NFKD")), "utf8");
return pbkdf2Sync(
mnemonicBuffer,
saltBuffer,
PBKDF2_ROUNDS,
KEY_LEN,
"sha512"
return Buffer.from(
pbkdf2(sha512, mnemonicBuffer, saltBuffer, {
c: PBKDF2_ROUNDS,
dkLen: KEY_LEN,
})
);
}

export function mnemonicToSeed(mnemonic = "", password = ""): Promise<Buffer> {
return new Promise((resolve, reject) => {
try {
const mnemonicBuffer = Buffer.from(mnemonic.normalize("NFKD"), "utf8");
const saltBuffer = Buffer.from(salt(password.normalize("NFKD")), "utf8");
pbkdf2(
mnemonicBuffer,
saltBuffer,
PBKDF2_ROUNDS,
KEY_LEN,
"sha512",
(err, data) => {
if (err) {
reject(err);
}
resolve(data);
}
);
} catch (error) {
reject(error);
}
});
const mnemonicBuffer = Buffer.from(mnemonic.normalize("NFKD"), "utf8");
const saltBuffer = Buffer.from(salt(password.normalize("NFKD")), "utf8");
return pbkdf2Async(sha512, mnemonicBuffer, saltBuffer, {
c: PBKDF2_ROUNDS,
dkLen: KEY_LEN,
}).then(Buffer.from);

Check warning on line 72 in packages/hd/src/mnemonic/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/hd/src/mnemonic/index.ts#L67-L72

Added lines #L67 - L72 were not covered by tests
}

export function mnemonicToEntropy(mnemonic = ""): HexString {
Expand Down
23 changes: 7 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8f8b18f

Please sign in to comment.