Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-use noble-hashes crypto module #18

Merged
merged 6 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const { utf8ToBytes } = require("ethereum-cryptography/utils");
sha256(utf8ToBytes("abc"))

// If you need hex
const { toHex } = require("ethereum-cryptography/utils");
const { bytesToHex as toHex } = require("ethereum-cryptography/utils");
toHex(sha256(utf8ToBytes("abc")))
```

Expand Down Expand Up @@ -208,7 +208,7 @@ Note: if you've been using ethereum-cryptography v0.1, it had different API. We'
This module exports a single class `HDKey`, which should be used like this:

```ts
const { HDKey } = require("ethereum-cryptography/secp256k1");
const { HDKey } = require("ethereum-cryptography/hdkey");
const hdkey1 = HDKey.fromMasterSeed(seed);
const hdkey2 = HDKey.fromExtendedKey(base58key);
const hdkey3 = HDKey.fromJSON({ xpriv: string });
Expand Down Expand Up @@ -269,17 +269,6 @@ but it's backed by this package's primitives, and has built-in TypeScript types.
Its only difference is that it has to be be used with a named import.
The implementation is [loosely based on hdkey, which has MIT License](#LICENSE).

```js
const { HDKey } = require("ethereum-cryptography/hdkey");
const { hexToBytes } = require("ethereum-cryptography/utils");

const seed = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542";
const hdkey = HDKey.fromMasterSeed(hexToBytes(seed));
const childkey = hdkey.derive("m/0/2147483647'/1");

console.log(childkey.privateExtendedKey);
```

## BIP39 Mnemonic Seed Phrase

```ts
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"*.d.ts.map"
],
"dependencies": {
"micro-base": "^0.10.0",
"@noble/hashes": "^0.5.2",
"micro-base": "^0.10.1",
"@noble/hashes": "^0.5.7",
"@noble/secp256k1": "^1.3.3"
},
"browser": {
Expand Down
3 changes: 2 additions & 1 deletion src/aes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { concatBytes, crypto, equalsBytes } from "./utils";
import { crypto } from "@noble/hashes/crypto";
import { concatBytes, equalsBytes } from "./utils";

function validateOpt(key: Uint8Array, iv: Uint8Array, mode: string) {
if (!mode.startsWith("aes-")) {
Expand Down
5 changes: 2 additions & 3 deletions src/bip39/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { pbkdf2, pbkdf2Async } from "@noble/hashes/pbkdf2";
import { sha256 } from "@noble/hashes/sha256";
import { sha512 } from "@noble/hashes/sha512";
import { assertBytes, assertNumber } from "@noble/hashes/utils";
import { assertBytes, assertNumber, randomBytes } from "@noble/hashes/utils";
import { utils as baseUtils } from "micro-base";
import { getRandomBytesSync } from "../random";

const isJapanese = (wordlist: string[]) =>
wordlist[0] === "\u3042\u3044\u3053\u304f\u3057\u3093"; // Japanese wordlist
Expand All @@ -27,7 +26,7 @@ export function generateMnemonic(
if (strength % 32 !== 0) {
throw new TypeError("Invalid entropy");
}
return entropyToMnemonic(getRandomBytesSync(strength / 8), wordlist);
return entropyToMnemonic(randomBytes(strength / 8), wordlist);
}

const checksum = (entropy: Uint8Array) => {
Expand Down
17 changes: 6 additions & 11 deletions src/hdkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { hmac } from "@noble/hashes/hmac";
import { ripemd160 } from "@noble/hashes/ripemd160";
import { sha256 } from "@noble/hashes/sha256";
import { sha512 } from "@noble/hashes/sha512";
import { bytesToHex } from "@noble/hashes/utils";
import { base58check } from "micro-base";
import * as secp from "./secp256k1";
import {
assertBytes,
bytesToHex,
concatBytes,
createView,
hexToBytes,
utf8ToBytes
} from "./utils";
} from "@noble/hashes/utils";
import { base58check } from "micro-base";
import * as secp from "./secp256k1";
const base58c = base58check(sha256);

function bytesToNumber(bytes: Uint8Array): bigint {
Expand Down Expand Up @@ -40,8 +40,8 @@ export interface Versions {
const hash160 = (data: Uint8Array) => ripemd160(sha256(data));
const fromU32 = (data: Uint8Array) => createView(data).getUint32(0, false);
const toU32 = (n: number) => {
if (!Number.isSafeInteger(n) || n < 0 || n >= 2 ** 32) {
throw new Error(`Invalid number=${n}. Should be [0, 2**32)`);
if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1) {
throw new Error(`Invalid number=${n}. Should be from 0 to 2 ** 32 - 1`);
}
const buf = new Uint8Array(4);
createView(buf).setUint32(0, n, false);
Expand Down Expand Up @@ -219,11 +219,6 @@ export class HDKey {
}

public deriveChild(index: number): HDKey {
if (!Number.isSafeInteger(index) || index < 0 || index >= 2 ** 32) {
throw new Error(
`Child index should be positive 32-bit integer, not ${index}`
);
}
if (!this.pubKey || !this.chainCode) {
throw new Error("No publicKey or chainCode set");
}
Expand Down
10 changes: 2 additions & 8 deletions src/random.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { crypto } from "./utils";
import { randomBytes } from "@noble/hashes/utils";

export function getRandomBytesSync(bytes: number): Uint8Array {
if (crypto.web) {
return crypto.web.getRandomValues(new Uint8Array(bytes));
} else if (crypto.node) {
return new Uint8Array(crypto.node.randomBytes(bytes).buffer);
} else {
throw new Error("The environment doesn't have randomBytes function");
}
return randomBytes(bytes);
}

export async function getRandomBytes(bytes: number): Promise<Uint8Array> {
Expand Down
14 changes: 13 additions & 1 deletion src/secp256k1-compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as secp from "./secp256k1";
import { assertBool, assertBytes, hexToBytes, toHex } from "./utils";

// Legacy compatibility layer for elliptic via noble-secp256k1
// Use `curve-secp256k1` module directly instead
// Use `secp256k1` module directly instead

// Copy-paste from secp256k1, maybe export it?
const bytesToNumber = (bytes: Uint8Array) => hexToNumber(toHex(bytes));
Expand Down Expand Up @@ -166,6 +166,9 @@ export function privateKeyTweakAdd(
assertBytes(privateKey, 32);
assertBytes(tweak, 32);
let bn = bytesToNumber(tweak);
if (bn === 0n) {
throw new Error("Tweak must not be zero");
}
if (bn >= ORDER) {
throw new Error("Tweak bigger than curve order");
}
Expand Down Expand Up @@ -231,6 +234,9 @@ export function publicKeyTweakAdd(
assertBool(compressed);
const p1 = secp.Point.fromHex(publicKey);
const p2 = secp.Point.fromPrivateKey(tweak);
if (p2.equals(secp.Point.ZERO)) {
throw new Error("Tweak must not be zero");
}
const point = p1.add(p2);
return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
}
Expand All @@ -245,6 +251,9 @@ export function publicKeyTweakMul(
assertBytes(tweak, 32);
assertBool(compressed);
const bn = bytesToNumber(tweak);
if (bn === 0n) {
throw new Error("Tweak must not be zero");
}
if (bn <= 0 || bn >= ORDER) {
throw new Error("Tweak is zero or bigger than curve order");
}
Expand All @@ -259,6 +268,9 @@ export function privateKeyTweakMul(
assertBytes(privateKey, 32);
assertBytes(tweak, 32);
let bn = bytesToNumber(tweak);
if (bn === 0n) {
paulmillr marked this conversation as resolved.
Show resolved Hide resolved
throw new Error("Tweak must not be zero");
}
if (bn >= ORDER) {
throw new Error("Tweak bigger than curve order");
}
Expand Down
3 changes: 2 additions & 1 deletion src/secp256k1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export {
utils,
Point,
Signature,
CURVE
CURVE,
schnorr
} from "@noble/secp256k1";

// Enable sync API for noble-secp256k1
Expand Down
52 changes: 9 additions & 43 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,24 @@
// buf.toString('hex') -> toHex(buf)
import { assertBytes } from "@noble/hashes/utils";
export {
assertBool,
assertBytes,
bytesToHex,
bytesToHex as toHex,
createView
concatBytes,
hexToBytes,
createView,
utf8ToBytes
} from "@noble/hashes/utils";
// Buffer.from(hex, 'hex') -> hexToBytes(hex)
export function hexToBytes(hex: string): Uint8Array {
if (typeof hex !== "string") {
throw new TypeError(`hexToBytes: expected string, got ${typeof hex}`);
}
if (hex.length % 2) {
throw new Error("hexToBytes: received invalid unpadded hex");
}
const array = new Uint8Array(hex.length / 2);
for (let i = 0; i < array.length; i++) {
const j = i * 2;
array[i] = Number.parseInt(hex.slice(j, j + 2), 16);
}
return array;
}
// Buffer.from(s, 'utf8') -> utf8ToBytes(s)
export function utf8ToBytes(s: string) {
if (typeof s !== "string") {
throw new TypeError(`utf8ToBytes expected string, got ${typeof s}`);
}
return new TextEncoder().encode(s);
}

// buf.toString('utf8') -> bytesToUtf8(buf)
export function bytesToUtf8(data: Uint8Array): string {
if (!(data instanceof Uint8Array)) {
throw new TypeError(`bytesToUtf8 expected Uint8Array, got ${typeof data}`);
}
return new TextDecoder().decode(data);
}

// buf.equals(buf2) -> equalsBytes(buf, buf2)
export function equalsBytes(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) {
Expand All @@ -46,27 +31,8 @@ export function equalsBytes(a: Uint8Array, b: Uint8Array): boolean {
}
return true;
}
// Buffer.concat([buf1, buf2]) -> concatBytes(buf1, buf2)
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
if (arrays.length === 1) {
return arrays[0];
}
const length = arrays.reduce((a, arr) => a + arr.length, 0);
const result = new Uint8Array(length);
for (let i = 0, pad = 0; i < arrays.length; i++) {
const arr = arrays[i];
result.set(arr, pad);
pad += arr.length;
}
return result;
}
// Internal utils
export function assertBool(b: boolean) {
if (typeof b !== "boolean") {
throw new Error(`Expected boolean, not ${b}`);
}
}

// Internal utils
export function wrapHash(hash: (msg: Uint8Array) => Uint8Array) {
return (msg: Uint8Array) => {
assertBytes(msg);
Expand Down