"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const kyber_1 = require("@dedis/kyber");
const point_1 = require("@dedis/kyber/pairing/point");
const crypto_browserify_1 = require("crypto-browserify");
const light_1 = require("protobufjs/light");
const proto_1 = require("../network/proto");
const protobuf_1 = require("../protobuf");
const EMPTY_BUFFER = Buffer.allocUnsafe(0);
exports.BLS_INDEX = 0;
exports.BDN_INDEX = 1;
const { bls, bdn, Mask } = kyber_1.sign;
/**
 * Convert an integer into a little-endian buffer
 *
 * @param v The number to convert
 * @returns a 32bits buffer
 */
function int2buf(v) {
    const b = Buffer.allocUnsafe(4);
    b.writeInt32LE(v, 0);
    return b;
}
class SkipBlock extends light_1.Message {
    constructor(props) {
        super(props);
        this.backlinks = this.backlinks || [];
        this.verifiers = this.verifiers || [];
        this.forward = this.forward || [];
        this.hash = Buffer.from(this.hash || EMPTY_BUFFER);
        this.data = Buffer.from(this.data || EMPTY_BUFFER);
        this.genesis = Buffer.from(this.genesis || EMPTY_BUFFER);
        this.payload = Buffer.from(this.payload || EMPTY_BUFFER);
    }
    /**
     * Getter for the forward links
     *
     * @returns the list of forward links
     */
    get forwardLinks() {
        return this.forward;
    }
    /**
     * @see README#Message classes
     */
    static register() {
        protobuf_1.registerMessage("SkipBlock", SkipBlock, proto_1.Roster, ForwardLink);
    }
    /**
     * Calculate the hash of the block
     *
     * @returns the hash
     */
    computeHash() {
        const h = crypto_browserify_1.createHash("sha256");
        h.update(int2buf(this.index));
        h.update(int2buf(this.height));
        h.update(int2buf(this.maxHeight));
        h.update(int2buf(this.baseHeight));
        for (const bl of this.backlinks) {
            h.update(bl);
        }
        for (const v of this.verifiers) {
            h.update(v);
        }
        h.update(this.genesis);
        h.update(this.data);
        if (this.roster) {
            for (const pub of this.roster.list.map((srvid) => srvid.getPublic())) {
                h.update(pub.marshalBinary());
            }
        }
        if (this.signatureScheme > 0) {
            h.update(int2buf(this.signatureScheme));
        }
        return h.digest();
    }
}
exports.SkipBlock = SkipBlock;
/**
 * Compute the minimum number of signatures an aggregate must have
 * according to the total number of nodes
 *
 * @param n The total numner of conodes
 * @returns the minimum number of signatures required
 */
function defaultThreshold(n) {
    // n = 3f + 1 with f the number of faulty nodes
    return n - ((n - 1) / 3);
}
class ForwardLink extends light_1.Message {
    constructor(props) {
        super(props);
        this.from = Buffer.from(this.from || EMPTY_BUFFER);
        this.to = Buffer.from(this.to || EMPTY_BUFFER);
    }
    /**
     * @see README#Message classes
     */
    static register() {
        protobuf_1.registerMessage("ForwardLink", ForwardLink, proto_1.Roster, ByzcoinSignature);
    }
    /**
     * Compute the hash of the forward link
     *
     * @returns the hash
     */
    hash() {
        const h = crypto_browserify_1.createHash("sha256");
        h.update(this.from);
        h.update(this.to);
        if (this.newRoster) {
            h.update(this.newRoster.id);
        }
        return h.digest();
    }
    /**
     * Verify the signature against the list of public keys
     *
     * @param publics The list of public keys
     * @returns an error if something is wrong, null otherwise
     */
    verify(publics) {
        return this.verifyWithScheme(publics, exports.BLS_INDEX);
    }
    /**
     * Verify the signature against the list of public keys
     * using the specified signature scheme
     *
     * @param publics   The list of public keys
     * @param scheme    The index of the signature scheme
     * @returns an error if something is wrong, null otherwise
     */
    verifyWithScheme(publics, scheme) {
        if (!this.hash().equals(this.signature.msg)) {
            return new Error("recreated message does not match");
        }
        const mask = new Mask(publics, this.signature.getMask());
        // Note: we only check that there are enough signatures because if the mask
        // is forged to have only one key for instance, the creation of the mask
        // will fail with a mismatch length
        if (mask.getCountEnabled() < defaultThreshold(mask.getCountTotal())) {
            return new Error("not enough signers");
        }
        switch (scheme) {
            case exports.BLS_INDEX:
                return this.verifyBLS(mask);
            case exports.BDN_INDEX:
                return this.verifyBDN(mask);
            default:
                return new Error("unknown signature scheme");
        }
    }
    verifyBLS(mask) {
        const agg = mask.aggregate;
        if (!bls.verify(this.signature.msg, agg, this.signature.getSignature())) {
            return new Error("BLS signature not verified");
        }
        return null;
    }
    verifyBDN(mask) {
        if (!bdn.verify(this.signature.msg, mask, this.signature.getSignature())) {
            return new Error("BDN signature not verified");
        }
        return null;
    }
}
exports.ForwardLink = ForwardLink;
class ByzcoinSignature extends light_1.Message {
    constructor(props) {
        super(props);
        this.msg = Buffer.from(this.msg || EMPTY_BUFFER);
        this.sig = Buffer.from(this.sig || EMPTY_BUFFER);
    }
    /**
     * @see README#Message classes
     */
    static register() {
        protobuf_1.registerMessage("ByzcoinSig", ByzcoinSignature);
    }
    /**
     * Get the actual bytes of the signature. The remaining part is the mask.
     *
     * @returns the signature
     */
    getSignature() {
        return this.sig.slice(0, new point_1.BN256G1Point().marshalSize());
    }
    /**
     * Get the correct aggregation of the public keys using the mask to know
     * which one has been used
     *
     * @param publics The public keys of the roster
     * @returns the aggregated public key for this signature
     */
    getAggregate(publics) {
        const mask = new Mask(publics, this.getMask());
        return mask.aggregate;
    }
    /**
     * Get the buffer slice that represents the mask
     *
     * @returns the mask as a buffer
     */
    getMask() {
        return this.sig.slice(new point_1.BN256G1Point().marshalSize());
    }
}
exports.ByzcoinSignature = ByzcoinSignature;
SkipBlock.register();
ForwardLink.register();
ByzcoinSignature.register();
