package meerkat.mixer.proofs.concrete;

import com.google.protobuf.InvalidProtocolBufferException;
import meerkat.crypto.concrete.ECElGamalEncryption;
import meerkat.mixer.proofs.Mix2nizk.Prover;
import meerkat.mixer.proofs.Mix2nizk.Verifier;
import meerkat.mixer.proofs.generic.SigmaFiatShamir;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.Mixing;
import org.factcenter.qilin.primitives.concrete.ECGroup;

import java.util.Random;


/**
 * implements of Prover and Verifier interfaces
 * this implementation assumes that each RerandomizableEncryptedMessage is an ElGamalCiphertext
 */
public class Mix2nizk implements Prover, Verifier {
//
    private final ECGroup group;
    private final Random rand;
    private final ECElGamalEncryption encryptor;
    private final Statements mixParams;
    final SigmaFiatShamir<Mixing.Mix2Proof, Mixing.Mix2Proof.FirstMessage, Mixing.Mix2Proof.FinalMessage> mix2NIZK;

    /**
     * @param rand
     * @param encryptor
     */
    public Mix2nizk(Random rand, ECElGamalEncryption encryptor) {

        this.rand = rand;
        this.encryptor = encryptor;
        this.group = this.encryptor.getGroup();
        this.mixParams = new Statements(encryptor);
        this.mix2NIZK =  new SigmaFiatShamir<>(ProtobufConcatenators.concatNIZK);
    }

    /**
     * @param a
     * @param b
     * @param c  - if switched then c = rerandomize(b,r2) else c = rerandomize(a,r1)
     * @param d  - if switched then d = rerandomize(a,r1) else d = rerandomize(b,r2)
     * @param switched    - trueClauseIndex
     * @param layer - row of a,b in encryption table
     * @param r1
     * @param r2
     * @return      - a valid ZKP that indeed c,d calculated as required
     * @throws InvalidProtocolBufferException
     */
    public Mixing.Mix2Proof prove(Crypto.RerandomizableEncryptedMessage a,
                                           Crypto.RerandomizableEncryptedMessage b,
                                           Crypto.RerandomizableEncryptedMessage c,
                                           Crypto.RerandomizableEncryptedMessage d,
                                           boolean switched, int layer, int switchIdx, int out0Idx, int out1Idx,
                                           Crypto.EncryptionRandomness r1,
                                           Crypto.EncryptionRandomness r2) throws InvalidProtocolBufferException {


        Statements.Mix2Statement statement = mixParams.createStatement(a, b, c, d);
        Statements.Mix2StatementWitness witness = mixParams.createMix2Witness(r1, r2, switched);

        Mix2.Prover prover = new Mix2.Prover(encryptor, rand, statement, witness);

        Mixing.Mix2Proof.Location location = Mixing.Mix2Proof.Location.newBuilder()
                .setLayer(layer)
                .setSwitchIdx(switchIdx)
                .setOut0(out0Idx)
                .setOut1(out1Idx)
                .build();
        return mix2NIZK.generateNizk(prover).toBuilder().setLocation(location).build();
    }

    /**
     * verify zero knowledge proof
     * @param in1
     * @param in2
     * @param out1
     * @param out2
     * @param proof out1 = rerandomize(in1) && out2 = rerandomize(in2)
     *              ||
     *              out1 = rerandomize(in2) && out2 = rerandomize(in1)
     * @return  true iff all 4 or proofs are valid
     * @throws InvalidProtocolBufferException
     */
    public boolean verify(Crypto.RerandomizableEncryptedMessage in1,
                          Crypto.RerandomizableEncryptedMessage in2,
                          Crypto.RerandomizableEncryptedMessage out1,
                          Crypto.RerandomizableEncryptedMessage out2,
                          Mixing.Mix2Proof proof) throws InvalidProtocolBufferException {

        Statements.Mix2Statement statement = mixParams.createStatement(in1,in2,out1,out2);
        Mix2.Verifier verifier = new Mix2.Verifier(encryptor, statement);

        return mix2NIZK.verifyNizk(proof, verifier);
    }
}

