Running prize contests without witness swiping

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.

1 Like