UNPKG

@safeheron/crypto-bip32

Version:

HDKey in js(embrace bip32-secp256k1, bip32-ed25519)

335 lines 14 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Ed25519HDKey = void 0; const assert = require("assert"); const bs58check_1 = require("bs58check"); const BN = require("bn.js"); const elliptic = require("elliptic"); const cryptoJS = require("crypto-js"); const Ed25519 = new elliptic.eddsa('ed25519'); const crypto_utils_1 = require("@safeheron/crypto-utils"); const MASTER_SECRET = cryptoJS.enc.Utf8.parse('Bitcoin seed'); const HARDENED_OFFSET = 0x80000000; const LEN = 78; const ZERO = new BN('0', 16); // Bitcoin hardcoded by default, can use package `coininfo` for others const BITCOIN_VERSIONS = { private: 0x03126f7c, public: 0x031273b7 }; // function Buffer2CryptoJSArray(buffer){ // const hexStr = buffer.toString('hex') // return Hex.pad64(hexStr) // } function HashForPrivateDerive(chainCode, index, privateKey) { // data = 0x00 || ser256(kpar) || ser32(index) let data = '00' + crypto_utils_1.Hex.fromBytes(privateKey.toArray('le', 32)) + crypto_utils_1.Hex.pad8(index.toString(16)); let chainCodeWordArray = crypto_utils_1.Hex.toCryptoJSBytes(crypto_utils_1.Hex.pad64(chainCode.toString(16))); let dataWordArray = cryptoJS.enc.Hex.parse(data); let h = cryptoJS.HmacSHA512(dataWordArray, chainCodeWordArray); let hStr = cryptoJS.enc.Hex.stringify(h); return hStr; } function HashForPublicDerive(chainCode, index, publicKey) { // Mark: for ed25519 the length of serP(point(kpar)) is 32 // data = 0x05 || serP(point(kpar)) || ser32(index) // = 0x05 || serP(Kpar) || ser32(index) let data = '05' + crypto_utils_1.Hex.fromBytes(Ed25519.encodePoint(publicKey)) + crypto_utils_1.Hex.pad8(index.toString(16)); let chainCodeWordArray = crypto_utils_1.Hex.toCryptoJSBytes(crypto_utils_1.Hex.pad64(chainCode.toString(16))); let dataWordArray = cryptoJS.enc.Hex.parse(data); let h = cryptoJS.HmacSHA512(dataWordArray, chainCodeWordArray); let hStr = cryptoJS.enc.Hex.stringify(h); return hStr; } function hash160(wordArray) { let sha = cryptoJS.SHA256(wordArray); return cryptoJS.RIPEMD160(sha); } class Ed25519HDKey { constructor(versions) { this.versions = versions || BITCOIN_VERSIONS; this.depth = 0; this.index = 0; this._privateKey = null; // BN this._publicKey = null; // elliptic point this.chainCode = null; this._fingerprint = 0; this.parentFingerprint = 0; this._identifier = null; } get fingerprint() { return this._fingerprint; } get identifier() { return this._identifier; } get pubKeyHash() { return this._identifier; } get privateKey() { return this._privateKey; } set privateKey(value) { if (typeof value === 'string') { assert.equal(value.length, 32 * 2, 'Private key must be 32 bytes.'); var valueNum = new BN(value, 16); if (valueNum.gt(Ed25519.curve.n) || valueNum.eq(0)) throw 'Invalid private key'; this._privateKey = valueNum; } else { //value = value.umod(Ed25519.curve.n) // Check if value % order === 0 assert(!value.gt(Ed25519.curve.n) && !value.eq(0), 'Invalid private key'); this._privateKey = value; } this._publicKey = Ed25519.g.mul(this._privateKey); this._identifier = hash160(cryptoJS.enc.Hex.parse(crypto_utils_1.Hex.fromBytes(Ed25519.encodePoint(this.publicKey)))); this._fingerprint = parseInt(cryptoJS.enc.Hex.stringify(this._identifier).substr(0, 4 * 2), 16); } get privateKeyAsHex() { return crypto_utils_1.Hex.fromBytes(this._privateKey.toArray('le', 32)); } get publicKey() { return this._publicKey; } set publicKey(value) { if (typeof value === 'string') { assert.equal(value.length, 32 * 2, 'Public key must be 32 bytes(Ed25519 public key).'); this._publicKey = Ed25519.decodePoint(value, 'hex'); } else { assert(Ed25519.curve.validate(value), 'Invalid public key'); this._publicKey = value; } this._identifier = hash160(cryptoJS.enc.Hex.parse(crypto_utils_1.Hex.fromBytes(Ed25519.encodePoint(this.publicKey)))); this._fingerprint = parseInt(cryptoJS.enc.Hex.stringify(this._identifier).substr(0, 4 * 2), 16); this._privateKey = null; } get publicKeyAsHex() { return crypto_utils_1.Hex.fromBytes(Ed25519.encodePoint(this._publicKey)); } get xprv() { if (this._privateKey) { // with prefix let keyBuff = '00' + this.privateKeyAsHex; return bs58check_1.default.encode(Ed25519HDKey.serialize(this, this.versions.private, keyBuff)); } else return null; } get xpub() { let keyBuff = '00' + this.publicKeyAsHex; return bs58check_1.default.encode(Ed25519HDKey.serialize(this, this.versions.public, keyBuff)); } derive(path) { if (path === 'm' || path === 'M' || path === "m'" || path === "M'") { return this; } let entries = path.split('/'); let hdkey = this; entries.forEach(function (c, i) { if (i === 0) { assert(/^[mM]{1}/.test(c), 'Path must start with "m" or "M"'); return; } let hardened = (c.length > 1) && (c[c.length - 1] === "'"); if (c[c.length - 1] === "'") { c = c.substring(0, c.length - 1); } if (/^\d+$/.test(c)) { let childIndex = parseInt(c, 10); // & (HARDENED_OFFSET - 1) assert(childIndex < HARDENED_OFFSET, 'Invalid index'); if (hardened) childIndex += HARDENED_OFFSET; // @ts-ignore hdkey = hdkey.deriveChild(childIndex); } else { throw "Invalid index"; } }); return hdkey; } deriveChild(index) { let isHardened = index >= HARDENED_OFFSET; let I = ""; if (isHardened) { // Hardened child assert(this._privateKey, 'Could not derive hardened child key'); I = HashForPrivateDerive(this.chainCode, index, this._privateKey); } else { // No-harden child I = HashForPublicDerive(this.chainCode, index, this._publicKey); } // Big endian let IL = new BN(crypto_utils_1.Hex.reverseHex(I.substr(0, 64)), 16); let IR = new BN(I.substr(64), 16); let hd = new Ed25519HDKey(this.versions); // Private parent key -> private child key if (this._privateKey) { // ki = parse256(IL) + kpar (mod n) try { let _privateKey = this._privateKey.add(IL).umod(Ed25519.curve.n); // throw if IL >= n || (privateKey + IL) === 0 if (_privateKey.eqn(0)) throw "Invalid child private key!"; hd.privateKey = _privateKey; } catch (err) { // In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i return this.deriveChild(index + 1); } // Public parent key -> public child key } else { // Ki = point(parse256(IL)) + Kpar // = G*IL + Kpar try { let _publicKey = this._publicKey.add(Ed25519.g.mul(IL)); // throw if IL >= n || (g**IL + publicKey) is infinity if (_publicKey.isInfinity()) throw "Invalid child public key!"; hd.publicKey = _publicKey; } catch (err) { // In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i return this.deriveChild(index + 1); } } hd.chainCode = IR; hd.depth = this.depth + 1; hd.parentFingerprint = this.fingerprint; // .readUInt32BE(0) hd.index = index; return hd; } publicDerive(path) { let delta = ZERO; if (path.indexOf("'") != -1) { throw "Could not derive hardened child key!"; } let entries = path.split('/'); let hdkey = this; entries.forEach(function (c, i) { if (i === 0) { assert(/^[mM]{1}/.test(c), 'Path must start with "m" or "M"'); return; } // let hardened = (c.length > 1) && (c[c.length - 1] === "'") if (/^\d+$/.test(c)) { let childIndex = parseInt(c, 10); // & (HARDENED_OFFSET - 1) assert(childIndex < HARDENED_OFFSET, 'Invalid index'); // if (hardened) childIndex += HARDENED_OFFSET const [_hdkey, _delta] = hdkey.publicDeriveChild(childIndex); delta = delta.add(_delta).umod(Ed25519.curve.n); // @ts-ignore hdkey = _hdkey; } else { throw "Invalid index"; } }); return [hdkey, delta]; } publicDeriveChild(index) { //let isHardened = index >= HARDENED_OFFSET // No-Harden child let I = HashForPublicDerive(this.chainCode, index, this._publicKey); // IL: little-endian let IL = new BN(crypto_utils_1.Hex.reverseHex(I.substr(0, 64)), 16); // It doesn't matter for iR's encode. let IR = new BN(I.substr(64), 16); let hd = new Ed25519HDKey(this.versions); // Public parent key -> public child key // Ki = point(parse256(IL)) + Kpar // = G*IL + Kpar try { let _publicKey = this._publicKey.add(Ed25519.g.mul(IL)); // throw if IL >= n || (g**IL + publicKey) is infinity if (_publicKey.isInfinity()) throw "Invalid child public key!"; hd.publicKey = _publicKey; } catch (err) { // In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i return this.publicDeriveChild(index + 1); } hd.chainCode = IR; hd.depth = this.depth + 1; hd.parentFingerprint = this.fingerprint; // .readUInt32BE(0) hd.index = index; let delta = IL; return [hd, delta]; } sign(hash) { throw "Not implemented!"; } verify(hash, signature) { throw "Not implemented!"; } static fromMasterSeed(seedWordArray) { let I = cryptoJS.enc.Hex.stringify(cryptoJS.HmacSHA512(seedWordArray, MASTER_SECRET)); // Big endian let IL = new BN(crypto_utils_1.Hex.reverseHex(I.substr(0, 64)), 16); let IR = new BN(I.substr(64), 16); IL = IL.umod(Ed25519.curve.n); if (IL.eqn(0)) { throw "Invalid Master Key!"; } let hdkey = new Ed25519HDKey(BITCOIN_VERSIONS); hdkey.privateKey = IL; hdkey.chainCode = IR; return hdkey; } static fromMasterSeedHex(seedHex) { return Ed25519HDKey.fromMasterSeed(cryptoJS.enc.Hex.parse(seedHex)); } static fromPublicKeyAndChainCode(publicKey, chainCode) { let hdkey = new Ed25519HDKey(BITCOIN_VERSIONS); hdkey.publicKey = publicKey; hdkey.chainCode = chainCode; return hdkey; } static fromPrivateKeyAndChainCode(privateKey, chainCode) { let hdkey = new Ed25519HDKey(BITCOIN_VERSIONS); hdkey.privateKey = privateKey; hdkey.chainCode = chainCode; return hdkey; } static fromExtendedKey(base58key) { // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) // Mark: key(33) = 00 + priv(32)/pub(32) let versions = BITCOIN_VERSIONS; let hdkey = new Ed25519HDKey(versions); let keyBuffer = bs58check_1.default.decode(base58key); let keyBufferHex = crypto_utils_1.Hex.fromBytes(keyBuffer); let version = parseInt(keyBufferHex.substr(0, 4 * 2), 16); assert(version === versions.private || version === versions.public, 'Version mismatch: does not match private or public'); hdkey.depth = parseInt(keyBufferHex.substr(4 * 2, 2), 16); hdkey.parentFingerprint = parseInt(keyBufferHex.substr(5 * 2, 4 * 2), 16); hdkey.index = parseInt(keyBufferHex.substr(9 * 2, 4 * 2), 16); hdkey.chainCode = new BN(keyBufferHex.substr(13 * 2, 32 * 2), 16); let prefix = parseInt(keyBufferHex.substr(45 * 2, 2), 16); assert(prefix === 0, "prefix should be zero"); if (version === versions.private) { // private hdkey.privateKey = new BN(crypto_utils_1.Hex.reverseHex(keyBufferHex.substr(46 * 2, 32 * 2)), 16); // cut off first 0x0 byte } else { hdkey.publicKey = Ed25519.decodePoint(keyBufferHex.substr(46 * 2, 32 * 2)); } return hdkey; } static serialize(hdkey, version, key) { // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) // Mark: key(33) = 00 + priv(32)/pub(32) let buffer = Buffer.allocUnsafe(LEN); buffer.writeUInt32BE(version, 0); buffer.writeUInt8(hdkey.depth, 4); let fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; buffer.writeUInt32BE(fingerprint, 5); buffer.writeUInt32BE(hdkey.index, 9); let chainCodeBuffer = Buffer.from(crypto_utils_1.Hex.pad64(hdkey.chainCode.toString(16)), "hex"); chainCodeBuffer.copy(buffer, 13); Buffer.from(key, 'hex').copy(buffer, 45); return buffer; } } exports.Ed25519HDKey = Ed25519HDKey; Ed25519HDKey.HARDENED_OFFSET = HARDENED_OFFSET; //# sourceMappingURL=Ed25519HDKey.js.map