Home Reference Source Test

src/main/generic/consensus/base/primitive/ExtendedPrivateKey.js

class ExtendedPrivateKey extends Serializable {
    /**
     * @param {PrivateKey} key
     * @param {Uint8Array} chainCode
     * @private
     */
    constructor(key, chainCode) {
        super();
        if (!(key instanceof PrivateKey)) throw new Error('ExtendedPrivateKey: Invalid key');
        if (!(chainCode instanceof Uint8Array)) throw new Error('ExtendedPrivateKey: Invalid chainCode');
        if (chainCode.length !== ExtendedPrivateKey.CHAIN_CODE_SIZE) throw new Error('ExtendedPrivateKey: Invalid chainCode length');
        this._key = key;
        this._chainCode = chainCode;
    }

    /**
     * @param {Uint8Array} seed
     * @return {ExtendedPrivateKey}
     */
    static generateMasterKey(seed) {
        const bCurve = BufferUtils.fromAscii('ed25519 seed');
        const hash = CryptoUtils.computeHmacSha512(bCurve, seed);
        return new ExtendedPrivateKey(new PrivateKey(hash.slice(0, 32)), hash.slice(32));
    }

    /**
     * @param {number} index
     * @return {ExtendedPrivateKey}
     */
    derive(index) {
        // Only hardened derivation is allowed for ed25519.
        if (index < 0x80000000) index += 0x80000000;

        const data = new SerialBuffer(1 + PrivateKey.SIZE + 4);
        data.writeUint8(0);
        this._key.serialize(data);
        data.writeUint32(index);

        const hash = CryptoUtils.computeHmacSha512(this._chainCode, data);
        return new ExtendedPrivateKey(new PrivateKey(hash.slice(0, 32)), hash.slice(32));
    }

    /**
     * @param {string} path
     * @return {boolean}
     */
    static isValidPath(path) {
        if (path.match(/^m(\/[0-9]+')*$/) === null) return false;

        // Overflow check.
        const segments = path.split('/');
        for (let i = 1; i < segments.length; i++) {
            if (!NumberUtils.isUint32(parseInt(segments[i]))) return false;
        }

        return true;
    }

    /**
     * @param {string} path
     * @return {ExtendedPrivateKey}
     */
    derivePath(path) {
        if (!ExtendedPrivateKey.isValidPath(path)) throw new Error('Invalid path');

        let extendedKey = this;
        const segments = path.split('/');
        for (let i = 1; i < segments.length; i++) {
            const index = parseInt(segments[i]);
            extendedKey = extendedKey.derive(index);
        }
        return extendedKey;
    }

    /**
     * @param {string} path
     * @param {Uint8Array} seed
     * @return {ExtendedPrivateKey}
     */
    static derivePathFromSeed(path, seed) {
        let extendedKey = ExtendedPrivateKey.generateMasterKey(seed);
        return extendedKey.derivePath(path);
    }

    /**
     * @param {SerialBuffer} buf
     * @return {ExtendedPrivateKey}
     */
    static unserialize(buf) {
        const privateKey = PrivateKey.unserialize(buf);
        const chainCode = buf.read(ExtendedPrivateKey.CHAIN_CODE_SIZE);
        return new ExtendedPrivateKey(privateKey, chainCode);
    }

    /**
     * @param {SerialBuffer} [buf]
     * @return {SerialBuffer}
     */
    serialize(buf) {
        buf = buf || new SerialBuffer(this.serializedSize);
        this._key.serialize(buf);
        buf.write(this._chainCode);
        return buf;
    }

    /** @type {number} */
    get serializedSize() {
        return this._key.serializedSize + ExtendedPrivateKey.CHAIN_CODE_SIZE;
    }

    /**
     * @param {Serializable} o
     * @return {boolean}
     */
    equals(o) {
        return o instanceof ExtendedPrivateKey && super.equals(o);
    }

    /**
     * @type {PrivateKey}
     */
    get privateKey() {
        return this._key;
    }

    /**
     * @return {Address}
     */
    toAddress() {
        return PublicKey.derive(this._key).toAddress();
    }
}

ExtendedPrivateKey.CHAIN_CODE_SIZE = 32;

Class.register(ExtendedPrivateKey);