const createBlakeHash = require("blake-hash");
const bigInt = require("snarkjs").bigInt;
const babyJub = require("./babyjub");
const pedersenHash = require("./pedersenHash").hash;
const crypto = require("crypto");

exports.cratePrvKey = cratePrvKey;
exports.prv2pub= prv2pub;
exports.sign = sign;
exports.verify = verify;
exports.packSignature = packSignature;
exports.unpackSignature = unpackSignature;


function cratePrvKey() {
    return crypto.randomBytes(32);
}

function pruneBuffer(_buff) {
    const buff = Buffer.from(_buff);
    buff[0] = buff[0] & 0xF8;
    buff[31] = buff[31] & 0x7F;
    buff[31] = buff[31] | 0x40;
    return buff;
}

function prv2pub(prv) {
    const sBuff = pruneBuffer(createBlakeHash("blake512").update(prv).digest().slice(0,32));
    let s = bigInt.leBuff2int(sBuff);
    const A = babyJub.mulPointEscalar(babyJub.Base8, s.shr(3));
    return A;
}

function sign(prv, msg) {
    const h1 = createBlakeHash("blake512").update(prv).digest();
    const sBuff = pruneBuffer(h1.slice(0,32));
    const s = bigInt.leBuff2int(sBuff);
    const A = babyJub.mulPointEscalar(babyJub.Base8, s.shr(3));

    const rBuff = createBlakeHash("blake512").update(Buffer.concat([h1.slice(32,64), msg])).digest();
    let r = bigInt.leBuff2int(rBuff);
    r = r.mod(babyJub.subOrder);
    const R8 = babyJub.mulPointEscalar(babyJub.Base8, r);
    const R8p = babyJub.packPoint(R8);
    const Ap = babyJub.packPoint(A);
    const hmBuff = pedersenHash(Buffer.concat([R8p, Ap, msg]));
    const hm = bigInt.leBuff2int(hmBuff);
    const S = r.add(hm.mul(s)).mod(babyJub.subOrder);
    return {
        R8: R8,
        S: S
    };
}

function verify(msg, sig, A) {
    // Check parameters
    if (typeof sig != "object") return false;
    if (!Array.isArray(sig.R8)) return false;
    if (sig.R8.length!= 2) return false;
    if (!babyJub.inCurve(sig.R8)) return false;
    if (!Array.isArray(A)) return false;
    if (A.length!= 2) return false;
    if (!babyJub.inCurve(A)) return false;
    if (sig.S>= babyJub.subOrder) return false;

    const R8p = babyJub.packPoint(sig.R8);
    const Ap = babyJub.packPoint(A);
    const hmBuff = pedersenHash(Buffer.concat([R8p, Ap, msg]));
    const hm = bigInt.leBuff2int(hmBuff);

    const Pleft = babyJub.mulPointEscalar(babyJub.Base8, sig.S);
    let Pright = babyJub.mulPointEscalar(A, hm.mul(bigInt("8")));
    Pright = babyJub.addPoint(sig.R8, Pright);

    if (!Pleft[0].equals(Pright[0])) return false;
    if (!Pleft[1].equals(Pright[1])) return false;
    return true;
}

function packSignature(sig) {
    const R8p = babyJub.packPoint(sig.R8);
    const Sp = bigInt.leInt2Buff(sig.S, 32);
    return Buffer.concat([R8p, Sp]);
}

function unpackSignature(sigBuff) {
    return {
        R8: babyJub.unpackPoint(sigBuff.slice(0,32)),
        S: bigInt.leBuff2int(sigBuff.slice(32,64))
    };
}


