include node_modules so release .zip is deployable

This commit is contained in:
2023-11-24 17:44:25 -05:00
parent 6c86cfe5d2
commit 8b11c41267
8963 changed files with 874175 additions and 1 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+107
View File
@@ -0,0 +1,107 @@
# scure-bip32
Secure, [audited](#security) & minimal implementation of BIP32 hierarchical deterministic (HD) wallets over secp256k1.
Compared to popular `hdkey` package, scure-bip32:
- Supports ESM and common.js
- Is 418KB all-bundled instead of 5.9MB
- Uses 3 dependencies instead of 24
- Had an external security [audit](#security) by Cure53
Check out [scure-bip39](https://github.com/paulmillr/scure-bip39) if you need mnemonic phrases. See [ed25519-keygen](https://github.com/paulmillr/ed25519-keygen) if you need SLIP-0010/BIP32 ed25519 hdkey implementation.
### This library belongs to *scure*
> **scure** — secure, independently audited packages for every use case.
- Audited by a third-party
- Releases are signed with PGP keys and built transparently with NPM provenance
- Check out all libraries:
[base](https://github.com/paulmillr/scure-base),
[bip32](https://github.com/paulmillr/scure-bip32),
[bip39](https://github.com/paulmillr/scure-bip39),
[btc-signer](https://github.com/paulmillr/scure-btc-signer)
## Usage
> npm install @scure/bip32
This module exports a single class `HDKey`, which should be used like this:
```ts
const { HDKey } = require("@scure/bip32");
const hdkey1 = HDKey.fromMasterSeed(seed);
const hdkey2 = HDKey.fromExtendedKey(base58key);
const hdkey3 = HDKey.fromJSON({ xpriv: string });
// props
[hdkey1.depth, hdkey1.index, hdkey1.chainCode];
console.log(hdkey2.privateKey, hdkey2.publicKey);
console.log(hdkey3.derive("m/0/2147483647'/1"));
const sig = hdkey3.sign(hash);
hdkey3.verify(hash, sig);
```
Note: `chainCode` property is essentially a private part
of a secret "master" key, it should be guarded from unauthorized access.
The full API is:
```ts
class HDKey {
public static HARDENED_OFFSET: number;
public static fromMasterSeed(seed: Uint8Array, versions: Versions): HDKey;
public static fromExtendedKey(base58key: string, versions: Versions): HDKey;
public static fromJSON(json: { xpriv: string }): HDKey;
readonly versions: Versions;
readonly depth: number = 0;
readonly index: number = 0;
readonly chainCode: Uint8Array | null = null;
readonly parentFingerprint: number = 0;
get fingerprint(): number;
get identifier(): Uint8Array | undefined;
get pubKeyHash(): Uint8Array | undefined;
get privateKey(): Uint8Array | null;
get publicKey(): Uint8Array | null;
get privateExtendedKey(): string;
get publicExtendedKey(): string;
derive(path: string): HDKey;
deriveChild(index: number): HDKey;
sign(hash: Uint8Array): Uint8Array;
verify(hash: Uint8Array, signature: Uint8Array): boolean;
wipePrivateData(): this;
}
interface Versions {
private: number;
public: number;
}
```
The `hdkey` submodule provides a library for keys derivation according to
[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
It has almost the exact same API than the version `1.x` of
[`hdkey` from cryptocoinjs](https://github.com/cryptocoinjs/hdkey),
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).
## Security
The library has been audited by Cure53 on Jan 5, 2022. Check out the audit [PDF](./audit/2022-01-05-cure53-audit-nbl2.pdf) & [URL](https://cure53.de/pentest-report_hashing-libs.pdf). See [changes since audit](https://github.com/paulmillr/scure-bip32/compare/1.0.1..main).
1. The library was initially developed for [js-ethereum-cryptography](https://github.com/ethereum/js-ethereum-cryptography)
2. At commit [ae00e6d7](https://github.com/ethereum/js-ethereum-cryptography/commit/ae00e6d7d24fb3c76a1c7fe10039f6ecd120b77e), it
was extracted to a separate package called `micro-bip32`
3. After the audit we've decided to use NPM namespace for security. Since `@micro` namespace was taken, we've renamed the package to `@scure/bip32`
## License
[MIT License](./LICENSE)
Copyright (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com)
+307
View File
@@ -0,0 +1,307 @@
/*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */
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 { bytes as assertBytes } from '@noble/hashes/_assert';
import { bytesToHex, concatBytes, createView, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
import { secp256k1 as secp } from '@noble/curves/secp256k1';
import { mod } from '@noble/curves/abstract/modular';
import { base58check as base58checker } from '@scure/base';
const Point = secp.ProjectivePoint;
const base58check = base58checker(sha256);
function bytesToNumber(bytes: Uint8Array): bigint {
return BigInt(`0x${bytesToHex(bytes)}`);
}
function numberToBytes(num: bigint): Uint8Array {
return hexToBytes(num.toString(16).padStart(64, '0'));
}
const MASTER_SECRET = utf8ToBytes('Bitcoin seed');
// Bitcoin hardcoded by default
const BITCOIN_VERSIONS: Versions = { private: 0x0488ade4, public: 0x0488b21e };
export const HARDENED_OFFSET: number = 0x80000000;
export interface Versions {
private: number;
public: number;
}
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 - 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);
return buf;
};
interface HDKeyOpt {
versions: Versions;
depth?: number;
index?: number;
parentFingerprint?: number;
chainCode: Uint8Array;
publicKey?: Uint8Array;
privateKey?: Uint8Array | bigint;
}
export class HDKey {
get fingerprint(): number {
if (!this.pubHash) {
throw new Error('No publicKey set!');
}
return fromU32(this.pubHash);
}
get identifier(): Uint8Array | undefined {
return this.pubHash;
}
get pubKeyHash(): Uint8Array | undefined {
return this.pubHash;
}
get privateKey(): Uint8Array | null {
return this.privKeyBytes || null;
}
get publicKey(): Uint8Array | null {
return this.pubKey || null;
}
get privateExtendedKey(): string {
const priv = this.privateKey;
if (!priv) {
throw new Error('No private key');
}
return base58check.encode(
this.serialize(this.versions.private, concatBytes(new Uint8Array([0]), priv))
);
}
get publicExtendedKey(): string {
if (!this.pubKey) {
throw new Error('No public key');
}
return base58check.encode(this.serialize(this.versions.public, this.pubKey));
}
public static fromMasterSeed(seed: Uint8Array, versions: Versions = BITCOIN_VERSIONS): HDKey {
assertBytes(seed);
if (8 * seed.length < 128 || 8 * seed.length > 512) {
throw new Error(
`HDKey: wrong seed length=${seed.length}. Should be between 128 and 512 bits; 256 bits is advised)`
);
}
const I = hmac(sha512, MASTER_SECRET, seed);
return new HDKey({
versions,
chainCode: I.slice(32),
privateKey: I.slice(0, 32),
});
}
public static fromExtendedKey(base58key: string, versions: Versions = BITCOIN_VERSIONS): HDKey {
// => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
const keyBuffer: Uint8Array = base58check.decode(base58key);
const keyView = createView(keyBuffer);
const version = keyView.getUint32(0, false);
const opt = {
versions,
depth: keyBuffer[4],
parentFingerprint: keyView.getUint32(5, false),
index: keyView.getUint32(9, false),
chainCode: keyBuffer.slice(13, 45),
};
const key = keyBuffer.slice(45);
const isPriv = key[0] === 0;
if (version !== versions[isPriv ? 'private' : 'public']) {
throw new Error('Version mismatch');
}
if (isPriv) {
return new HDKey({ ...opt, privateKey: key.slice(1) });
} else {
return new HDKey({ ...opt, publicKey: key });
}
}
public static fromJSON(json: { xpriv: string }): HDKey {
return HDKey.fromExtendedKey(json.xpriv);
}
public readonly versions: Versions;
public readonly depth: number = 0;
public readonly index: number = 0;
public readonly chainCode: Uint8Array | null = null;
public readonly parentFingerprint: number = 0;
private privKey?: bigint;
private privKeyBytes?: Uint8Array;
private pubKey?: Uint8Array;
private pubHash: Uint8Array | undefined;
constructor(opt: HDKeyOpt) {
if (!opt || typeof opt !== 'object') {
throw new Error('HDKey.constructor must not be called directly');
}
this.versions = opt.versions || BITCOIN_VERSIONS;
this.depth = opt.depth || 0;
this.chainCode = opt.chainCode;
this.index = opt.index || 0;
this.parentFingerprint = opt.parentFingerprint || 0;
if (!this.depth) {
if (this.parentFingerprint || this.index) {
throw new Error('HDKey: zero depth with non-zero index/parent fingerprint');
}
}
if (opt.publicKey && opt.privateKey) {
throw new Error('HDKey: publicKey and privateKey at same time.');
}
if (opt.privateKey) {
if (!secp.utils.isValidPrivateKey(opt.privateKey)) {
throw new Error('Invalid private key');
}
this.privKey =
typeof opt.privateKey === 'bigint' ? opt.privateKey : bytesToNumber(opt.privateKey);
this.privKeyBytes = numberToBytes(this.privKey);
this.pubKey = secp.getPublicKey(opt.privateKey, true);
} else if (opt.publicKey) {
this.pubKey = Point.fromHex(opt.publicKey).toRawBytes(true); // force compressed point
} else {
throw new Error('HDKey: no public or private key provided');
}
this.pubHash = hash160(this.pubKey);
}
public derive(path: string): HDKey {
if (!/^[mM]'?/.test(path)) {
throw new Error('Path must start with "m" or "M"');
}
if (/^[mM]'?$/.test(path)) {
return this;
}
const parts = path.replace(/^[mM]'?\//, '').split('/');
// tslint:disable-next-line
let child: HDKey = this;
for (const c of parts) {
const m = /^(\d+)('?)$/.exec(c);
if (!m || m.length !== 3) {
throw new Error(`Invalid child index: ${c}`);
}
let idx = +m[1];
if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) {
throw new Error('Invalid index');
}
// hardened key
if (m[2] === "'") {
idx += HARDENED_OFFSET;
}
child = child.deriveChild(idx);
}
return child;
}
public deriveChild(index: number): HDKey {
if (!this.pubKey || !this.chainCode) {
throw new Error('No publicKey or chainCode set');
}
let data = toU32(index);
if (index >= HARDENED_OFFSET) {
// Hardened
const priv = this.privateKey;
if (!priv) {
throw new Error('Could not derive hardened child key');
}
// Hardened child: 0x00 || ser256(kpar) || ser32(index)
data = concatBytes(new Uint8Array([0]), priv, data);
} else {
// Normal child: serP(point(kpar)) || ser32(index)
data = concatBytes(this.pubKey, data);
}
const I = hmac(sha512, this.chainCode, data);
const childTweak = bytesToNumber(I.slice(0, 32));
const chainCode = I.slice(32);
if (!secp.utils.isValidPrivateKey(childTweak)) {
throw new Error('Tweak bigger than curve order');
}
const opt: HDKeyOpt = {
versions: this.versions,
chainCode,
depth: this.depth + 1,
parentFingerprint: this.fingerprint,
index,
};
try {
// Private parent key -> private child key
if (this.privateKey) {
const added = mod(this.privKey! + childTweak, secp.CURVE.n);
if (!secp.utils.isValidPrivateKey(added)) {
throw new Error('The tweak was out of range or the resulted private key is invalid');
}
opt.privateKey = added;
} else {
const added = Point.fromHex(this.pubKey).add(Point.fromPrivateKey(childTweak));
// Cryptographically impossible: hmac-sha512 preimage would need to be found
if (added.equals(Point.ZERO)) {
throw new Error('The tweak was equal to negative P, which made the result key invalid');
}
opt.publicKey = added.toRawBytes(true);
}
return new HDKey(opt);
} catch (err) {
return this.deriveChild(index + 1);
}
}
public sign(hash: Uint8Array): Uint8Array {
if (!this.privateKey) {
throw new Error('No privateKey set!');
}
assertBytes(hash, 32);
return secp.sign(hash, this.privKey!).toCompactRawBytes();
}
public verify(hash: Uint8Array, signature: Uint8Array): boolean {
assertBytes(hash, 32);
assertBytes(signature, 64);
if (!this.publicKey) {
throw new Error('No publicKey set!');
}
let sig;
try {
sig = secp.Signature.fromCompact(signature);
} catch (error) {
return false;
}
return secp.verify(sig, hash, this.publicKey);
}
public wipePrivateData(): this {
this.privKey = undefined;
if (this.privKeyBytes) {
this.privKeyBytes.fill(0);
this.privKeyBytes = undefined;
}
return this;
}
public toJSON(): { xpriv: string; xpub: string } {
return {
xpriv: this.privateExtendedKey,
xpub: this.publicExtendedKey,
};
}
private serialize(version: number, key: Uint8Array) {
if (!this.chainCode) {
throw new Error('No chainCode set');
}
assertBytes(key, 33);
// version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33)
return concatBytes(
toU32(version),
new Uint8Array([this.depth]),
toU32(this.parentFingerprint),
toU32(this.index),
this.chainCode,
key
);
}
}
+257
View File
@@ -0,0 +1,257 @@
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 { bytes as assertBytes } from '@noble/hashes/_assert';
import { bytesToHex, concatBytes, createView, hexToBytes, utf8ToBytes } from '@noble/hashes/utils';
import { secp256k1 as secp } from '@noble/curves/secp256k1';
import { mod } from '@noble/curves/abstract/modular';
import { base58check as base58checker } from '@scure/base';
const Point = secp.ProjectivePoint;
const base58check = base58checker(sha256);
function bytesToNumber(bytes) {
return BigInt(`0x${bytesToHex(bytes)}`);
}
function numberToBytes(num) {
return hexToBytes(num.toString(16).padStart(64, '0'));
}
const MASTER_SECRET = utf8ToBytes('Bitcoin seed');
const BITCOIN_VERSIONS = { private: 0x0488ade4, public: 0x0488b21e };
export const HARDENED_OFFSET = 0x80000000;
const hash160 = (data) => ripemd160(sha256(data));
const fromU32 = (data) => createView(data).getUint32(0, false);
const toU32 = (n) => {
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);
return buf;
};
export class HDKey {
get fingerprint() {
if (!this.pubHash) {
throw new Error('No publicKey set!');
}
return fromU32(this.pubHash);
}
get identifier() {
return this.pubHash;
}
get pubKeyHash() {
return this.pubHash;
}
get privateKey() {
return this.privKeyBytes || null;
}
get publicKey() {
return this.pubKey || null;
}
get privateExtendedKey() {
const priv = this.privateKey;
if (!priv) {
throw new Error('No private key');
}
return base58check.encode(this.serialize(this.versions.private, concatBytes(new Uint8Array([0]), priv)));
}
get publicExtendedKey() {
if (!this.pubKey) {
throw new Error('No public key');
}
return base58check.encode(this.serialize(this.versions.public, this.pubKey));
}
static fromMasterSeed(seed, versions = BITCOIN_VERSIONS) {
assertBytes(seed);
if (8 * seed.length < 128 || 8 * seed.length > 512) {
throw new Error(`HDKey: wrong seed length=${seed.length}. Should be between 128 and 512 bits; 256 bits is advised)`);
}
const I = hmac(sha512, MASTER_SECRET, seed);
return new HDKey({
versions,
chainCode: I.slice(32),
privateKey: I.slice(0, 32),
});
}
static fromExtendedKey(base58key, versions = BITCOIN_VERSIONS) {
const keyBuffer = base58check.decode(base58key);
const keyView = createView(keyBuffer);
const version = keyView.getUint32(0, false);
const opt = {
versions,
depth: keyBuffer[4],
parentFingerprint: keyView.getUint32(5, false),
index: keyView.getUint32(9, false),
chainCode: keyBuffer.slice(13, 45),
};
const key = keyBuffer.slice(45);
const isPriv = key[0] === 0;
if (version !== versions[isPriv ? 'private' : 'public']) {
throw new Error('Version mismatch');
}
if (isPriv) {
return new HDKey({ ...opt, privateKey: key.slice(1) });
}
else {
return new HDKey({ ...opt, publicKey: key });
}
}
static fromJSON(json) {
return HDKey.fromExtendedKey(json.xpriv);
}
constructor(opt) {
this.depth = 0;
this.index = 0;
this.chainCode = null;
this.parentFingerprint = 0;
if (!opt || typeof opt !== 'object') {
throw new Error('HDKey.constructor must not be called directly');
}
this.versions = opt.versions || BITCOIN_VERSIONS;
this.depth = opt.depth || 0;
this.chainCode = opt.chainCode;
this.index = opt.index || 0;
this.parentFingerprint = opt.parentFingerprint || 0;
if (!this.depth) {
if (this.parentFingerprint || this.index) {
throw new Error('HDKey: zero depth with non-zero index/parent fingerprint');
}
}
if (opt.publicKey && opt.privateKey) {
throw new Error('HDKey: publicKey and privateKey at same time.');
}
if (opt.privateKey) {
if (!secp.utils.isValidPrivateKey(opt.privateKey)) {
throw new Error('Invalid private key');
}
this.privKey =
typeof opt.privateKey === 'bigint' ? opt.privateKey : bytesToNumber(opt.privateKey);
this.privKeyBytes = numberToBytes(this.privKey);
this.pubKey = secp.getPublicKey(opt.privateKey, true);
}
else if (opt.publicKey) {
this.pubKey = Point.fromHex(opt.publicKey).toRawBytes(true);
}
else {
throw new Error('HDKey: no public or private key provided');
}
this.pubHash = hash160(this.pubKey);
}
derive(path) {
if (!/^[mM]'?/.test(path)) {
throw new Error('Path must start with "m" or "M"');
}
if (/^[mM]'?$/.test(path)) {
return this;
}
const parts = path.replace(/^[mM]'?\//, '').split('/');
let child = this;
for (const c of parts) {
const m = /^(\d+)('?)$/.exec(c);
if (!m || m.length !== 3) {
throw new Error(`Invalid child index: ${c}`);
}
let idx = +m[1];
if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) {
throw new Error('Invalid index');
}
if (m[2] === "'") {
idx += HARDENED_OFFSET;
}
child = child.deriveChild(idx);
}
return child;
}
deriveChild(index) {
if (!this.pubKey || !this.chainCode) {
throw new Error('No publicKey or chainCode set');
}
let data = toU32(index);
if (index >= HARDENED_OFFSET) {
const priv = this.privateKey;
if (!priv) {
throw new Error('Could not derive hardened child key');
}
data = concatBytes(new Uint8Array([0]), priv, data);
}
else {
data = concatBytes(this.pubKey, data);
}
const I = hmac(sha512, this.chainCode, data);
const childTweak = bytesToNumber(I.slice(0, 32));
const chainCode = I.slice(32);
if (!secp.utils.isValidPrivateKey(childTweak)) {
throw new Error('Tweak bigger than curve order');
}
const opt = {
versions: this.versions,
chainCode,
depth: this.depth + 1,
parentFingerprint: this.fingerprint,
index,
};
try {
if (this.privateKey) {
const added = mod(this.privKey + childTweak, secp.CURVE.n);
if (!secp.utils.isValidPrivateKey(added)) {
throw new Error('The tweak was out of range or the resulted private key is invalid');
}
opt.privateKey = added;
}
else {
const added = Point.fromHex(this.pubKey).add(Point.fromPrivateKey(childTweak));
if (added.equals(Point.ZERO)) {
throw new Error('The tweak was equal to negative P, which made the result key invalid');
}
opt.publicKey = added.toRawBytes(true);
}
return new HDKey(opt);
}
catch (err) {
return this.deriveChild(index + 1);
}
}
sign(hash) {
if (!this.privateKey) {
throw new Error('No privateKey set!');
}
assertBytes(hash, 32);
return secp.sign(hash, this.privKey).toCompactRawBytes();
}
verify(hash, signature) {
assertBytes(hash, 32);
assertBytes(signature, 64);
if (!this.publicKey) {
throw new Error('No publicKey set!');
}
let sig;
try {
sig = secp.Signature.fromCompact(signature);
}
catch (error) {
return false;
}
return secp.verify(sig, hash, this.publicKey);
}
wipePrivateData() {
this.privKey = undefined;
if (this.privKeyBytes) {
this.privKeyBytes.fill(0);
this.privKeyBytes = undefined;
}
return this;
}
toJSON() {
return {
xpriv: this.privateExtendedKey,
xpub: this.publicExtendedKey,
};
}
serialize(version, key) {
if (!this.chainCode) {
throw new Error('No chainCode set');
}
assertBytes(key, 33);
return concatBytes(toU32(version), new Uint8Array([this.depth]), toU32(this.parentFingerprint), toU32(this.index), this.chainCode, key);
}
}
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
{
"type": "module",
"browser": {
"crypto": false
}
}
+49
View File
@@ -0,0 +1,49 @@
export declare const HARDENED_OFFSET: number;
export interface Versions {
private: number;
public: number;
}
interface HDKeyOpt {
versions: Versions;
depth?: number;
index?: number;
parentFingerprint?: number;
chainCode: Uint8Array;
publicKey?: Uint8Array;
privateKey?: Uint8Array | bigint;
}
export declare class HDKey {
get fingerprint(): number;
get identifier(): Uint8Array | undefined;
get pubKeyHash(): Uint8Array | undefined;
get privateKey(): Uint8Array | null;
get publicKey(): Uint8Array | null;
get privateExtendedKey(): string;
get publicExtendedKey(): string;
static fromMasterSeed(seed: Uint8Array, versions?: Versions): HDKey;
static fromExtendedKey(base58key: string, versions?: Versions): HDKey;
static fromJSON(json: {
xpriv: string;
}): HDKey;
readonly versions: Versions;
readonly depth: number;
readonly index: number;
readonly chainCode: Uint8Array | null;
readonly parentFingerprint: number;
private privKey?;
private privKeyBytes?;
private pubKey?;
private pubHash;
constructor(opt: HDKeyOpt);
derive(path: string): HDKey;
deriveChild(index: number): HDKey;
sign(hash: Uint8Array): Uint8Array;
verify(hash: Uint8Array, signature: Uint8Array): boolean;
wipePrivateData(): this;
toJSON(): {
xpriv: string;
xpub: string;
};
private serialize;
}
export {};
+261
View File
@@ -0,0 +1,261 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HDKey = exports.HARDENED_OFFSET = void 0;
const hmac_1 = require("@noble/hashes/hmac");
const ripemd160_1 = require("@noble/hashes/ripemd160");
const sha256_1 = require("@noble/hashes/sha256");
const sha512_1 = require("@noble/hashes/sha512");
const _assert_1 = require("@noble/hashes/_assert");
const utils_1 = require("@noble/hashes/utils");
const secp256k1_1 = require("@noble/curves/secp256k1");
const modular_1 = require("@noble/curves/abstract/modular");
const base_1 = require("@scure/base");
const Point = secp256k1_1.secp256k1.ProjectivePoint;
const base58check = (0, base_1.base58check)(sha256_1.sha256);
function bytesToNumber(bytes) {
return BigInt(`0x${(0, utils_1.bytesToHex)(bytes)}`);
}
function numberToBytes(num) {
return (0, utils_1.hexToBytes)(num.toString(16).padStart(64, '0'));
}
const MASTER_SECRET = (0, utils_1.utf8ToBytes)('Bitcoin seed');
const BITCOIN_VERSIONS = { private: 0x0488ade4, public: 0x0488b21e };
exports.HARDENED_OFFSET = 0x80000000;
const hash160 = (data) => (0, ripemd160_1.ripemd160)((0, sha256_1.sha256)(data));
const fromU32 = (data) => (0, utils_1.createView)(data).getUint32(0, false);
const toU32 = (n) => {
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);
(0, utils_1.createView)(buf).setUint32(0, n, false);
return buf;
};
class HDKey {
get fingerprint() {
if (!this.pubHash) {
throw new Error('No publicKey set!');
}
return fromU32(this.pubHash);
}
get identifier() {
return this.pubHash;
}
get pubKeyHash() {
return this.pubHash;
}
get privateKey() {
return this.privKeyBytes || null;
}
get publicKey() {
return this.pubKey || null;
}
get privateExtendedKey() {
const priv = this.privateKey;
if (!priv) {
throw new Error('No private key');
}
return base58check.encode(this.serialize(this.versions.private, (0, utils_1.concatBytes)(new Uint8Array([0]), priv)));
}
get publicExtendedKey() {
if (!this.pubKey) {
throw new Error('No public key');
}
return base58check.encode(this.serialize(this.versions.public, this.pubKey));
}
static fromMasterSeed(seed, versions = BITCOIN_VERSIONS) {
(0, _assert_1.bytes)(seed);
if (8 * seed.length < 128 || 8 * seed.length > 512) {
throw new Error(`HDKey: wrong seed length=${seed.length}. Should be between 128 and 512 bits; 256 bits is advised)`);
}
const I = (0, hmac_1.hmac)(sha512_1.sha512, MASTER_SECRET, seed);
return new HDKey({
versions,
chainCode: I.slice(32),
privateKey: I.slice(0, 32),
});
}
static fromExtendedKey(base58key, versions = BITCOIN_VERSIONS) {
const keyBuffer = base58check.decode(base58key);
const keyView = (0, utils_1.createView)(keyBuffer);
const version = keyView.getUint32(0, false);
const opt = {
versions,
depth: keyBuffer[4],
parentFingerprint: keyView.getUint32(5, false),
index: keyView.getUint32(9, false),
chainCode: keyBuffer.slice(13, 45),
};
const key = keyBuffer.slice(45);
const isPriv = key[0] === 0;
if (version !== versions[isPriv ? 'private' : 'public']) {
throw new Error('Version mismatch');
}
if (isPriv) {
return new HDKey({ ...opt, privateKey: key.slice(1) });
}
else {
return new HDKey({ ...opt, publicKey: key });
}
}
static fromJSON(json) {
return HDKey.fromExtendedKey(json.xpriv);
}
constructor(opt) {
this.depth = 0;
this.index = 0;
this.chainCode = null;
this.parentFingerprint = 0;
if (!opt || typeof opt !== 'object') {
throw new Error('HDKey.constructor must not be called directly');
}
this.versions = opt.versions || BITCOIN_VERSIONS;
this.depth = opt.depth || 0;
this.chainCode = opt.chainCode;
this.index = opt.index || 0;
this.parentFingerprint = opt.parentFingerprint || 0;
if (!this.depth) {
if (this.parentFingerprint || this.index) {
throw new Error('HDKey: zero depth with non-zero index/parent fingerprint');
}
}
if (opt.publicKey && opt.privateKey) {
throw new Error('HDKey: publicKey and privateKey at same time.');
}
if (opt.privateKey) {
if (!secp256k1_1.secp256k1.utils.isValidPrivateKey(opt.privateKey)) {
throw new Error('Invalid private key');
}
this.privKey =
typeof opt.privateKey === 'bigint' ? opt.privateKey : bytesToNumber(opt.privateKey);
this.privKeyBytes = numberToBytes(this.privKey);
this.pubKey = secp256k1_1.secp256k1.getPublicKey(opt.privateKey, true);
}
else if (opt.publicKey) {
this.pubKey = Point.fromHex(opt.publicKey).toRawBytes(true);
}
else {
throw new Error('HDKey: no public or private key provided');
}
this.pubHash = hash160(this.pubKey);
}
derive(path) {
if (!/^[mM]'?/.test(path)) {
throw new Error('Path must start with "m" or "M"');
}
if (/^[mM]'?$/.test(path)) {
return this;
}
const parts = path.replace(/^[mM]'?\//, '').split('/');
let child = this;
for (const c of parts) {
const m = /^(\d+)('?)$/.exec(c);
if (!m || m.length !== 3) {
throw new Error(`Invalid child index: ${c}`);
}
let idx = +m[1];
if (!Number.isSafeInteger(idx) || idx >= exports.HARDENED_OFFSET) {
throw new Error('Invalid index');
}
if (m[2] === "'") {
idx += exports.HARDENED_OFFSET;
}
child = child.deriveChild(idx);
}
return child;
}
deriveChild(index) {
if (!this.pubKey || !this.chainCode) {
throw new Error('No publicKey or chainCode set');
}
let data = toU32(index);
if (index >= exports.HARDENED_OFFSET) {
const priv = this.privateKey;
if (!priv) {
throw new Error('Could not derive hardened child key');
}
data = (0, utils_1.concatBytes)(new Uint8Array([0]), priv, data);
}
else {
data = (0, utils_1.concatBytes)(this.pubKey, data);
}
const I = (0, hmac_1.hmac)(sha512_1.sha512, this.chainCode, data);
const childTweak = bytesToNumber(I.slice(0, 32));
const chainCode = I.slice(32);
if (!secp256k1_1.secp256k1.utils.isValidPrivateKey(childTweak)) {
throw new Error('Tweak bigger than curve order');
}
const opt = {
versions: this.versions,
chainCode,
depth: this.depth + 1,
parentFingerprint: this.fingerprint,
index,
};
try {
if (this.privateKey) {
const added = (0, modular_1.mod)(this.privKey + childTweak, secp256k1_1.secp256k1.CURVE.n);
if (!secp256k1_1.secp256k1.utils.isValidPrivateKey(added)) {
throw new Error('The tweak was out of range or the resulted private key is invalid');
}
opt.privateKey = added;
}
else {
const added = Point.fromHex(this.pubKey).add(Point.fromPrivateKey(childTweak));
if (added.equals(Point.ZERO)) {
throw new Error('The tweak was equal to negative P, which made the result key invalid');
}
opt.publicKey = added.toRawBytes(true);
}
return new HDKey(opt);
}
catch (err) {
return this.deriveChild(index + 1);
}
}
sign(hash) {
if (!this.privateKey) {
throw new Error('No privateKey set!');
}
(0, _assert_1.bytes)(hash, 32);
return secp256k1_1.secp256k1.sign(hash, this.privKey).toCompactRawBytes();
}
verify(hash, signature) {
(0, _assert_1.bytes)(hash, 32);
(0, _assert_1.bytes)(signature, 64);
if (!this.publicKey) {
throw new Error('No publicKey set!');
}
let sig;
try {
sig = secp256k1_1.secp256k1.Signature.fromCompact(signature);
}
catch (error) {
return false;
}
return secp256k1_1.secp256k1.verify(sig, hash, this.publicKey);
}
wipePrivateData() {
this.privKey = undefined;
if (this.privKeyBytes) {
this.privKeyBytes.fill(0);
this.privKeyBytes = undefined;
}
return this;
}
toJSON() {
return {
xpriv: this.privateExtendedKey,
xpub: this.publicExtendedKey,
};
}
serialize(version, key) {
if (!this.chainCode) {
throw new Error('No chainCode set');
}
(0, _assert_1.bytes)(key, 33);
return (0, utils_1.concatBytes)(toU32(version), new Uint8Array([this.depth]), toU32(this.parentFingerprint), toU32(this.index), this.chainCode, key);
}
}
exports.HDKey = HDKey;
//# sourceMappingURL=index.js.map
+1
View File
File diff suppressed because one or more lines are too long
+72
View File
@@ -0,0 +1,72 @@
{
"name": "@scure/bip32",
"version": "1.3.1",
"description": "Secure, audited & minimal implementation of BIP32 hierarchical deterministic (HD) wallets over secp256k1",
"files": [
"index.ts",
"lib/index.js",
"lib/index.d.ts",
"lib/index.js.map",
"lib/esm/package.json",
"lib/esm/index.js",
"lib/esm/index.js.map"
],
"main": "lib/index.js",
"module": "lib/esm/index.js",
"types": "lib/index.d.ts",
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./lib/esm/index.js",
"default": "./lib/index.js"
}
},
"dependencies": {
"@noble/curves": "~1.1.0",
"@noble/hashes": "~1.3.1",
"@scure/base": "~1.1.0"
},
"devDependencies": {
"micro-should": "0.4.0",
"prettier": "2.8.4",
"typescript": "5.0.2"
},
"author": "Paul Miller (https://paulmillr.com)",
"homepage": "https://paulmillr.com/",
"repository": {
"type": "git",
"url": "https://github.com/paulmillr/scure-bip32.git"
},
"contributors": [
{
"name": "Patricio Palladino",
"email": "patricio@nomiclabs.io"
},
{
"name": "Paul Miller",
"url": "https://paulmillr.com"
}
],
"license": "MIT",
"scripts": {
"build": "tsc -d && tsc -p tsconfig.esm.json",
"lint": "prettier --check 'index.ts' 'test/*.test.ts'",
"format": "prettier --write 'index.ts' 'test/*.test.ts'",
"test": "cd test && tsc && node hdkey.test.js"
},
"keywords": [
"bip32",
"hierarchical",
"deterministic",
"hd key",
"bip0032",
"bip-32",
"bip39",
"micro",
"scure",
"mnemonic",
"phrase",
"code"
],
"funding": "https://paulmillr.com/funding/"
}