Bitcoin Forum
October 04, 2026, 09:02:10 PM *
News: Latest Bitcoin Core release: 31.1 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: Can a contract react to a difficulty value? (for an option-like contract)  (Read 234 times)
d5000 (OP)
Legendary
*
Offline

Activity: 4788
Merit: 11308


Decentralization Maximalist


View Profile
August 08, 2026, 02:41:31 AM
Merited by Mia Chloe (4), ABCbits (2), vapourminer (1), athanred (1)
 #1

As of 2026, there seems to be no Bitcoin Script opcode that can react to the nBits value (difficulty). So currently "natively" we can't set up a contract with branches executed depending on the difficulty.

Let's say Alice and Bob enter a contract which allow the person who guessed the difficulty range correctly to claim an amount of Bitcoin.
Code:
If BLOCK(X)["nBits"] > Y
then
   <allow Alice to claim Z BTC>
endif
<allow Bob to claim Z BTC from BLOCK(X+200) on>

This could be used in a kind of option-like contract, where users could "win" Bitcoins if the difficulty is higher or lower than a certain value. I think this is interesting because difficulty roughly reacts to price, and thus this could allow to create derivatives in the style of futures.

Now asking Gemini it I found a possible method: using BitVM. The idea is that the Alice (the beneficiary) has to upload a proof based on the block header. If there is something wrong with the proof, Bob can submit a fraud proof, submitting the headers of the blocks before the target block until a checkpoint both agreed on, and BitVM would check the proof-of-work and if Bob was correct that Alice has commited fraud, Alice would be penalized and Bob would get an extra amount.

This looks almost to good to be true so I'd like to ask:

1) Is that method feasible?

2) Would the fraud proof require an extremely large transaction if multiple headers are submitted?

3) Are there other methods? Or are there future planned softforks or BIPs which could allow this feature?

athanred
Full Member
***
Offline

Activity: 174
Merit: 313


View Profile
August 08, 2026, 05:15:22 AM
Merited by vapourminer (4), LoyceV (4), ABCbits (2), d5000 (1), Mia Chloe (1)
 #2

Quote
Can a contract react to a difficulty value?
Yes. Make two transactions, with two different locktimes: one should be block-based, and another should be time-based. If the given block number will be mined faster than predicted, then block-based timelock will be confirmed faster. If not, then time-based transaction will be processed instead.

https://en.bitcoin.it/wiki/NLockTime
d5000 (OP)
Legendary
*
Offline

Activity: 4788
Merit: 11308


Decentralization Maximalist


View Profile
August 08, 2026, 05:33:15 AM
Last edit: August 08, 2026, 05:45:44 AM by d5000
Merited by athanred (1)
 #3

Yes. Make two transactions, with two different locktimes: one should be block-based, and another should be time-based.
That's a quite nice workaround for block times and short-term hashrate. I've read elswhere that this could be used like an "insurance contract" against stretches of very low hashrate compared to the current difficulty value, although I think this would be more suitable for altcoins or forks. It can of course be used to speculate on the next difficulty change, and that would be indeed perhaps useful.

However, it will not work if the contract resolution should depend on the exact difficulty value itself, and of course also not if we want to react to a difficulty change in the relative distant future because the block time is of course "reset" every diff change. The idea of these contracts would be to be able to speculate on the difficulty various periods away from the present.

PS: Shower thought: Could you perhaps chain such contracts together for several difficulty periods, using multisig and maybe some atomic swap-style HTLC to ensure atomicity? You could for example pre-sign a big amount of transactions with different values and broadcast always the one that works. However, I can imagine there could be problems if the blocks where you wanted to "renew" the contract are too full. In addition you probably will need one transaction per difficulty period, and if you want to speculate on the diff in a year, then you need to pay 28 times the transaction fee.

athanred
Full Member
***
Offline

Activity: 174
Merit: 313


View Profile
August 08, 2026, 05:50:52 AM
Merited by vapourminer (1), d5000 (1)
 #4

Quote
However, it will not work if the contract resolution should depend on the exact difficulty value itself
Then, you can use Proof of Work inside Script, in this case, coins will move, if a given signature will be below N bytes. However, because some Bitcoin developers are against Merged Mining, it is independent from the currently used block headers. If you want to connect it with real headers, then use Signet, because OP_CAT is there.
d5000 (OP)
Legendary
*
Offline

Activity: 4788
Merit: 11308


Decentralization Maximalist


View Profile
August 12, 2026, 12:20:25 AM
Merited by athanred (1)
 #5

Then, you can use Proof of Work inside Script, in this case, coins will move, if a given signature will be below N bytes.
I don't understand how that can be used to resolve a contract based on the "real" block headers?

