include "../node_modules/circomlib/circuits/pedersen.circom";
include "../node_modules/circomlib/circuits/mimcsponge.circom";
include "../node_modules/circomlib/circuits/bitify.circom";
include "../node_modules/circomlib/circuits/eddsamimcsponge.circom";
include "./blake2s/blake2s.circom";

template HashLeftRight(n_rounds) {
  signal input left;
  signal input right;

  signal output hash;

  component hasher = MiMCSponge(2, n_rounds, 1);
  left ==> hasher.ins[0];
  right ==> hasher.ins[1];
  hasher.k <== 0;

  hash <== hasher.outs[0];
}

template Selector() {
  signal input input_elem;
  signal input path_elem;
  signal input path_index;

  signal output left;
  signal output right;

  signal left_selector_1;
  signal left_selector_2;
  signal right_selector_1;
  signal right_selector_2;

  path_index * (1-path_index) === 0

  left_selector_1 <== (1 - path_index)*input_elem;
  left_selector_2 <== (path_index)*path_elem;
  right_selector_1 <== (path_index)*input_elem;
  right_selector_2 <== (1 - path_index)*path_elem;

  left <== left_selector_1 + left_selector_2;
  right <== right_selector_1 + right_selector_2;
}

template Semaphore(jubjub_field_size, n_levels, n_rounds) {
    // BEGIN signals

    signal input signal_hash;
    signal input external_nullifier;
    
    signal private input fake_zero;

    fake_zero === 0;

    // mimc vector commitment
    signal private input identity_pk[2];
    signal private input identity_nullifier;
    signal private input identity_trapdoor;
    signal private input identity_path_elements[n_levels];
    signal private input identity_path_index[n_levels];

    // signature on (external nullifier, signal_hash) with identity_pk
    signal private input auth_sig_r[2];
    signal private input auth_sig_s;

    // get a prime subgroup element derived from identity_pk
    component dbl1 = BabyDbl();
    dbl1.x <== identity_pk[0];
    dbl1.y <== identity_pk[1];
    component dbl2 = BabyDbl();
    dbl2.x <== dbl1.xout;
    dbl2.y <== dbl1.yout;
    component dbl3 = BabyDbl();
    dbl3.x <== dbl2.xout;
    dbl3.y <== dbl2.yout;

    // mimc hash
    signal output root;
    signal output nullifiers_hash;

    // END signals


    component identity_nullifier_bits = Num2Bits(248);
    identity_nullifier_bits.in <== identity_nullifier;

    component identity_trapdoor_bits = Num2Bits(248);
    identity_trapdoor_bits.in <== identity_trapdoor;

    component identity_pk_0_bits = Num2Bits_strict();
    identity_pk_0_bits.in <== dbl3.xout;

    component identity_commitment = Pedersen(3*256);
    // BEGIN identity commitment
    for (var i = 0; i < 256; i++) {
      if (i < 254) {
        identity_commitment.in[i] <== identity_pk_0_bits.out[i];
      } else {
        identity_commitment.in[i] <== 0;
      }

      if (i < 248) {
        identity_commitment.in[i + 256] <== identity_nullifier_bits.out[i];
        identity_commitment.in[i + 2*256] <== identity_trapdoor_bits.out[i];
      } else {
        identity_commitment.in[i + 256] <== 0;
        identity_commitment.in[i + 2*256] <== 0;
      }
    }
    // END identity commitment

    // BEGIN tree
    component selectors[n_levels];
    component hashers[n_levels];

    for (var i = 0; i < n_levels; i++) {
      selectors[i] = Selector();
      hashers[i] = HashLeftRight(n_rounds);

      identity_path_index[i] ==> selectors[i].path_index;
      identity_path_elements[i] ==> selectors[i].path_elem;

      selectors[i].left ==> hashers[i].left;
      selectors[i].right ==> hashers[i].right;
    }

    identity_commitment.out[0] ==> selectors[0].input_elem;

    for (var i = 1; i < n_levels; i++) {
      hashers[i-1].hash ==> selectors[i].input_elem;
    }

    root <== hashers[n_levels - 1].hash;
    // END tree

    // BEGIN nullifiers
    component external_nullifier_bits = Num2Bits(232);
    external_nullifier_bits.in <== external_nullifier;

    component nullifiers_hasher = Blake2s(512, 0);
    for (var i = 0; i < 248; i++) {
      nullifiers_hasher.in_bits[i] <== identity_nullifier_bits.out[i];
    }

    for (var i = 0; i < 232; i++) {
      nullifiers_hasher.in_bits[248 + i] <== external_nullifier_bits.out[i];
    }

    for (var i = 0; i < n_levels; i++) {
      nullifiers_hasher.in_bits[248 + 232 + i] <== identity_path_index[i];
    }

    for (var i = (248 + 232 + n_levels); i < 512; i++) {
      nullifiers_hasher.in_bits[i] <== 0;
    }

    component nullifiers_hash_num = Bits2Num(253);
    for (var i = 0; i < 253; i++) {
      nullifiers_hash_num.in[i] <== nullifiers_hasher.out[i];
    }

    nullifiers_hash <== nullifiers_hash_num.out;

    // END nullifiers

    // BEGIN verify sig
    component msg_hasher = MiMCSponge(2, n_rounds, 1);
    msg_hasher.ins[0] <== external_nullifier;
    msg_hasher.ins[1] <== signal_hash;
    msg_hasher.k <== 0;

    component sig_verifier = EdDSAMiMCSpongeVerifier();
    (1 - fake_zero) ==> sig_verifier.enabled;
    identity_pk[0] ==> sig_verifier.Ax;
    identity_pk[1] ==> sig_verifier.Ay;
    auth_sig_r[0] ==> sig_verifier.R8x;
    auth_sig_r[1] ==> sig_verifier.R8y;
    auth_sig_s ==> sig_verifier.S;
    msg_hasher.outs[0] ==> sig_verifier.M;

    // END verify sig
}
