UNPKG

@zlattice/lattice-js

Version:

Lattice blockchain TypeScript SDK with dual module support (CJS + ESM)

348 lines 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HDKey = exports.HARDENED_OFFSET = void 0; const index_1 = require("../common/index.js"); const sm2_1 = require("../crypto/sm2/index.js"); const ec_1 = require("../crypto/sm2/ec.js"); const modular_1 = require("@noble/curves/abstract/modular"); const secp256k1_1 = require("@noble/curves/secp256k1"); const hmac_1 = require("@noble/hashes/hmac"); const legacy_1 = require("@noble/hashes/legacy"); const sha2_1 = require("@noble/hashes/sha2"); const utils_1 = require("@noble/hashes/utils"); const base_1 = require("@scure/base"); const Point = secp256k1_1.secp256k1.ProjectivePoint; const base58check = (0, base_1.createBase58check)(sha2_1.sha256); function bytesToNumber(bytes) { (0, utils_1.abytes)(bytes); const h = bytes.length === 0 ? "0" : (0, utils_1.bytesToHex)(bytes); return BigInt(`0x${h}`); } function numberToBytes(num) { if (typeof num !== "bigint") throw new Error("bigint expected"); 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 }; /** Hardened offset from Bitcoin, default */ exports.HARDENED_OFFSET = 0x80000000; const hash160 = (data) => (0, legacy_1.ripemd160)((0, sha2_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, should be from 0 to 2**32-1, got ${n}`); } const buf = new Uint8Array(4); (0, utils_1.createView)(buf).setUint32(0, n, false); return buf; }; /** * HDKey from BIP32 * @example ```js const hdkey1 = HDKey.fromMasterSeed(seed); const hdkey2 = HDKey.fromExtendedKey(base58key); const hdkey3 = HDKey.fromJSON({ xpriv: string }); ``` */ 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, curve = index_1.Curves.Sm2p256v1) { (0, utils_1.abytes)(seed); if (8 * seed.length < 128 || 8 * seed.length > 512) { throw new Error(`HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got ${seed.length}`); } const I = (0, hmac_1.hmac)(sha2_1.sha512, MASTER_SECRET, seed); return new HDKey({ versions, chainCode: I.slice(32), privateKey: I.slice(0, 32), curve }); } static fromExtendedKey(base58key, versions = BITCOIN_VERSIONS, curve = index_1.Curves.Sm2p256v1) { // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) 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), curve }); } return new HDKey({ ...opt, publicKey: key, curve }); } 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 || null; this.index = opt.index || 0; this.parentFingerprint = opt.parentFingerprint || 0; this.curve = opt.curve || index_1.Curves.Sm2p256v1; 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) { switch (this.curve) { case index_1.Curves.Sm2p256v1: if (!ec_1.sm2Curve.utils.isValidPrivateKey((0, utils_1.bytesToHex)(typeof opt.privateKey === "bigint" ? numberToBytes(opt.privateKey) : 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 = (0, utils_1.hexToBytes)((0, sm2_1.getPublicKeyFromPrivateKey)(typeof opt.privateKey === "bigint" ? (0, utils_1.bytesToHex)(numberToBytes(opt.privateKey)) : (0, utils_1.bytesToHex)(opt.privateKey), true)); break; case index_1.Curves.Secp256k1: 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); break; default: throw new Error(`Unsupported curve: ${opt.curve}`); } } else if (opt.publicKey) { if (opt.curve === index_1.Curves.Sm2p256v1) { // compressed public key const compressedPublicKey = (0, sm2_1.compressPublicKeyHex)(`0x${(0, utils_1.bytesToHex)(opt.publicKey)}`); this.pubKey = (0, utils_1.hexToBytes)(compressedPublicKey); } else { 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); } 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("/"); // tslint:disable-next-line let child = this; for (const c of parts) { const m = /^(\d+)('?)$/.exec(c); const m1 = m?.[1]; if (!m || m.length !== 3 || typeof m1 !== "string") { throw new Error(`invalid child index: ${c}`); } let idx = +m1; if (!Number.isSafeInteger(idx) || idx >= exports.HARDENED_OFFSET) { throw new Error("Invalid index"); } // hardened key 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) { // Hardened const priv = this.privateKey; if (!priv) { throw new Error("Could not derive hardened child key"); } // Hardened child: 0x00 || ser256(kpar) || ser32(index) data = (0, utils_1.concatBytes)(new Uint8Array([0]), priv, data); } else { // Normal child: serP(point(kpar)) || ser32(index) data = (0, utils_1.concatBytes)(this.pubKey, data); } const I = (0, hmac_1.hmac)(sha2_1.sha512, this.chainCode, data); const childTweak = bytesToNumber(I.slice(0, 32)); const chainCode = I.slice(32); if (this.curve === index_1.Curves.Sm2p256v1) { if (!ec_1.sm2Curve.utils.isValidPrivateKey(childTweak)) { throw new Error("Tweak bigger than curve order"); } } else { 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 { // Private parent key -> private child key if (this.privateKey) { if (this.curve === index_1.Curves.Sm2p256v1) { const added = (0, modular_1.mod)(this.privKey + childTweak, ec_1.sm2Curve.CURVE.n); if (!ec_1.sm2Curve.utils.isValidPrivateKey((0, utils_1.bytesToHex)(numberToBytes(added)))) { throw new Error("The tweak was out of range or the resulted private key is invalid"); } opt.privateKey = added; } else { 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 { if (this.curve === index_1.Curves.Sm2p256v1) { const added = ec_1.sm2Curve.ProjectivePoint.fromHex(this.pubKey).add(ec_1.sm2Curve.ProjectivePoint.fromPrivateKey(childTweak)); if (added.equals(ec_1.sm2Curve.ProjectivePoint.ZERO)) { throw new Error("The tweak was equal to negative P, which made the result key invalid"); } opt.publicKey = added.toRawBytes(true); } 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); } } sign(hash) { if (!this.privateKey) { throw new Error("No privateKey set!"); } (0, utils_1.abytes)(hash, 32); return this.curve === index_1.Curves.Sm2p256v1 ? ec_1.sm2Curve.sign(hash, this.privKey).toCompactRawBytes() : secp256k1_1.secp256k1.sign(hash, this.privKey).toCompactRawBytes(); } verify(hash, signature) { (0, utils_1.abytes)(hash, 32); (0, utils_1.abytes)(signature, 64); if (!this.publicKey) { throw new Error("No publicKey set!"); } let sig; try { sig = this.curve === index_1.Curves.Sm2p256v1 ? ec_1.sm2Curve.Signature.fromCompact(signature) : secp256k1_1.secp256k1.Signature.fromCompact(signature); } catch (error) { return false; } return this.curve === index_1.Curves.Sm2p256v1 ? ec_1.sm2Curve.verify(sig, hash, this.publicKey) : 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, utils_1.abytes)(key, 33); // version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || 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=hd-key.js.map