What seems to be possible, according to Google's AI, is to check if a header matches a certain difficulty value. The proposed opcodes are:

Quote from: Gemini
Code:
// --- STEP 1: Verify Header Hash <= Target ---
OP_DUP
OP_HASH256                   // Stack: [Header, Hash256(Header)]
OP_OVER                      // Stack: [Header, Hash, Header]
72 4 OP_SUBSTR               // Extract 4-byte 'bits' from header (bytes 72-75)
OP_CALL <UNPACK_BITS_TO_TARGET> // Convert compact 'bits' to 256-bit Target
OP_OVER OP_OVER              // Stack: [Header, Hash, Target, Hash, Target]
OP_LESSTHANOREQUAL256        // Check: Hash <= Target
OP_VERIFY                    // Fail if invalid PoW

// --- STEP 2: Verify Target Meets Contract Difficulty Condition ---
OP_PUSH32 <MAX_TARGET>       // Push required max target threshold
OP_LESSTHANOREQUAL256        // Check: Target <= Max_Target (High difficulty check)
OP_VERIFY

// --- STEP 3: Spender Authentication ---
<PUBKEY>
OP_CHECKSIG

I have interpreted that in the following way: if a fake header is provided, then the person spending the coins would actually have to solve the puzzle with massive resources, while if you use a real header, then you would be able to simply re-use the hash of the existing block.

In other words: the code allows people to cheat, but it would be probably much more expensive (due to the power wasted to solve the puzzle) than the value that can be extracted. Is this correct?

