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_loose(y, shifted_target);

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