k = 13;
field = "pallas";

constant "ConsensusProposal_V1" {
    EcFixedPointShort VALUE_COMMIT_VALUE,
    EcFixedPoint VALUE_COMMIT_RANDOM,
    EcFixedPointBase NULLIFIER_K,
}

witness "ConsensusProposal_V1" {
    # Burnt coin secret key
    Base input_secret_key,
    # Unique serial number corresponding to the burnt coin
    Base input_serial,
    # The value of the burnt coin
    Base input_value,
    # The epoch the burnt coin was minted on
    Base epoch,
    # The reward value
    Base reward,
    # Random blinding factor for the value commitment
    Scalar input_value_blind,
    # Leaf position of the coin in the Merkle tree of coins
    Uint32 leaf_pos,
    # Merkle path to the coin
    MerklePath path,
    # Random blinding factor for the value commitment of the new coin
    Scalar output_value_blind,
    # Election seed y
    Base mu_y,
    # Election seed rho
    Base mu_rho,
    # Sigma1
    Base sigma1,
    # Sigma2
    Base sigma2,
    # Lottery headstart
    Base headstart,
}

circuit "ConsensusProposal_V1" {
    # Witnessed constants
    ZERO = witness_base(0);
    SERIAL_PREFIX = witness_base(2);
    SEED_PREFIX = witness_base(3);
    SECRET_PREFIX = witness_base(4);

    # =============
    # Burn old coin
    # =============

    # Poseidon hash of the nullifier
    nullifier = poseidon_hash(input_secret_key, input_serial);
    constrain_instance(nullifier);

    # Constrain the epoch this coin was minted on.
    # We use this as our timelock mechanism.
    constrain_instance(epoch);

    # We derive the coin's public key for the signature and
    # VRF proof verification and constrain its coordinates:
    input_pub = ec_mul_base(input_secret_key, NULLIFIER_K);
    pub_x = ec_get_x(input_pub);
    pub_y = ec_get_y(input_pub);
    constrain_instance(pub_x);
    constrain_instance(pub_y);

    # Construct the burned coin
    C = poseidon_hash(
        pub_x,
        pub_y,
        input_value,
        epoch,
        input_serial,
    );

    # Merkle inclusion proof
    root = merkle_root(leaf_pos, path, C);
    constrain_instance(root);

    # Pedersen commitment for burned coin's value
    vcv = ec_mul_short(input_value, VALUE_COMMIT_VALUE);
    vcr = ec_mul(input_value_blind, VALUE_COMMIT_RANDOM);
    value_commit = ec_add(vcv, vcr);
    # Since value_commit is a curve point, we fetch its coordinates
    # and constrain them:
    constrain_instance(ec_get_x(value_commit));
    constrain_instance(ec_get_y(value_commit));

    # =============
    # Mint new coin
    # =============

    # Constrain reward value
    constrain_instance(reward);

    # Pedersen commitment for new coin's value (old value + reward)
    output_value = base_add(input_value, reward);
    nvcv = ec_mul_short(output_value, VALUE_COMMIT_VALUE);
    nvcr = ec_mul(output_value_blind, VALUE_COMMIT_RANDOM);
    output_value_commit = ec_add(nvcv, nvcr);
    # Since the new value commit is also a curve point, we'll do the same
    # coordinate dance:
    constrain_instance(ec_get_x(output_value_commit));
    constrain_instance(ec_get_y(output_value_commit));

    # The serial of the new coin is derived from the old coin
    output_serial = poseidon_hash(SERIAL_PREFIX, input_secret_key, input_serial);

    # The secret key of the new coin is derived from old coin
    output_secret_key = poseidon_hash(SECRET_PREFIX, input_secret_key);
    output_pub = ec_mul_base(output_secret_key, NULLIFIER_K);
    output_pub_x = ec_get_x(output_pub);
    output_pub_y = ec_get_y(output_pub);

    # Poseidon hash of the new coin
    # In here we set the new epoch as ZERO, thus removing a
    # potentially existing timelock.
    output_coin = poseidon_hash(
        output_pub_x,
        output_pub_y,
        output_value,
        ZERO,
        output_serial,
    );
    constrain_instance(output_coin);

    # ============================
    # Constrain lottery parameters
    # ============================

    # Coin y, constructed with the old serial for seeding:
    seed = poseidon_hash(SEED_PREFIX, input_serial);
    y = poseidon_hash(seed, mu_y);
    constrain_instance(mu_y);
    constrain_instance(y);

    # Coin rho (seed):
    rho = poseidon_hash(seed, mu_rho);
    constrain_instance(mu_rho);
    constrain_instance(rho);

    # Calculate lottery target
    term_1 = base_mul(sigma1, input_value);
    term_2 = base_mul(sigma2, input_value);
    shifted_term_2 = base_mul(term_2, input_value);
    target = base_add(term_1, shifted_term_2);
    shifted_target = base_add(target, headstart);
    constrain_instance(sigma1);
    constrain_instance(sigma2);
    constrain_instance(headstart);

    # Play lottery
    less_than_strict(y, shifted_target);

    # At this point we've enforced all of our public inputs.
}
