src/proof/base/epoch0/verifier.js

import * as bn128 from '@aztec/bn128';
import { constants, errors } from '@aztec/dev-utils';
import BN from 'bn.js';
import ProofType from '../types';

const { ZERO_BN } = constants;

/**
 * @class
 * @classdesc Class to verify AZTEC zero-knowledge proofs
 */
class Verifier {
    /**
     * @param {Proof} proof the Proof object to be verified
     */
    constructor(proof) {
        if (!proof) {
            throw new Error('proof cannot be undefined');
        }
        if (!proof.type || !ProofType[proof.type]) {
            throw new Error(`proof type should be one of ${ProofType.enumValues}`);
        }
        this.proof = proof;
        this.errors = [];

        this.challenge = this.hexToGroupScalar(this.proof.challengeHex);
        if (this.proof.type === ProofType.JOIN_SPLIT.name) {
            this.publicValue = this.hexToGroupScalar(this.proof.data[this.proof.data.length - 1][0], true);
        } else {
            this.publicValue = bn128.zeroBnRed;
        }
        this.extractData();
    }

    get isValid() {
        return this.errors.length === 0;
    }

    /**
     * Convert ABI encoded proof transcript back into BN.js form (for scalars) and elliptic.js form (for points)
     */
    extractData() {
        this.data = this.proof.data.map((item) => {
            return {
                kBar: this.hexToGroupScalar(item[0]),
                aBar: this.hexToGroupScalar(item[1]),
                gamma: this.hexToGroupPoint(item[2], item[3]),
                sigma: this.hexToGroupPoint(item[4], item[5]),
            };
        });
    }

    /**
     * Converts a hexadecimal input to a group point

     * @param {string} xHex hexadecimal representation of x coordinate
     * @param {string} yHex hexadecimal representation of y coordinate
     * @returns {BN} bn.js formatted version of a point on the bn128 curve
     */
    hexToGroupPoint(xHex, yHex) {
        let x = new BN(xHex.slice(2), 16);
        let y = new BN(yHex.slice(2), 16);
        if (!x.lt(bn128.curve.p)) {
            this.errors.push(errors.codes.X_TOO_BIG);
        }
        if (!y.lt(bn128.curve.p)) {
            this.errors.push(errors.codes.Y_TOO_BIG);
        }
        x = x.toRed(bn128.curve.red);
        y = y.toRed(bn128.curve.red);
        const lhs = y.redSqr();
        const rhs = x
            .redSqr()
            .redMul(x)
            .redAdd(bn128.curve.b);
        if (!lhs.fromRed().eq(rhs.fromRed())) {
            this.errors.push(errors.codes.NOT_ON_CURVE);
        }
        return bn128.curve.point(x, y);
    }

    /**
     * Convert a hexadecimal input into a scalar bn.js
     *
     * @param {string} hex hex input
     * @param {boolean} canbeZero control to determine hex input can be zero
     * @returns {BN} bn.js formatted version of the scalar
     */
    hexToGroupScalar(hex, canBeZero = false) {
        const hexBN = new BN(hex.slice(2), 16);
        if (!hexBN.lt(bn128.curve.n)) {
            this.errors.push(errors.codes.SCALAR_TOO_BIG);
        }
        if (!canBeZero && hexBN.eq(ZERO_BN)) {
            this.errors.push(errors.codes.SCALAR_IS_ZERO);
        }
        return hexBN.toRed(bn128.groupReduction);
    }
}

export default Verifier;