Home Reference Source Test

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

class KeyPair extends Serializable {
    /**
     * @param {PrivateKey} privateKey
     * @param {PublicKey} publicKey
     * @param {boolean} locked
     * @param {Uint8Array} lockSalt
     * @private
     */
    constructor(privateKey, publicKey, locked = false, lockSalt = null) {
        if (!(privateKey instanceof Object)) throw new Error('Primitive: Invalid type');
        if (!(publicKey instanceof Object)) throw new Error('Primitive: Invalid type');
        super();

        /** @type {boolean} */
        this._locked = locked;
        /** @type {boolean} */
        this._lockedInternally = locked;
        /** @type {Uint8Array} */
        this._lockSalt = lockSalt;
        /** @type {PublicKey} */
        this._publicKey = publicKey;
        /** @type {PrivateKey} */
        this._internalPrivateKey = new PrivateKey(privateKey.serialize());
    }

    /**
     * @return {KeyPair}
     */
    static generate() {
        const privateKey = PrivateKey.generate();
        return new KeyPair(privateKey, PublicKey.derive(privateKey));
    }

    /**
     * @param {PrivateKey} privateKey
     * @return {KeyPair}
     */
    static derive(privateKey) {
        return new KeyPair(privateKey, PublicKey.derive(privateKey));
    }

    /**
     * @param {string} hexBuf
     * @return {KeyPair}
     */
    static fromHex(hexBuf) {
        return KeyPair.unserialize(BufferUtils.fromHex(hexBuf));
    }

    /**
     * @param {SerialBuffer} buf
     * @return {KeyPair}
     */
    static unserialize(buf) {
        const privateKey = PrivateKey.unserialize(buf);
        const publicKey = PublicKey.unserialize(buf);
        let locked = false;
        let lockSalt = null;
        if (buf.readPos < buf.byteLength) {
            const extra = buf.readUint8();
            if (extra === 1) {
                locked = true;
                lockSalt = buf.read(32);
            }
        }
        return new KeyPair(privateKey, publicKey, locked, lockSalt);
    }

    /**
     * @param {SerialBuffer} [buf]
     * @return {SerialBuffer}
     */
    serialize(buf) {
        buf = buf || new SerialBuffer(this.serializedSize);
        this._privateKey.serialize(buf);
        this.publicKey.serialize(buf);
        if (this._locked) {
            buf.writeUint8(1);
            buf.write(this._lockSalt);
        } else {
            buf.writeUint8(0);
        }
        return buf;
    }

    /**
     * The unlocked private key.
     * @type {PrivateKey}
     */
    get privateKey() {
        if (this.isLocked) throw new Error('KeyPair is locked');
        return this._privateKey;
    }

    /**
     * The private key in its current state, i.e., depending on this._locked.
     * If this._locked, it is the internally locked private key.
     * If !this._locked, it is either the internally unlocked private key (if !this._lockedInternally)
     * or this._unlockedPrivateKey.
     * @type {PrivateKey}
     */
    get _privateKey() {
        return this._unlockedPrivateKey || this._internalPrivateKey;
    }

    /** @type {PublicKey} */
    get publicKey() {
        return this._publicKey || (this._publicKey = new PublicKey(this._obj.publicKey));
    }

    /** @type {number} */
    get serializedSize() {
        return this._privateKey.serializedSize + this.publicKey.serializedSize + (this._locked ? this._lockSalt.byteLength + 1 : 1);
    }

    /**
     * @param {Uint8Array} key
     * @param {Uint8Array} [lockSalt]
     */
    async lock(key, lockSalt) {
        if (this._locked) throw new Error('KeyPair already locked');

        if (lockSalt) this._lockSalt = lockSalt;
        if (!this._lockSalt || this._lockSalt.length === 0) {
            this._lockSalt = new Uint8Array(32);
            CryptoWorker.lib.getRandomValues(this._lockSalt);
        }

        this._internalPrivateKey.overwrite(await this._otpPrivateKey(key));
        this._clearUnlockedPrivateKey();
        this._locked = true;
        this._lockedInternally = true;
    }

    /**
     * @param {Uint8Array} key
     */
    async unlock(key) {
        if (!this._locked) throw new Error('KeyPair not locked');

        const privateKey = await this._otpPrivateKey(key);
        const verifyPub = PublicKey.derive(privateKey);
        if (verifyPub.equals(this.publicKey)) {
            // Only set this._internalPrivateKey, but keep this._obj locked.
            this._unlockedPrivateKey = privateKey;
            this._locked = false;
        } else {
            throw new Error('Invalid key');
        }
    }

    /**
     * Destroy cached unlocked private key if the internal key is in locked state.
     */
    relock() {
        if (this._locked) throw new Error('KeyPair already locked');
        if (!this._lockedInternally) throw new Error('KeyPair was never locked');
        this._clearUnlockedPrivateKey();
        this._locked = true;
    }

    _clearUnlockedPrivateKey() {
        // If this wallet is not locked internally and unlocked, this method does not have any effect.
        if (!this._lockedInternally || this._locked) return;

        // Overwrite cached key in this._unlockedPrivateKey with 0s.
        this._unlockedPrivateKey.overwrite(PrivateKey.unserialize(new SerialBuffer(this._unlockedPrivateKey.serializedSize)));
        // Then, reset it.
        this._unlockedPrivateKey = null;
    }

    /**
     * @param {Uint8Array} key
     * @return {Promise<PrivateKey>}
     * @private
     */
    async _otpPrivateKey(key) {
        return new PrivateKey(await CryptoUtils.otpKdfLegacy(this._privateKey.serialize(), key, this._lockSalt, KeyPair.LOCK_KDF_ROUNDS));
    }

    get isLocked() {
        return this._locked;
    }

    /**
     * @param {SerialBuffer} buf
     * @param {Uint8Array} key
     * @return {Promise.<KeyPair>}
     */
    static async fromEncrypted(buf, key) {
        const privateKey = await Secret.fromEncrypted(buf, key);
        if (privateKey.type !== Secret.Type.PRIVATE_KEY) throw new Error('Expected privateKey, got Entropy');
        return KeyPair.derive(privateKey);
    }

    /**
     * @param {Uint8Array} key
     * @return {Promise.<SerialBuffer>}
     */
    exportEncrypted(key) {
        return this._privateKey.exportEncrypted(key);
    }

    /** @type {number} */
    get encryptedSize() {
        return this._privateKey.encryptedSize;
    }

    /**
     * @param {Serializable} o
     * @return {boolean}
     */
    equals(o) {
        return o instanceof KeyPair && super.equals(o);
    }
}
KeyPair.LOCK_KDF_ROUNDS = 256;


Class.register(KeyPair);