193 lines
7.2 KiB
JavaScript
193 lines
7.2 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.Slip10 = exports.Slip10RawIndex = exports.Slip10Curve = void 0;
|
|
exports.slip10CurveFromString = slip10CurveFromString;
|
|
exports.pathToString = pathToString;
|
|
exports.stringToPath = stringToPath;
|
|
const encoding_1 = require("@cosmjs/encoding");
|
|
const math_1 = require("@cosmjs/math");
|
|
const utils_1 = require("@cosmjs/utils");
|
|
const secp256k1_1 = require("@noble/curves/secp256k1");
|
|
const hmac_1 = require("./hmac");
|
|
const sha_1 = require("./sha");
|
|
/**
|
|
* Raw values must match the curve string in SLIP-0010 master key generation
|
|
*
|
|
* @see https://github.com/satoshilabs/slips/blob/master/slip-0010.md#master-key-generation
|
|
*/
|
|
var Slip10Curve;
|
|
(function (Slip10Curve) {
|
|
Slip10Curve["Secp256k1"] = "Bitcoin seed";
|
|
Slip10Curve["Ed25519"] = "ed25519 seed";
|
|
})(Slip10Curve || (exports.Slip10Curve = Slip10Curve = {}));
|
|
function bytesToUnsignedBigInt(a) {
|
|
return BigInt("0x" + (0, encoding_1.toHex)(a));
|
|
}
|
|
function intTo32be(n) {
|
|
(0, utils_1.assert)(n >= 0n);
|
|
(0, utils_1.assert)(n < 2n ** (32n * 8n));
|
|
// 32 bytes is 64 hexadecimal characters
|
|
const hex = n.toString(16).padStart(64, "0");
|
|
return (0, encoding_1.fromHex)(hex);
|
|
}
|
|
/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
|
|
/**
|
|
* Reverse mapping of Slip10Curve
|
|
*/
|
|
function slip10CurveFromString(curveString) {
|
|
switch (curveString) {
|
|
case Slip10Curve.Ed25519:
|
|
return Slip10Curve.Ed25519;
|
|
case Slip10Curve.Secp256k1:
|
|
return Slip10Curve.Secp256k1;
|
|
default:
|
|
throw new Error(`Unknown curve string: '${curveString}'`);
|
|
}
|
|
}
|
|
class Slip10RawIndex extends math_1.Uint32 {
|
|
static hardened(hardenedIndex) {
|
|
return new Slip10RawIndex(hardenedIndex + 2 ** 31);
|
|
}
|
|
static normal(normalIndex) {
|
|
return new Slip10RawIndex(normalIndex);
|
|
}
|
|
isHardened() {
|
|
return this.data >= 2 ** 31;
|
|
}
|
|
}
|
|
exports.Slip10RawIndex = Slip10RawIndex;
|
|
// Universal private key derivation according to
|
|
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md
|
|
class Slip10 {
|
|
static derivePath(curve, seed, path) {
|
|
let result = this.master(curve, seed);
|
|
for (const rawIndex of path) {
|
|
result = this.child(curve, result.privkey, result.chainCode, rawIndex);
|
|
}
|
|
return result;
|
|
}
|
|
static master(curve, seed) {
|
|
const i = new hmac_1.Hmac(sha_1.Sha512, (0, encoding_1.toAscii)(curve)).update(seed).digest();
|
|
const il = i.slice(0, 32);
|
|
const ir = i.slice(32, 64);
|
|
if (curve !== Slip10Curve.Ed25519 && (this.isZero(il) || this.isGteN(curve, il))) {
|
|
return this.master(curve, i);
|
|
}
|
|
return {
|
|
chainCode: ir,
|
|
privkey: il,
|
|
};
|
|
}
|
|
static child(curve, parentPrivkey, parentChainCode, rawIndex) {
|
|
let i;
|
|
if (rawIndex.isHardened()) {
|
|
const payload = new Uint8Array([0x00, ...parentPrivkey, ...rawIndex.toBytesBigEndian()]);
|
|
i = new hmac_1.Hmac(sha_1.Sha512, parentChainCode).update(payload).digest();
|
|
}
|
|
else {
|
|
if (curve === Slip10Curve.Ed25519) {
|
|
throw new Error("Normal keys are not allowed with ed25519");
|
|
}
|
|
else {
|
|
// Step 1 of https://github.com/satoshilabs/slips/blob/master/slip-0010.md#private-parent-key--private-child-key
|
|
// Calculate I = HMAC-SHA512(Key = c_par, Data = ser_P(point(k_par)) || ser_32(i)).
|
|
// where the functions point() and ser_p() are defined in BIP-0032
|
|
const data = new Uint8Array([
|
|
...Slip10.serializedPoint(curve, bytesToUnsignedBigInt(parentPrivkey)),
|
|
...rawIndex.toBytesBigEndian(),
|
|
]);
|
|
i = new hmac_1.Hmac(sha_1.Sha512, parentChainCode).update(data).digest();
|
|
}
|
|
}
|
|
return this.childImpl(curve, parentPrivkey, parentChainCode, rawIndex, i);
|
|
}
|
|
/**
|
|
* Implementation of ser_P(point(k_par)) from BIP-0032
|
|
*
|
|
* @see https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
|
*/
|
|
static serializedPoint(curve, p) {
|
|
switch (curve) {
|
|
case Slip10Curve.Secp256k1:
|
|
return secp256k1_1.secp256k1.Point.BASE.multiply(p).toBytes(true);
|
|
default:
|
|
throw new Error("curve not supported");
|
|
}
|
|
}
|
|
static childImpl(curve, parentPrivkey, parentChainCode, rawIndex, i) {
|
|
// step 2 (of the Private parent key → private child key algorithm)
|
|
const il = i.slice(0, 32);
|
|
const ir = i.slice(32, 64);
|
|
// step 3
|
|
const returnChainCode = ir;
|
|
// step 4
|
|
if (curve === Slip10Curve.Ed25519) {
|
|
return {
|
|
chainCode: returnChainCode,
|
|
privkey: il,
|
|
};
|
|
}
|
|
// step 5
|
|
const n = this.n(curve);
|
|
const returnChildKeyAsNumber = (bytesToUnsignedBigInt(il) + bytesToUnsignedBigInt(parentPrivkey)) % n;
|
|
const returnChildKey = intTo32be(returnChildKeyAsNumber);
|
|
// step 6
|
|
if (this.isGteN(curve, il) || this.isZero(returnChildKey)) {
|
|
const newI = new hmac_1.Hmac(sha_1.Sha512, parentChainCode)
|
|
.update(new Uint8Array([0x01, ...ir, ...rawIndex.toBytesBigEndian()]))
|
|
.digest();
|
|
return this.childImpl(curve, parentPrivkey, parentChainCode, rawIndex, newI);
|
|
}
|
|
// step 7
|
|
return {
|
|
chainCode: returnChainCode,
|
|
privkey: returnChildKey,
|
|
};
|
|
}
|
|
static isZero(privkey) {
|
|
return privkey.every((byte) => byte === 0);
|
|
}
|
|
static isGteN(curve, privkey) {
|
|
const keyAsNumber = bytesToUnsignedBigInt(privkey);
|
|
return keyAsNumber >= this.n(curve);
|
|
}
|
|
static n(curve) {
|
|
switch (curve) {
|
|
case Slip10Curve.Secp256k1:
|
|
return 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
|
default:
|
|
throw new Error("curve not supported");
|
|
}
|
|
}
|
|
}
|
|
exports.Slip10 = Slip10;
|
|
function pathToString(path) {
|
|
return path.reduce((current, component) => {
|
|
const componentString = component.isHardened()
|
|
? `${component.toNumber() - 2 ** 31}'`
|
|
: component.toString();
|
|
return current + "/" + componentString;
|
|
}, "m");
|
|
}
|
|
function stringToPath(input) {
|
|
if (!input.startsWith("m"))
|
|
throw new Error("Path string must start with 'm'");
|
|
let rest = input.slice(1);
|
|
const out = new Array();
|
|
while (rest) {
|
|
const match = rest.match(/^\/([0-9]+)('?)/);
|
|
if (!match)
|
|
throw new Error("Syntax error while reading path component");
|
|
const [fullMatch, numberString, apostrophe] = match;
|
|
const value = math_1.Uint53.fromString(numberString).toNumber();
|
|
if (value >= 2 ** 31)
|
|
throw new Error("Component value too high. Must not exceed 2**31-1.");
|
|
if (apostrophe)
|
|
out.push(Slip10RawIndex.hardened(value));
|
|
else
|
|
out.push(Slip10RawIndex.normal(value));
|
|
rest = rest.slice(fullMatch.length);
|
|
}
|
|
return out;
|
|
}
|
|
//# sourceMappingURL=slip10.js.map
|