But even if this worked this way, could the "cheater" not simply use any existing block header? Or is there a method to control the block height? (If I interpreted correctly, that's the step where you would need BitVM.)

(To be clear: the idea here is to say: If at block e.g. 1,000,000 the difficulty is above or below X, then coins can be spent by an user owning an address. But after e.g. 1000 blocks another person can spend the coins.)

athanred
Full Member
***
Offline

Activity: 174
Merit: 313


View Profile
August 12, 2026, 04:27:18 AM
Merited by d5000 (2), vapourminer (1)
 #6

Quote
I don't understand how that can be used to resolve a contract based on the "real" block headers?
Because it doesn't. It basically blocks coins on a Proof of Work, and then, depending on which side has higher hashrate, this side can move coins.

If you want to solve it on real block headers, then you need OP_CAT, or something like that. And in this case, you can simply use Signet, because it is deployed here.

Code:
OP_LESSTHANOREQUAL256
This opcode does not exist. If it would, then we would have sidechains already deployed on BTC, as well as many other things.

Code:
OP_SUBSTR
If you have this opcode, then it is like having OP_CAT.

Code:
OP_CALL <UNPACK_BITS_TO_TARGET>
Yeah, of course. I will simplify your Script even further: "OP_CALL <DO_EVERYTHING>". You know, that unpacking bits to target requires OP_CAT, or similar things, right? Again: if you need it here and now, then use Signet, because it is enabled there.

Quote
could the "cheater" not simply use any existing block header?
Of course, which is why when you have Proof of Work faucet inside Signet, it is quite complicated, because it is needed to prevent this attack, and also some more tricks.

Quote
Or is there a method to control the block height?
You can use locktime, to control the block height directly. If you use "OP_CHECKLOCKTIMEVERIFY", then your transaction is valid only after a given block height. And the same is true, if you use a timestamp here. Which allows you to make simple contracts, like that:
Code:
OP_IF
  <aliceKey> <timestamp>
OP_ELSE
  <bobKey> <blockNumber>
OP_ENDIF
OP_CHECKLOCKTIMEVERIFY OP_DROP OP_CHECKSIG
Or, in this simple case, you can have just two transactions, with two different locktimes, one sending coins to Alice, and another one to Bob.

Quote
But after e.g. 1000 blocks another person can spend the coins.
Then simply add "1000 OP_CHECKSEQUENCEVERIFY", after executing the previous contract.
Danish Ali
Member
**
Offline

Activity: 84
Merit: 166

★Bitvest.io★ Play Plinko or Invest!


View Profile
August 12, 2026, 02:02:30 PM
Merited by d5000 (3), vapourminer (1)
 #7

Are there other methods?
DLCs (Discreet Log Contracts) — works with opcodes that already exist on mainnet, no OP_CAT needed:
​
Code:
OP_IF
<outcome_high_sig> <oracle_pubkey> OP_CHECKSIGVERIFY
<alice_sig> <aliceKey> OP_CHECKSIG
OP_ELSE
<outcome_low_sig> <oracle_pubkey> OP_CHECKSIGVERIFY
<bob_sig> <bobKey> OP_CHECKSIG
OP_ENDIF
​Oracle signs the actual nBits outcome, contract is pre-signed for both branches using adapter signatures. Trade-off: you trust the oracle instead of trusting consensus rules directly. Deployable today though, unlike everything else discussed here.
​
Would the fraud proof require an extremely large transaction if multiple headers are submitted?
​No, if you bisect instead of dumping the whole chain on-chain:
​
Code:
Alice commits: MerkleRoot(header_1...header_N)
Bob disputes -> binary search over N headers (log N rounds)
Only the ONE disputed header gets submitted on-chain
​On-chain footprint stays constant regardless of N. It's the number of off-chain rounds that scales, not on-chain data.
​
Of course, which is why when you have Proof of Work faucet inside Signet, it is quite complicated,
The "trick" is checkpoint linkage — you can't just verify a valid header, you need to verify it chains back to the agreed checkpoint:
​
Code:
OP_DUP OP_HASH256                    // hash current 80-byte header
// (requires byte-slicing via OP_CAT/OP_SUBSTR to extract prev_block_hash from offset 4-36)
<expected_prev_hash> OP_EQUALVERIFY  // verify linkage
// repeat per header back to checkpoint
​This is exactly why you need OP_CAT — not for hashing a single header, but to concatenate and fold header→header links together inside Script.

▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ ★ ★ ★ ★ ★ ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
● PLINKO    |7| SLOTS     (+) ROULETTE    ▼ BIT SPIN ║ BITVEST ║ PLAY or INVEST ║ ✔ Rainbot  ✔ Happy Hours  ✔ Faucet
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ ★ ★ ★ ★ ★ ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
d5000 (OP)
Legendary
*
Offline

Activity: 4788
Merit: 11308


Decentralization Maximalist


View Profile
August 12, 2026, 07:46:50 PM
Merited by vapourminer (1)
 #8

And in this case, you can simply use Signet, because it is deployed here.
Yes, but the idea is to use real Bitcoins Smiley I'll look into OP_CAT though. Perhaps that stuff could be tested first on BCH or some other chain which offers it.

Code:
OP_LESSTHANOREQUAL256
This opcode does not exist.

Code:
OP_SUBSTR
If you have this opcode, then it is like having OP_CAT.
Lol! Thanks for pointing that out. I already suspected it a bit that the AI was hallucinating here ... should have insisted. Anyway you wrote that the "cheat" is indeed possible.

Regarding "controlling the block height", I meant to prevent that cheat by "testing" the block height in the header, and I don't see how CLTV can help with that (if not by a long chain of proofs that a CLTV transaction was spent in the block with the header ...). The CSV part is quite clear to me. What I want to understand is the "react to difficulty" part.

DLCs (Discreet Log Contracts)
Thanks. Yes, I'm aware of DLCs. But I would have preferred a way without an oracle, be it trustless or not.

​No, if you bisect instead of dumping the whole chain on-chain:
Ah, that makes sense, thanks. So basically I understand that with OP_CAT the idea with PoW puzzles should be possible, but without OP_CAT, it is not.



In my edit of the second post, I came up with an idea based on @athanred's first interesting method with the two CLTV (timestamp based and blockheight based) outputs. I would like to develop this idea a bit further:

Let's say Alice and Bob want to enter a contract: In 2 difficulty periods in the future, if the difficulty is 10 or higher, then Alice has the right to spend 1 BTC. (I intentionally am simplifying the numbers of course Wink ).

Let's say the difficulty periods are: period 0 (current one), period 1, and period 2 (target period of the contract).

At the start the difficulty is at 9. The idea is to have a chain of two transactions: Transaction 1 gets signed by both (via multisig) and can be also spent by both after the start of Period 1, and the second one is the one that actually "divides" the money according to the contract rules: if Alice wins, she can spend the coins. Transaction 2 is to be signed at the start of the difficult period 2, so it can react to the real difficulty of the target period.

- Transaction 1 does the following (in pseudocode): It contains different timestamps and a block height when the spending transaction could be spent if the difficulty is higher than the values inmediately over 9 (I ignore values over 12, but they could also be added with similar branches).

Code:
OP_IF
   <multisig_addr1> <timestamp corresponding to difficulty 12 or higher>
OP_ELSE OP_IF
  <multisig_addr2> <timestamp corresponding to difficulty 11 or higher>
OP_ELSE OP_IF
 <multisig_addr3> <timestamp corresponding to difficulty 10 or higher>
OP_ELSE
  <multisig_addr4> <blockheight period 1 start> OP_CHECKLOCKTIMEVERIFY
OP_ENDIF OP_ENDIF OP_ENDIF OP_CHECKLOCKTIMEVERIFY OP_DROP OP_CHECKSIG
This means basically: if the difficulty in the next period is higher than the current period, then one of the timestamp based transactions can be signed first.

I hope the idea becomes clear. Depending on the timestamp, the coins "land" on a different multisig address after Period 1 has started.

Now you also pre-sign a similar Transaction 2 for every multisig address with the following code:

Code:
OP_IF
  <Alice's key> <timestamp corresponding to difficulty 10 or higher>
OP_ELSE
  <Bob's key> <block height period 2 start>
OP_ENDIF
OP_CHECKLOCKTIMEVERIFY OP_DROP OP_CHECKSIG
So Bob can move the coins once the period starts.

You need the different multisig addresses, because the timestamp for difficulty 10+ will differ, depending on the difficulty of the previous period.

I have one doubt though: Transaction 1 seems only to work if the difficulty keeps the same or increases (i.e. if block times are becoming shorter, so the timestamp based conditions are triggered).

And Transaction 2 also only works this way if the difficulty at the start of period 1 had been 9 or lower.

But maybe instead of the block height of the period start we could choose a block height already some blocks inside the new difficulty period (e.g. "period start + 100 blocks"), so we can add timestamps for lower difficulties too.

Am I missing something?

d5000 (OP)
Legendary
*
Offline

Activity: 4788
Merit: 11308


Decentralization Maximalist


View Profile
August 12, 2026, 10:56:35 PM
Last edit: August 12, 2026, 11:16:31 PM by d5000
Merited by Danish Ali (1)
 #9

​Transaction 1 sends funds to other multisig addresses (multisig_addr1, multisig_addr2 etc). As spending from those outputs needs to be 2-of-2 multisig, Alice and Bob must both actively cooperate in creating and signing the child transaction (Transaction 2) on the branch representing the reality.
True, but the idea is that all transactions are pre-signed from the start, like in atomic swaps. The "game" is thus only limited to the question "who broadcasts what, and when"?

And here we have always someone who is interested in broadcasting. First, if Alice bets on the higher difficulty and Bob on the lower one, then Alice will be more incentived to "continue with the contract" if the difficulty in Period 1 was rising. But both have of course an interest that the other party can't run away with the funds in the last transaction. after the CLTV deadline of transaction 2 ends, both can spend the funds.

This is actually something that I missed: at the very block when Bob has access to the funds, there is a race condition and the one with the higher fees probably will wins. So that transaction is "too simple" ... (This is actually probably already the case with @athanred's first idea too. So we would need some other condition preventing Alice to spend the funds after Bob gets access. I will rethink that, but I'm sure HTLCs give us the needed tools ...) (No, here I think I was wrong, as long as Alice's timestamp hasn't triggered, Bob is the only one with access to the funds in this case. So Transaction 2 seems to be ok.)

​3. Timestamps are Stochastic Proxies for Difficulty:
Yes that adds a bit of unpredictability. Thus I think that the method is only feasible for short periods and for relatively big "steps" for the "difficulty triggers". For example in the current difficulty situation of >125 trillion, the "steps" should be at least making up 5% of the current value (a bit more than 5 trillion).

Danish Ali
Member
**
Offline

Activity: 84
Merit: 166

★Bitvest.io★ Play Plinko or Invest!


View Profile
August 13, 2026, 12:40:19 AM
Merited by vapourminer (1)
 #10

​
True, but the idea is that all transactions are pre-signed from the start, like in atomic swaps. The "game" is thus only limited to the question "who broadcasts what, and when"?
​Pre-signing resolving the "refuse to sign" problem is a fair point. But I believe it compounds the issue, actually, in a more dramatic fashion.

​CLTV is only checking wall-clock time and not chain state. After a branch passes the diff-12 timestamp, it becomes broadcastable — even if the actual difficulty is still sitting at 9 and it was just variance in block timing that reached the timestamp, not an actual hashrate/difficulty increase. There is no way for script to verify that the timestamp actually corresponds to the true difficulty event on-chain.

​So, "who broadcasts what, and when" is not a race to the true outcome. Rather, it's a race to broadcast as soon as it is technically possible, so you may be the first to release your favorable branch before your counterparty broadcasts their version. This makes the problem more concrete than just a "noisy proxy": script cannot cross-check the timestamp with what actually happened on-chain.

▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ ★ ★ ★ ★ ★ ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
● PLINKO    |7| SLOTS     (+) ROULETTE    ▼ BIT SPIN ║ BITVEST ║ PLAY or INVEST ║ ✔ Rainbot  ✔ Happy Hours  ✔ Faucet
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ ★ ★ ★ ★ ★ ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!