"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NoiseState = void 0;
const crypto_1 = require("@node-dlc/crypto");
const crypto_2 = require("@node-dlc/crypto");
class NoiseState {
    /**
     * State machine for perforing noise-protocol handshake, message
     * encryption and decryption, and key rotation.
     */
    constructor({ ls, es, logger }) {
        /**
         * The official protocol name for the Lightning variant of Noise. This
         * value is mixed into the iniitialiization function to start the
         * handshake.
         */
        this.protocolName = Buffer.from('Noise_XK_secp256k1_ChaChaPoly_SHA256');
        /**
         * Appended to the hash of the protocolName during initialization.
         */
        this.prologue = Buffer.from('lightning');
        this.logger = logger;
        this.ls = ls;
        this.lpk = (0, crypto_2.getPublicKey)(ls);
        this.es = es;
        this.epk = (0, crypto_2.getPublicKey)(es);
    }
    /**
     * Initiator Act1 is the starting point for the authenticated key exchange
     * handshake. The initiator attempts to satisfy an implicit challenge by the
     * responder: knowledge of the static public key of the responder. It also
     * transmits the initiators ephemeral key.
     * @param rpk remote public key
     * @return Buffer that is 50 bytes
     */
    initiatorAct1(rpk) {
        if (this.logger)
            this.logger.debug('initiator act1');
        this.rpk = rpk;
        this._initialize(this.rpk);
        // 2. h = SHA-256(h || epk)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, this.epk]));
        // 3. es = ECDH(e.priv, rs)
        const ss = (0, crypto_1.ecdh)(this.rpk, this.es);
        // 4. ck, temp_k1 = HKDF(ck, es)
        const tempK1 = (0, crypto_1.hkdf)(ss, 64, this.ck);
        this.ck = tempK1.slice(0, 32);
        this.tempK1 = tempK1.slice(32);
        // 5. c = encryptWithAD(temp_k1, 0, h, zero)
        const c = (0, crypto_1.ccpEncrypt)(this.tempK1, Buffer.alloc(12), this.h, Buffer.alloc(0));
        // 6. h = SHA-256(h || c)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, c]));
        // 7. m = 0 || epk || c
        const m = Buffer.concat([Buffer.alloc(1), this.epk, c]);
        return m;
    }
    /**
     * Initiator Act2 handles the response generated by the receiver's
     * Act1, a 50-byte message. The responder's ephemeral key is extacted
     * from the message during this phase.
     *
     * @param m 50-byte message from responder's act1
     */
    initiatorAct2(m) {
        if (this.logger)
            this.logger.debug('initiator act2');
        // 1. read exactly 50 bytes off the stream
        if (m.length !== 50)
            throw new Error('ACT2_READ_FAILED');
        // 2. parse th read message m into v, re, and c
        const v = m.slice(0, 1)[0];
        const re = m.slice(1, 34);
        const c = m.slice(34);
        // 2a. convert re to public key
        this.repk = re;
        // 3. assert version is known version
        if (v !== 0)
            throw new Error('ACT2_BAD_VERSION');
        // 4. sha256(h || re.serializedCompressed');
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, this.repk]));
        // 5. ss = ECDH(re, e.priv);
        const ss = (0, crypto_1.ecdh)(this.repk, this.es);
        // 6. ck, temp_k2 = HKDF(cd, ss)
        const tempK2 = (0, crypto_1.hkdf)(ss, 64, this.ck);
        this.ck = tempK2.slice(0, 32);
        this.tempK2 = tempK2.slice(32);
        // 7. p = decryptWithAD()
        (0, crypto_1.ccpDecrypt)(this.tempK2, Buffer.alloc(12), this.h, c);
        // 8. h = sha256(h || c)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, c]));
    }
    /**
     * Initiator Act3 is the final phase in the authenticated
     * key agreement. This act is executed only if act 2
     * was successful. The initiator transports its static public key
     * to the responder.
     */
    initiatorAct3() {
        if (this.logger)
            this.logger.debug('initiator act3');
        // 1. c = encryptWithAD(temp_k2, 1, h, lpk)
        const c = (0, crypto_1.ccpEncrypt)(this.tempK2, Buffer.from([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]), this.h, this.lpk);
        // 2. h = sha256(h || c)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, c]));
        // 3. ss = ECDH(re, s.priv)
        const ss = (0, crypto_1.ecdh)(this.repk, this.ls);
        // 4. ck, temp_k3 = HKDF(ck, ss)
        const tempK3 = (0, crypto_1.hkdf)(ss, 64, this.ck);
        this.ck = tempK3.slice(0, 32);
        this.tempK3 = tempK3.slice(32);
        // 5. t = encryptWithAD(temp_k3, 0, h, zero)
        const t = (0, crypto_1.ccpEncrypt)(this.tempK3, Buffer.alloc(12), this.h, Buffer.alloc(0));
        // 6. sk, rk = hkdf(ck, zero)
        const sk = (0, crypto_1.hkdf)(Buffer.alloc(0), 64, this.ck);
        this.rk = sk.slice(32);
        this.sk = sk.slice(0, 32);
        // 7. rn = 0, sn = 0
        this.sn = Buffer.alloc(12);
        this.rn = Buffer.alloc(12);
        // 8. send m = 0 || c || t
        const m = Buffer.concat([Buffer.alloc(1), c, t]);
        return m;
    }
    /**
     * Receiver Act1 extracts the initiators ephemeral key. It also
     * validates that the initiator knows the receivers public key.
     * @param m 50-byte message sent by the initiator
     */
    receiveAct1(m) {
        this._initialize(this.lpk);
        if (this.logger)
            this.logger.debug('receive act1');
        // 1. read exactly 50 bytes off the stream
        if (m.length !== 50)
            throw new Error('ACT1_READ_FAILED');
        // 2. parse th read message m into v,re, and c
        const v = m.slice(0, 1)[0];
        const re = m.slice(1, 34);
        const c = m.slice(34);
        this.repk = re;
        // 3. assert version is known version
        if (v !== 0)
            throw new Error('ACT1_BAD_VERSION');
        // 4. sha256(h || re.serializedCompressed');
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, re]));
        // 5. ss = ECDH(re, ls.priv);
        const ss = (0, crypto_1.ecdh)(re, this.ls);
        // 6. ck, temp_k1 = HKDF(cd, ss)
        const tempK1 = (0, crypto_1.hkdf)(ss, 64, this.ck);
        this.ck = tempK1.slice(0, 32);
        this.tempK1 = tempK1.slice(32);
        // 7. p = decryptWithAD(temp_k1, 0, h, c)
        (0, crypto_1.ccpDecrypt)(this.tempK1, Buffer.alloc(12), this.h, c);
        // 8. h = sha256(h || c)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, c]));
    }
    /**
     * Receiver Act2 takes place only if Act1 was successful.
     * This act sends responder's ephermeral key to the initiator.
     */
    recieveAct2() {
        // 1. e = generateKey() => done in initialization
        // 2. h = sha256(h || e.pub.compressed())
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, this.epk]));
        // 3. ss = ecdh(re, e.priv)
        const ss = (0, crypto_1.ecdh)(this.repk, this.es);
        // 4. ck, temp_k2 = hkdf(ck, ss)
        const tempK2 = (0, crypto_1.hkdf)(ss, 64, this.ck);
        this.ck = tempK2.slice(0, 32);
        this.tempK2 = tempK2.slice(32);
        // 5. c = encryptWithAd(temp_k2, 0, h, zero)
        const c = (0, crypto_1.ccpEncrypt)(this.tempK2, Buffer.alloc(12), this.h, Buffer.alloc(0));
        // 6. h = sha256(h || c)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, c]));
        // 7. m = 0 || e.pub.compressed() Z|| c
        const m = Buffer.concat([Buffer.alloc(1), this.epk, c]);
        return m;
    }
    /**
     * Receiver Act3 is the final phase in the authenticated key
     * agreement. This act is executed only if act 2 was successful.
     * The receiver extracts the public key of the initiator.
     * @param m 66-byte message
     */
    receiveAct3(m) {
        // 1. read exactly 66 bytes from the network buffer
        if (m.length !== 66)
            throw new Error('ACT3_READ_FAILED');
        // 2. parse m into v, c, t
        const v = m.slice(0, 1)[0];
        const c = m.slice(1, 50);
        const t = m.slice(50);
        // 3. validate v is recognized
        if (v !== 0)
            throw new Error('ACT3_BAD_VERSION');
        // 4. rs = decryptWithAD(temp_k2, 1, h, c)
        const rs = (0, crypto_1.ccpDecrypt)(this.tempK2, Buffer.from('000000000100000000000000', 'hex'), this.h, c);
        this.rpk = rs;
        // 5. h = sha256(h || c)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, c]));
        // 6. ss = ECDH(rs, e.priv)
        const ss = (0, crypto_1.ecdh)(this.rpk, this.es);
        // 7. ck, temp_k3 = hkdf(cs, ss)
        const tempK3 = (0, crypto_1.hkdf)(ss, 64, this.ck);
        this.ck = tempK3.slice(0, 32);
        this.tempK3 = tempK3.slice(32);
        // 8. p = decryptWithAD(temp_k3, 0, h, t)
        (0, crypto_1.ccpDecrypt)(this.tempK3, Buffer.alloc(12), this.h, t);
        // 9. rk, sk = hkdf(ck, zero)
        const sk = (0, crypto_1.hkdf)(Buffer.alloc(0), 64, this.ck);
        this.rk = sk.slice(0, 32);
        this.sk = sk.slice(32);
        // 10. rn = 0, sn = 0
        this.rn = Buffer.alloc(12);
        this.sn = Buffer.alloc(12);
    }
    /**
     * Sends an encrypted message using the shared sending key and nonce.
     * The nonce is rotated once the message is sent. The sending key is
     * rotated every 1000 messages.
     * @param m
     */
    encryptMessage(m) {
        // step 1/2. serialize m length into int16
        const l = Buffer.alloc(2);
        l.writeUInt16BE(m.length, 0);
        // step 3. encrypt l, using chachapoly1305, sn, sk)
        const lc = (0, crypto_1.ccpEncrypt)(this.sk, this.sn, Buffer.alloc(0), l);
        // step 3a: increment sn
        if (this._incrementSendingNonce() >= 1000)
            this._rotateSendingKeys();
        // step 4 encrypt m using chachapoly1305, sn, sk
        const c = (0, crypto_1.ccpEncrypt)(this.sk, this.sn, Buffer.alloc(0), m);
        // step 4a: increment sn
        if (this._incrementSendingNonce() >= 1000)
            this._rotateSendingKeys();
        // step 5 return m to be sent
        return Buffer.concat([lc, c]);
    }
    /**
     * Decrypts the length of the message using the receiving key and nonce.
     * The receiving key is rotated every 1000 messages.
     */
    decryptLength(lc) {
        const l = (0, crypto_1.ccpDecrypt)(this.rk, this.rn, Buffer.alloc(0), lc);
        if (this._incrementRecievingNonce() >= 1000)
            this._rotateRecievingKeys();
        return l.readUInt16BE(0);
    }
    /**
     * Decrypts the message using the receiving key and nonce. The receiving
     * key is rotated every 1000 messages.
     */
    decryptMessage(c) {
        const m = (0, crypto_1.ccpDecrypt)(this.rk, this.rn, Buffer.alloc(0), c);
        if (this._incrementRecievingNonce() >= 1000)
            this._rotateRecievingKeys();
        return m;
    }
    /////////////////////////////////////
    /**
     * Initializes the noise state prior to Act1.
     */
    _initialize(pubkey) {
        if (this.logger)
            this.logger.debug('initialize noise state');
        // 1. h = SHA-256(protocolName)
        this.h = (0, crypto_1.sha256)(Buffer.from(this.protocolName));
        // 2. ck = h
        this.ck = this.h;
        // 3. h = SHA-256(h || prologue)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, this.prologue]));
        // 4. h = SHA-256(h || pubkey)
        this.h = (0, crypto_1.sha256)(Buffer.concat([this.h, pubkey]));
    }
    _incrementSendingNonce() {
        const newValue = this.sn.readUInt16LE(4) + 1;
        this.sn.writeUInt16LE(newValue, 4);
        return newValue;
    }
    _incrementRecievingNonce() {
        const newValue = this.rn.readUInt16LE(4) + 1;
        this.rn.writeUInt16LE(newValue, 4);
        return newValue;
    }
    _rotateSendingKeys() {
        if (this.logger)
            this.logger.debug('rotating sending key');
        const result = (0, crypto_1.hkdf)(this.sk, 64, this.ck);
        this.sk = result.slice(32);
        this.ck = result.slice(0, 32);
        this.sn = Buffer.alloc(12);
    }
    _rotateRecievingKeys() {
        if (this.logger)
            this.logger.debug('rotating receiving key');
        const result = (0, crypto_1.hkdf)(this.rk, 64, this.ck);
        this.rk = result.slice(32);
        this.ck = result.slice(0, 32);
        this.rn = Buffer.alloc(12);
    }
}
exports.NoiseState = NoiseState;
//# sourceMappingURL=noise-state.js.map