libnexa-ts
Version:
A pure and powerful Nexa SDK library.
140 lines (118 loc) • 4.89 kB
text/typescript
import type { Networkish } from "../common/types";
import { networks } from "../core/network/network-manager";
import BufferUtils from "../utils/buffer.utils";
import { ArgumentIsPrivateExtended, InvalidB58Char, InvalidB58Checksum, InvalidLength, InvalidNetwork, InvalidNetworkArgument } from "./exceptions";
import Base58 from "../encoding/base58";
import Base58Check from "../encoding/base58check";
export default class HDKeyUtils {
public static readonly Hardened = 0x80000000;
public static readonly MaxIndex = 2 * HDKeyUtils.Hardened;
public static readonly RootElementAlias = ['m', 'M', 'm\'', 'M\''];
public static readonly VersionSize = 4;
public static readonly DepthSize = 1;
public static readonly ParentFingerPrintSize = 4;
public static readonly ChildIndexSize = 4;
public static readonly ChainCodeSize = 32;
public static readonly CheckSumSize = 4;
public static readonly DataSize = 78;
public static readonly SerializedByteSize = 82;
public static readonly VersionStart = 0;
public static readonly VersionEnd = HDKeyUtils.VersionStart + HDKeyUtils.VersionSize;
public static readonly DepthStart = HDKeyUtils.VersionEnd;
public static readonly DepthEnd = HDKeyUtils.DepthStart + HDKeyUtils.DepthSize;
public static readonly ParentFingerPrintStart = HDKeyUtils.DepthEnd;
public static readonly ParentFingerPrintEnd = HDKeyUtils.ParentFingerPrintStart + HDKeyUtils.ParentFingerPrintSize;
public static readonly ChildIndexStart = HDKeyUtils.ParentFingerPrintEnd;
public static readonly ChildIndexEnd = HDKeyUtils.ChildIndexStart + HDKeyUtils.ChildIndexSize;
public static readonly ChainCodeStart = HDKeyUtils.ChildIndexEnd;
public static readonly ChainCodeEnd = HDKeyUtils.ChainCodeStart + HDKeyUtils.ChainCodeSize;
/**
* Util function that splits a string path into a derivation index array.
* It will return null if the string path is malformed.
* It does not validate if indexes are in bounds.
*
* @param path
*/
public static getDerivationIndexes(path: string): number[] | null {
// Special cases:
if (this.RootElementAlias.includes(path)) {
return [];
}
let steps = path.split('/');
if (!this.RootElementAlias.includes(steps[0])) {
return null;
}
let indexes = steps.slice(1).map(step => {
let isHardened = step.slice(-1) === "'";
if (isHardened) {
step = step.slice(0, -1);
}
if (!step || step[0] === '-') {
return NaN;
}
let index = +step; // cast to number
if (isHardened) {
index += this.Hardened;
}
return index;
});
return indexes.some(isNaN) ? null : indexes;
}
private static _validateNetwork(data: Uint8Array, networkArg: Networkish, isPrivate = false): Error | null {
let network = networks.get(networkArg);
if (!network) {
return new InvalidNetworkArgument(networkArg);
}
let version = data.subarray(this.VersionStart, this.VersionEnd);
if (BufferUtils.integerFromBuffer(version) !== (isPrivate ? network.xprivkey : network.xpubkey)) {
return new InvalidNetwork(version);
}
return null;
}
/**
* Verifies that a given serialized hd key in base58 with checksum format
* is valid.
*
* @param data - the serialized hd key
* @param network - optional, if present, checks that the network provided matches the network serialized.
*/
public static isValidSerialized(data: Uint8Array | string, network?: Networkish, isPrivate = false): boolean {
return this.getSerializedError(data, network, isPrivate) == null;
}
/**
* Checks what's the error that causes the validation of a serialized public key
* in base58 with checksum to fail.
*
* @param data - the serialized hd key
* @param network - optional, if present, checks that the network provided matches the network serialized.
*/
public static getSerializedError(data: string | Uint8Array, network?: Networkish, isPrivate = false): Error | null {
if (!(typeof data === 'string' || BufferUtils.isBuffer(data))) {
return new TypeError('Expected string or buffer')
}
if (!Base58.validCharacters(data)) {
return new InvalidB58Char('(unknown)', data);
}
try {
data = Base58Check.decode(data as string);
} catch {
return new InvalidB58Checksum(data);
}
if (data.length !== HDKeyUtils.DataSize) {
return new InvalidLength(data);
}
if (network != null) {
let error = this._validateNetwork(data, network, isPrivate);
if (error) {
return error;
}
}
if (!isPrivate) {
let version = BufferUtils.integerFromBuffer(data.subarray(0, 4));
if (version === networks.mainnet.xprivkey || version === networks.testnet.xprivkey ) {
return new ArgumentIsPrivateExtended();
}
}
return null;
}
}