"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tests = exports.vrf = exports.HDKD = void 0;
exports.getPublicKey = getPublicKey;
exports.secretFromSeed = secretFromSeed;
exports.fromKeypair = fromKeypair;
exports.sign = sign;
exports.verify = verify;
exports.getSharedSecret = getSharedSecret;
/**
 * Minimal JS implementation of sr25519 cryptography for Pezkuwi.
 *
 * Uses [Merlin](https://merlin.cool/index.html),
 * a transcript construction, built on [Strobe](https://strobe.sourceforge.io).
 * Merlin ensures two parties agree on the same state when communicating.
 *
 * More: https://wiki.pezkuwi.network/docs/learn-cryptography.
 */
const modular_js_1 = require("@noble/curves/abstract/modular.js");
const utils_js_1 = require("@noble/curves/abstract/utils.js");
const ed25519_js_1 = require("@noble/curves/ed25519.js");
const sha3_js_1 = require("@noble/hashes/sha3.js");
const sha512_js_1 = require("@noble/hashes/sha512.js");
const utils_js_2 = require("@noble/hashes/utils.js");
// prettier-ignore
const _0n = BigInt(0), _3n = BigInt(3);
function toData(d) {
    if (typeof d === 'string')
        return (0, utils_js_2.utf8ToBytes)(d);
    if ((0, utils_js_1.isBytes)(d))
        return d;
    throw new Error('Wrong data');
}
// Could've used bytes from hashes/assert, but we add extra arg
function abytes(title, b, ...lengths) {
    if (!(0, utils_js_1.isBytes)(b))
        throw new Error(`${title}: Uint8Array expected`);
    if (lengths.length && !lengths.includes(b.length))
        throw new Error(`${title}: Uint8Array expected of length ${lengths}, not of length=${b.length}`);
}
function checkU32(title, n) {
    if (!Number.isSafeInteger(n) || n < 0 || n > 4294967295)
        throw new Error(`${title}: wrong u32 integer: ${n}`);
    return n;
}
function cleanBytes(...list) {
    for (const t of list)
        t.fill(0);
}
const EMPTY = Uint8Array.of();
const CURVE_ORDER = ed25519_js_1.ed25519.CURVE.n;
function parseScalar(title, bytes) {
    abytes(title, bytes, 32);
    const n = (0, utils_js_1.bytesToNumberLE)(bytes);
    (0, utils_js_1.aInRange)(title, n, _0n, CURVE_ORDER);
    return n;
}
const modN = (n) => (0, modular_js_1.mod)(n, CURVE_ORDER);
// STROBE128 (minimal version required for Merlin)
// - https://strobe.sourceforge.io/specs/
// We can implement full version, but seems nobody uses this much.
const STROBE_R = 166;
const Flags = {
    I: 1,
    A: 1 << 1,
    C: 1 << 2,
    T: 1 << 3,
    M: 1 << 4,
    K: 1 << 5,
};
// Differences: suffix, additional methods/flags
class Strobe128 {
    constructor(protocolLabel) {
        this.state = new Uint8Array(200);
        this.pos = 0;
        this.posBegin = 0;
        this.curFlags = 0;
        this.state.set([1, STROBE_R + 2, 1, 0, 1, 96], 0);
        this.state.set((0, utils_js_2.utf8ToBytes)('STROBEv1.0.2'), 6);
        this.state32 = (0, utils_js_2.u32)(this.state);
        this.keccakF1600();
        this.metaAD(protocolLabel, false);
    }
    keccakF1600() {
        (0, sha3_js_1.keccakP)(this.state32);
    }
    runF() {
        this.state[this.pos] ^= this.posBegin;
        this.state[this.pos + 1] ^= 0x04;
        this.state[STROBE_R + 1] ^= 0x80;
        this.keccakF1600();
        this.pos = 0;
        this.posBegin = 0;
    }
    // keccak.update()
    absorb(data) {
        for (let i = 0; i < data.length; i++) {
            this.state[this.pos++] ^= data[i];
            if (this.pos === STROBE_R)
                this.runF();
        }
    }
    // keccak.xof()
    squeeze(len) {
        const data = new Uint8Array(len);
        for (let i = 0; i < data.length; i++) {
            data[i] = this.state[this.pos];
            this.state[this.pos++] = 0;
            if (this.pos === STROBE_R)
                this.runF();
        }
        return data;
    }
    overwrite(data) {
        for (let i = 0; i < data.length; i++) {
            this.state[this.pos++] = data[i];
            if (this.pos === STROBE_R)
                this.runF();
        }
    }
    beginOp(flags, more) {
        if (more) {
            if (this.curFlags !== flags) {
                throw new Error(`Continued op with changed flags from ${this.curFlags.toString(2)} to ${flags.toString(2)}`);
            }
            return;
        }
        if ((flags & Flags.T) !== 0)
            throw new Error('T flag is not supported');
        const oldBegin = this.posBegin;
        this.posBegin = this.pos + 1;
        this.curFlags = flags;
        this.absorb(new Uint8Array([oldBegin, flags]));
        const forceF = (flags & (Flags.C | Flags.K)) !== 0;
        if (forceF && this.pos !== 0)
            this.runF();
    }
    // Public API
    metaAD(data, more) {
        this.beginOp(Flags.M | Flags.A, more);
        this.absorb(toData(data));
    }
    AD(data, more) {
        this.beginOp(Flags.A, more);
        this.absorb(toData(data));
    }
    PRF(len, more) {
        this.beginOp(Flags.I | Flags.A | Flags.C, more);
        return this.squeeze(len);
    }
    KEY(data, more) {
        this.beginOp(Flags.A | Flags.C, more);
        this.overwrite(toData(data));
    }
    // Utils
    clone() {
        const n = new Strobe128('0'); // tmp
        n.pos = this.pos;
        n.posBegin = this.posBegin;
        n.state.set(this.state);
        n.curFlags = this.curFlags;
        return n;
    }
    clean() {
        this.state.fill(0); // also clears state32, because same buffer
        this.pos = 0;
        this.curFlags = 0;
        this.posBegin = 0;
    }
}
// /STROBE128
// Merlin
// https://merlin.cool/index.html
class Merlin {
    constructor(label) {
        this.strobe = new Strobe128('Merlin v1.0');
        this.appendMessage('dom-sep', label);
    }
    appendMessage(label, message) {
        this.strobe.metaAD(label, false);
        checkU32('Merlin.appendMessage', message.length);
        this.strobe.metaAD((0, utils_js_1.numberToBytesLE)(message.length, 4), true);
        this.strobe.AD(message, false);
    }
    challengeBytes(label, len) {
        this.strobe.metaAD(label, false);
        checkU32('Merlin.challengeBytes', len);
        this.strobe.metaAD((0, utils_js_1.numberToBytesLE)(len, 4), true);
        return this.strobe.PRF(len, false);
    }
    clean() {
        this.strobe.clean();
    }
}
// /Merlin
// Merlin signging context/transcript (sr25519 specific stuff, Merlin and Strobe are generic (but minimal))
class SigningContext extends Merlin {
    constructor(name, rng = utils_js_2.randomBytes) {
        super(name);
        this.rng = rng;
    }
    label(label) {
        this.appendMessage('', label);
    }
    bytes(bytes) {
        this.appendMessage('sign-bytes', bytes);
        return this;
    }
    protoName(label) {
        this.appendMessage('proto-name', label);
    }
    commitPoint(label, point) {
        this.appendMessage(label, point.toRawBytes());
    }
    challengeScalar(label) {
        return modN((0, utils_js_1.bytesToNumberLE)(this.challengeBytes(label, 64)));
    }
    witnessScalar(label, nonceSeeds = []) {
        return modN((0, utils_js_1.bytesToNumberLE)(this.witnessBytes(label, 64, nonceSeeds)));
    }
    witnessBytes(label, len, nonceSeeds = []) {
        checkU32('SigningContext.witnessBytes', len);
        const strobeRng = this.strobe.clone();
        for (const ns of nonceSeeds) {
            strobeRng.metaAD(label, false);
            checkU32('SigningContext.witnessBytes nonce length', ns.length);
            strobeRng.metaAD((0, utils_js_1.numberToBytesLE)(ns.length, 4), true);
            strobeRng.KEY(ns, false);
        }
        const random = this.rng(32);
        strobeRng.metaAD('rng', false);
        strobeRng.KEY(random, false);
        strobeRng.metaAD((0, utils_js_1.numberToBytesLE)(len, 4), false);
        return strobeRng.PRF(len, false);
    }
}
// /Merlin signing context
const MASK = (0, utils_js_1.bitMask)(256);
// == (n * CURVE.h) % CURVE_BIT_MASK
const encodeScalar = (n) => (0, utils_js_1.numberToBytesLE)((n << _3n) & MASK, 32);
// n / CURVE.h
const decodeScalar = (n) => (0, utils_js_1.bytesToNumberLE)(n) >> _3n;
// NOTE: secretKey is 64 bytes (key + nonce). This required for HDKD, since key can be derived not only from seed, but from other keys.
function getPublicKey(secretKey) {
    abytes('secretKey', secretKey, 64);
    const scalar = decodeScalar(secretKey.subarray(0, 32));
    return ed25519_js_1.RistrettoPoint.BASE.multiply(scalar).toRawBytes();
}
function secretFromSeed(seed) {
    abytes('seed', seed, 32);
    const r = (0, sha512_js_1.sha512)(seed);
    // NOTE: different from ed25519
    r[0] &= 248;
    r[31] &= 63;
    r[31] |= 64;
    // this will strip upper 3 bits and lower 3 bits
    const key = encodeScalar(decodeScalar(r.subarray(0, 32)));
    const nonce = r.subarray(32, 64);
    const res = (0, utils_js_2.concatBytes)(key, nonce);
    cleanBytes(key, nonce, r);
    return res;
}
// Seems like ed25519 keypair? Generates keypair from other keypair in ed25519 format
// NOTE: not exported from wasm. Do we need this at all?
function fromKeypair(pair) {
    abytes('keypair', pair, 96);
    const sk = pair.subarray(0, 32);
    const nonce = pair.subarray(32, 64);
    const pubBytes = pair.subarray(64, 96);
    const key = encodeScalar((0, utils_js_1.bytesToNumberLE)(sk));
    const realPub = getPublicKey(pair.subarray(0, 64));
    if (!(0, utils_js_1.equalBytes)(pubBytes, realPub))
        throw new Error('wrong public key');
    // No need to clean since subarray's
    return (0, utils_js_2.concatBytes)(key, nonce, realPub);
}
// Basic sign. NOTE: context is currently constant. Please open issue if you need different one.
const BIZINIKIWI_CONTEXT = (0, utils_js_2.utf8ToBytes)('bizinikiwi');
function sign(secretKey, message, rng = utils_js_2.randomBytes) {
    abytes('message', message);
    abytes('secretKey', secretKey, 64);
    const t = new SigningContext('SigningContext', rng);
    t.label(BIZINIKIWI_CONTEXT);
    t.bytes(message);
    const keyScalar = decodeScalar(secretKey.subarray(0, 32));
    const nonce = secretKey.subarray(32, 64);
    const pubPoint = ed25519_js_1.RistrettoPoint.fromHex(getPublicKey(secretKey));
    t.protoName('Schnorr-sig');
    t.commitPoint('sign:pk', pubPoint);
    const r = t.witnessScalar('signing', [nonce]);
    const R = ed25519_js_1.RistrettoPoint.BASE.multiply(r);
    t.commitPoint('sign:R', R);
    const k = t.challengeScalar('sign:c');
    const s = modN(k * keyScalar + r);
    const res = (0, utils_js_2.concatBytes)(R.toRawBytes(), (0, utils_js_1.numberToBytesLE)(s, 32));
    res[63] |= 128; // add Schnorrkel marker
    t.clean();
    return res;
}
function verify(message, signature, publicKey) {
    abytes('message', message);
    abytes('signature', signature, 64);
    abytes('publicKey', publicKey, 32);
    if ((signature[63] & 128) === 0)
        throw new Error('Schnorrkel marker missing');
    const sBytes = Uint8Array.from(signature.subarray(32, 64)); // copy before modification
    sBytes[31] &= 127; // remove Schnorrkel marker
    const R = ed25519_js_1.RistrettoPoint.fromHex(signature.subarray(0, 32));
    const s = (0, utils_js_1.bytesToNumberLE)(sBytes);
    (0, utils_js_1.aInRange)('s', s, _0n, CURVE_ORDER); // Just in case, it will be checked at multiplication later
    const t = new SigningContext('SigningContext');
    t.label(BIZINIKIWI_CONTEXT);
    t.bytes(message);
    const pubPoint = ed25519_js_1.RistrettoPoint.fromHex(publicKey);
    if (pubPoint.equals(ed25519_js_1.RistrettoPoint.ZERO))
        return false;
    t.protoName('Schnorr-sig');
    t.commitPoint('sign:pk', pubPoint);
    t.commitPoint('sign:R', R);
    const k = t.challengeScalar('sign:c');
    const sP = ed25519_js_1.RistrettoPoint.BASE.multiply(s);
    const RR = pubPoint.negate().multiply(k).add(sP);
    t.clean();
    cleanBytes(sBytes);
    return RR.equals(R);
}
function getSharedSecret(secretKey, publicKey) {
    abytes('secretKey', secretKey, 64);
    abytes('publicKey', publicKey, 32);
    const keyScalar = decodeScalar(secretKey.subarray(0, 32));
    const pubPoint = ed25519_js_1.RistrettoPoint.fromHex(publicKey);
    if (pubPoint.equals(ed25519_js_1.RistrettoPoint.ZERO))
        throw new Error('wrong public key (infinity)');
    return pubPoint.multiply(keyScalar).toRawBytes();
}
// Derive
exports.HDKD = {
    secretSoft(secretKey, chainCode, rng = utils_js_2.randomBytes) {
        abytes('secretKey', secretKey, 64);
        abytes('chainCode', chainCode, 32);
        const masterScalar = decodeScalar(secretKey.subarray(0, 32));
        const masterNonce = secretKey.subarray(32, 64);
        const pubPoint = ed25519_js_1.RistrettoPoint.fromHex(getPublicKey(secretKey));
        const t = new SigningContext('SchnorrRistrettoHDKD', rng);
        t.bytes(EMPTY);
        t.appendMessage('chain-code', chainCode);
        t.commitPoint('public-key', pubPoint);
        const scalar = t.challengeScalar('HDKD-scalar');
        const hdkdChainCode = t.challengeBytes('HDKD-chaincode', 32);
        const nonceSeed = (0, utils_js_2.concatBytes)((0, utils_js_1.numberToBytesLE)(masterScalar, 32), masterNonce);
        const nonce = t.witnessBytes('HDKD-nonce', 32, [masterNonce, nonceSeed]);
        const key = encodeScalar(modN(masterScalar + scalar));
        const res = (0, utils_js_2.concatBytes)(key, nonce);
        cleanBytes(key, nonce, nonceSeed, hdkdChainCode);
        t.clean();
        return res;
    },
    publicSoft(publicKey, chainCode) {
        abytes('publicKey', publicKey, 32);
        abytes('chainCode', chainCode, 32);
        const pubPoint = ed25519_js_1.RistrettoPoint.fromHex(publicKey);
        const t = new SigningContext('SchnorrRistrettoHDKD');
        t.bytes(EMPTY);
        t.appendMessage('chain-code', chainCode);
        t.commitPoint('public-key', pubPoint);
        const scalar = t.challengeScalar('HDKD-scalar');
        t.challengeBytes('HDKD-chaincode', 32);
        t.clean();
        return pubPoint.add(ed25519_js_1.RistrettoPoint.BASE.multiply(scalar)).toRawBytes();
    },
    secretHard(secretKey, chainCode) {
        abytes('secretKey', secretKey, 64);
        abytes('chainCode', chainCode, 32);
        const key = (0, utils_js_1.numberToBytesLE)(decodeScalar(secretKey.subarray(0, 32)), 32);
        const t = new SigningContext('SchnorrRistrettoHDKD');
        t.bytes(EMPTY);
        t.appendMessage('chain-code', chainCode);
        t.appendMessage('secret-key', key);
        const msk = t.challengeBytes('HDKD-hard', 32);
        const hdkdChainCode = t.challengeBytes('HDKD-chaincode', 32);
        t.clean();
        const res = secretFromSeed(msk);
        cleanBytes(key, msk, hdkdChainCode);
        t.clean();
        return res;
    },
};
const dleq = {
    proove(keyScalar, nonce, pubPoint, t, input, output) {
        t.protoName('DLEQProof');
        t.commitPoint('vrf:h', input);
        const r = t.witnessScalar(`proving${'\0'}0`, [nonce]);
        const R = ed25519_js_1.RistrettoPoint.BASE.multiply(r);
        t.commitPoint('vrf:R=g^r', R);
        const Hr = input.multiply(r);
        t.commitPoint('vrf:h^r', Hr);
        t.commitPoint('vrf:pk', pubPoint);
        t.commitPoint('vrf:h^sk', output);
        const c = t.challengeScalar('prove');
        const s = modN(r - c * keyScalar);
        return { proof: { c, s }, proofBatchable: { R, Hr, s } };
    },
    verify(pubPoint, t, input, output, proof) {
        if (pubPoint.equals(ed25519_js_1.RistrettoPoint.ZERO))
            return false;
        t.protoName('DLEQProof');
        t.commitPoint('vrf:h', input);
        const R = pubPoint.multiply(proof.c).add(ed25519_js_1.RistrettoPoint.BASE.multiply(proof.s));
        t.commitPoint('vrf:R=g^r', R);
        const Hr = output.multiply(proof.c).add(input.multiply(proof.s));
        t.commitPoint('vrf:h^r', Hr);
        t.commitPoint('vrf:pk', pubPoint);
        t.commitPoint('vrf:h^sk', output);
        const realC = t.challengeScalar('prove');
        if (proof.c === realC)
            return { R, Hr, s: proof.s }; // proofBatchable
        return false;
    },
};
// VRF: Verifiable Random Function
function initVRF(ctx, msg, extra, pubPoint, rng = utils_js_2.randomBytes) {
    const t = new SigningContext('SigningContext', rng);
    t.label(ctx);
    t.bytes(msg);
    t.commitPoint('vrf-nm-pk', pubPoint);
    const hash = t.challengeBytes('VRFHash', 64);
    const input = ed25519_js_1.RistrettoPoint.hashToCurve(hash);
    const transcript = new SigningContext('VRF', rng);
    if (extra.length)
        transcript.label(extra);
    t.clean();
    cleanBytes(hash);
    return { input, t: transcript };
}
exports.vrf = {
    sign(msg, secretKey, ctx = EMPTY, extra = EMPTY, rng = utils_js_2.randomBytes) {
        abytes('msg', msg);
        abytes('secretKey', secretKey, 64);
        abytes('ctx', ctx);
        abytes('extra', extra);
        const keyScalar = decodeScalar(secretKey.subarray(0, 32));
        const nonce = secretKey.subarray(32, 64);
        const pubPoint = ed25519_js_1.RistrettoPoint.fromHex(getPublicKey(secretKey));
        const { input, t } = initVRF(ctx, msg, extra, pubPoint, rng);
        const output = input.multiply(keyScalar);
        const p = { input, output };
        const { proof } = dleq.proove(keyScalar, nonce, pubPoint, t, input, output);
        const cBytes = (0, utils_js_1.numberToBytesLE)(proof.c, 32);
        const sBytes = (0, utils_js_1.numberToBytesLE)(proof.s, 32);
        const res = (0, utils_js_2.concatBytes)(p.output.toRawBytes(), cBytes, sBytes);
        cleanBytes(nonce, cBytes, sBytes);
        return res;
    },
    verify(msg, signature, publicKey, ctx = EMPTY, extra = EMPTY, rng = utils_js_2.randomBytes) {
        abytes('msg', msg);
        abytes('signature', signature, 96); // O(point) || c(scalar) || s(scalar)
        abytes('pubkey', publicKey, 32);
        abytes('ctx', ctx);
        abytes('extra', extra);
        const pubPoint = ed25519_js_1.RistrettoPoint.fromHex(publicKey);
        if (pubPoint.equals(ed25519_js_1.RistrettoPoint.ZERO))
            return false;
        const proof = {
            c: parseScalar('signature.c', signature.subarray(32, 64)),
            s: parseScalar('signature.s', signature.subarray(64, 96)),
        };
        const { input, t } = initVRF(ctx, msg, extra, pubPoint, rng);
        const output = ed25519_js_1.RistrettoPoint.fromHex(signature.subarray(0, 32));
        if (output.equals(ed25519_js_1.RistrettoPoint.ZERO))
            throw new Error('vrf.verify: wrong output point (identity)');
        const proofBatchable = dleq.verify(pubPoint, t, input, output, proof);
        return proofBatchable === false ? false : true;
    },
};
// NOTE: for tests only, don't use
exports.__tests = {
    Strobe128,
    Merlin,
    SigningContext,
};
//# sourceMappingURL=index.js.map