I got this to work with this implementation of the contract:
simc "0.7.0"; // compile contract with -Z enums
fn eq_128(a: u128, b: u128) -> bool {
// eq_128 is not a jet, but it will be in the stdlib eventually.
jet::eq_256(<(u128, u128)>::into((0, a)), <(u128, u128)>::into((0, b)))
}
fn is_correct(p: u64, q: u64) -> bool {
// Check that the proposed solution to the challenge is correct.
// This is a slightly realistic version. The challenge is to
// factor 311954490450626290040901547370562193609. This size of
// integer factorization isn't very difficult for sympy.factorint().
let n: u128 = 311954490450626290040901547370562193609;
eq_128(n, jet::multiply_64(p, q))
}
enum Action {
Update,
Claim(u64, u64),
}
fn script_hash_for_input_script(state_data: u256) -> u256 {
let tap_leaf: u256 = jet::tapleaf_hash();
let state_ctx1: Ctx8 = jet::tapdata_init();
let state_ctx2: Ctx8 = jet::sha_256_ctx_8_add_32(state_ctx1, state_data);
let state_leaf: u256 = jet::sha_256_ctx_8_finalize(state_ctx2);
let tap_node: u256 = jet::build_tapbranch(tap_leaf, state_leaf);
// Compute a taptweak using this.
let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0;
let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node);
// Turn the taptweak into a script hash
let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init();
let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32
let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key);
jet::sha_256_ctx_8_finalize(hash_ctx3)
}
fn is_current_stored_state(dest_addr_script_hash: u256) -> bool {
jet::eq_256(
script_hash_for_input_script(dest_addr_script_hash),
unwrap(jet::input_script_hash(jet::current_index()))
)
}
fn store(new_state: u256) {
// Assert that the output state is correct, i.e. "store".
assert!(jet::eq_256(
script_hash_for_input_script(new_state),
unwrap(jet::output_script_hash(0))
));
}
fn enforce_relative_duration(min_duration: Duration) {
// Assert that the current input is spent in a transaction that can only
// appear a duration of at least min_duration units of 512 seconds after
// the input's UTXO. Panic otherwise.
// Transaction version must be at least 2.
assert!(jet::le_32(2, jet::version()));
// Fetch and parse sequence
let actual_data: Either<Distance, Duration> = unwrap(jet::parse_sequence(jet::current_sequence()));
let actual_duration: Duration = unwrap_right::<Distance>(actual_data);
assert!(jet::le_16(min_duration, actual_duration));
}
fn and(a: bool, b: bool) -> bool {
match a {
true => b,
false => false,
}
}
fn not(b: bool) -> bool {
match b {
true => false,
false => true,
}
}
fn asset_equal(a_asset: Asset1, b_asset: Asset1) -> bool {
match a_asset {
Left((a1, a2): (u1, u256)) => match b_asset {
Left((b1, b2): (u1, u256)) => and(jet::eq_1(a1, b1), jet::eq_256(a2, b2)),
Right(b: u256) => false,
},
Right(a3: u256) => match b_asset {
Left(b: (u1, u256)) => false,
Right(b3: u256) => jet::eq_256(a3, b3),
},
}
}
fn safe_add_64(a: u64, b: u64) -> u64 {
let (carry, sum): (bool, u64) = jet::add_64(a, b);
assert!(not(carry));
sum
}
fn update(dest_addr_script_hash: u256) {
let NUMS: u256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
let required_duration: Duration = 10; // Can only update destination every 5120 seconds
let minimum_deposit: u64 = 100;
// Check that time is far enough in the future, or that this is the
// first update (the stored state should be set to all 1s when the
// prize is established, which is an unattainable scriptHash value
// and is a signal that no user has yet tried to claim the prize, so
// the update() can happen with no delay).
match is_current_stored_state(NUMS) {
true => (),
false => enforce_relative_duration(required_duration),
};
// Assert covenant is input 0 and fetch its details
assert!(jet::eq_32(jet::current_index(), 0));
let (prize_asset, prize_amount): (Asset1, Amount1) = jet::current_amount();
let explicit_prize_amount: u64 = unwrap_right::<(u1, u256)>(prize_amount);
let (out_asset, out_amount): (Asset1, Amount1) = unwrap(jet::output_amount(0));
let explicit_out_amount: u64 = unwrap_right::<(u1, u256)>(out_amount);
// Enforce recursive covenant script on output 0
store(dest_addr_script_hash);
// Ensure the covenant retains the same asset type
assert!(asset_equal(prize_asset, out_asset));
// Invariant: The covenant balance must increase by at least
// the minimum deposit.
// Note: This permits an unlimited number of other inputs and
// outputs (including change and fees). The only requirement is that
// the assets controlled by the covenant increase by the required
// deposit amount.
let minimum_output: u64 = safe_add_64(explicit_prize_amount, minimum_deposit);
assert!(jet::le_64(minimum_output, explicit_out_amount));
}
fn claim(dest_addr_script_hash: u256, p: u64, q: u64) {
// Check that dest_addr_script_hash is stored in the covenant's
// existing state commitment (as the script hash that a user has
// most recently stored using an Update action).
assert!(is_current_stored_state(dest_addr_script_hash));
// Check that output[0] also matches the dest_addr_script_hash.
let osh: u256 = unwrap(jet::output_script_hash(0));
assert!(jet::eq_256(dest_addr_script_hash, osh));
// Check that output[0] is receiving the full prize amount. (Otherwise,
// an attacker could replace a legitimate claim with a modified
// transaction that pays the legitimate winner a trivial amount and
// pays the attacker the remainder!)
let (prize_asset, prize_amount): (Asset1, Amount1) = jet::current_amount();
let explicit_prize_amount: u64 = unwrap_right::<(u1, u256)>(prize_amount);
let (out_asset, out_amount): (Asset1, Amount1) = unwrap(jet::output_amount(0));
let explicit_out_amount: u64 = unwrap_right::<(u1, u256)>(out_amount);
assert!(jet::le_64(explicit_prize_amount, explicit_out_amount));
// Check that output[0] is receiving the original prize pool asset.
// (Otherwise, an attacker could replace a legitimate claim with one
// that pays the legitimate winner a numerically equal amount of a
// worthless asset!)
assert!(asset_equal(prize_asset, out_asset));
// Check that the proposed solution to the challenge is correct.
assert!(is_correct(p, q));
}
fn main() {
let dest_addr_script_hash: u256 = witness::DEST_ADDR_SCRIPT_HASH;
match witness::ACTION {
Action::Update => update(dest_addr_script_hash),
Action::Claim(p: u64, q: u64) => claim(dest_addr_script_hash, p, q),
}
}
The contract pays a prize to anyone who reveals the factorization of 311954490450626290040901547370562193609.
There’s a lot more I can say about this, but I still want to clean up my demo scripts.