include node_modules so release .zip is deployable
This commit is contained in:
+21
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"browser": {
|
||||
"crypto": false
|
||||
}
|
||||
}
|
||||
+49
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
+72
@@ -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/"
|
||||
}
|
||||
Reference in New Issue
Block a user