Skip to content

Commit b3f7c1d

Browse files
authored
Merge pull request #3 from Merkleize/fraud
Fraud proofs
2 parents 9678fb8 + 4a27e30 commit b3f7c1d

14 files changed

+1408
-133
lines changed

.github/workflows/run-tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
- name: Add repository root to PYTHONPATH
4040
run: echo "PYTHONPATH=$PWD" >> $GITHUB_ENV
4141
- name: Run tests and capture output
42-
run: pytest -vv >test_output.txt
42+
run: pytest -vv
4343
- name: Upload test output as artifact
4444
uses: actions/upload-artifact@v2
4545
with:

examples/game256/game256_contracts.py

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
2+
3+
from dataclasses import dataclass
4+
from matt import NUMS_KEY
5+
from matt.argtypes import BytesType, IntType, SignerType
6+
from matt.btctools.common import sha256
7+
from matt.btctools.script import OP_ADD, OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_FROMALTSTACK, OP_NOT, OP_PICK, OP_ROT, OP_SHA256, OP_SWAP, OP_TOALTSTACK, OP_VERIFY, CScript
8+
from matt.contracts import ClauseOutput, StandardClause, StandardAugmentedP2TR, StandardP2TR, ContractState
9+
from matt.hub.fraud import Bisect_1, Computer, Leaf
10+
from matt.merkle import MerkleTree
11+
from matt.script_helpers import check_input_contract, check_output_contract, drop, dup, merkle_root, older
12+
from matt.utils import encode_wit_element
13+
14+
15+
# TODO: add forfait clauses whenever needed
16+
17+
# TODO: how to generalize what the contract does after the leaf? We should be able to compose clauses with some external code.
18+
# Do we need "clause" algebra?
19+
20+
class G256_S0(StandardP2TR):
21+
def __init__(self, alice_pk: bytes, bob_pk: bytes, forfait_timeout: int = 10):
22+
self.alice_pk = alice_pk
23+
self.bob_pk = bob_pk
24+
self.forfait_timeout = forfait_timeout
25+
26+
g256_s1 = G256_S1(alice_pk, bob_pk, forfait_timeout)
27+
# witness: <bob_sig> <x>
28+
choose = StandardClause(
29+
name="choose",
30+
script=CScript([
31+
*g256_s1.State.encoder_script(),
32+
*check_output_contract(g256_s1),
33+
34+
bob_pk,
35+
OP_CHECKSIG
36+
]),
37+
arg_specs=[
38+
('bob_sig', SignerType(bob_pk)),
39+
('x', IntType()),
40+
],
41+
next_outputs_fn=lambda args, _: [ClauseOutput(
42+
n=-1,
43+
next_contract=g256_s1,
44+
next_state=g256_s1.State(x=args['x'])
45+
)]
46+
)
47+
48+
super().__init__(NUMS_KEY, choose)
49+
50+
51+
class G256_S1(StandardAugmentedP2TR):
52+
@dataclass
53+
class State(ContractState):
54+
x: int
55+
56+
def encode(self):
57+
return sha256(encode_wit_element(self.x))
58+
59+
def encoder_script():
60+
return CScript([OP_SHA256])
61+
62+
def __init__(self, alice_pk: bytes, bob_pk: bytes, forfait_timeout):
63+
self.alice_pk = alice_pk
64+
self.bob_pk = bob_pk
65+
self.forfait_timeout = forfait_timeout
66+
67+
g256_s2 = G256_S2(alice_pk, bob_pk, forfait_timeout)
68+
69+
# reveal: <alice_sig> <t_a> <y> <sha256(x)>
70+
reveal = StandardClause(
71+
name="reveal",
72+
script=CScript([
73+
OP_DUP,
74+
75+
# check that the top of the stack is the embedded data
76+
*self.State.encoder_script(),
77+
*check_input_contract(),
78+
79+
# <alice_sig> <t_a> <y> <x>
80+
*g256_s2.State.encoder_script(),
81+
*check_output_contract(g256_s2),
82+
83+
alice_pk,
84+
OP_CHECKSIG
85+
]),
86+
arg_specs=[
87+
('alice_sig', SignerType(alice_pk)),
88+
('t_a', BytesType()),
89+
('y', IntType()),
90+
('x', IntType()),
91+
],
92+
next_outputs_fn=lambda args, _: [ClauseOutput(
93+
n=-1,
94+
next_contract=g256_s2,
95+
next_state=g256_s2.State(t_a=args['t_a'], y=args['y'], x=args['x'])
96+
)]
97+
)
98+
99+
super().__init__(NUMS_KEY, reveal)
100+
101+
102+
Compute2x = Computer(
103+
encoder=CScript([OP_SHA256]),
104+
func=CScript([OP_DUP, OP_ADD]),
105+
specs=[('x', IntType())],
106+
)
107+
108+
109+
NopInt = Computer(
110+
encoder=CScript([]),
111+
func=CScript([]),
112+
specs=[('x', IntType())],
113+
)
114+
115+
116+
class G256_S2(StandardAugmentedP2TR):
117+
@dataclass
118+
class State(ContractState):
119+
t_a: bytes
120+
y: int
121+
x: bytes
122+
123+
def encode(self):
124+
return MerkleTree([self.t_a, sha256(encode_wit_element(self.y)), sha256(encode_wit_element(self.x))]).root
125+
126+
def encoder_script():
127+
return CScript([
128+
OP_TOALTSTACK, OP_SHA256, OP_FROMALTSTACK, OP_SHA256,
129+
*merkle_root(3)
130+
])
131+
132+
def __init__(self, alice_pk: bytes, bob_pk: bytes, forfait_timeout: int = 10):
133+
self.alice_pk = alice_pk
134+
self.bob_pk = bob_pk
135+
self.forfait_timeout = forfait_timeout
136+
137+
# reveal: <alice_sig>
138+
withdraw = StandardClause(
139+
name="withdraw",
140+
script=CScript([
141+
*older(forfait_timeout),
142+
143+
alice_pk,
144+
OP_CHECKSIG
145+
]),
146+
arg_specs=[('alice_sig', SignerType(alice_pk))]
147+
)
148+
149+
def leaf_factory(i: int): return Leaf(alice_pk, bob_pk, Compute2x)
150+
151+
bisectg256_0 = Bisect_1(alice_pk, bob_pk, 0, 7, leaf_factory, forfait_timeout)
152+
# start_challenge: <bob_sig> <t_a> <y> <x> <z> <t_b>
153+
start_challenge = StandardClause(
154+
name="start_challenge",
155+
script=CScript([
156+
OP_TOALTSTACK,
157+
158+
# check that y != z
159+
OP_DUP, 3, OP_PICK, OP_EQUAL, OP_NOT, OP_VERIFY,
160+
161+
OP_TOALTSTACK,
162+
163+
# <bob_sig> <t_a> <y> <x> --- <t_b> <z>
164+
165+
*dup(3),
166+
167+
# verify the embedded data
168+
*self.State.encoder_script(),
169+
*check_input_contract(),
170+
171+
# <bob_sig> <t_a> <y> <x> --- <t_b> <z>
172+
OP_SHA256, OP_SWAP, OP_SHA256,
173+
# <bob_sig> <t_a> <sha256(x)> <sha256(y)> --- <t_b> <z>
174+
OP_ROT,
175+
# <bob_sig> <sha256(x)> <sha256(y)> <t_a> --- <t_b> <sha256(z)>
176+
177+
OP_FROMALTSTACK, OP_SHA256,
178+
# <bob_sig> <sha256(x)> <sha256(y)> <t_a> <sha256(z)> --- <t_b>
179+
OP_SWAP,
180+
# <bob_sig> <sha256(x)> <sha256(y)> <sha256(z)> <t_a> --- <t_b>
181+
182+
OP_FROMALTSTACK,
183+
184+
# <bob_sig> <sha256(x)> <sha256(y)> <sha256(z)> <t_a> <t_b>
185+
186+
*bisectg256_0.State.encoder_script(),
187+
*check_output_contract(bisectg256_0),
188+
189+
bob_pk,
190+
OP_CHECKSIG
191+
]),
192+
arg_specs=[
193+
('bob_sig', SignerType(bob_pk)),
194+
('t_a', BytesType()),
195+
('y', IntType()),
196+
('x', IntType()),
197+
('z', IntType()),
198+
('t_b', BytesType()),
199+
],
200+
next_outputs_fn=lambda args, _: [ClauseOutput(
201+
n=-1,
202+
next_contract=bisectg256_0,
203+
next_state=bisectg256_0.State(
204+
h_start=sha256(encode_wit_element(args['x'])),
205+
h_end_a=sha256(encode_wit_element(args['y'])),
206+
h_end_b=sha256(encode_wit_element(args['z'])),
207+
trace_a=args['t_a'],
208+
trace_b=args['t_b'],
209+
)
210+
)]
211+
)
212+
213+
super().__init__(NUMS_KEY, [withdraw, start_challenge])

examples/init.sh

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if [[ -z $NEW_ADDRESS ]]; then
2121
exit 1
2222
fi
2323

24-
# Generate 101 blocks, sending the block reward to the new address
25-
$BITCOIN_CLI generatetoaddress 110 $NEW_ADDRESS
24+
# Generate 300 blocks, sending the block reward to the new address
25+
$BITCOIN_CLI generatetoaddress 300 $NEW_ADDRESS
2626

27-
echo "Generated 101 blocks to address: $NEW_ADDRESS"
27+
echo "Generated 300 blocks to address: $NEW_ADDRESS"

examples/ram/ram_contracts.py

+67-48
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1+
from dataclasses import dataclass
12
from typing import List
23

34
from matt import CCV_FLAG_CHECK_INPUT, NUMS_KEY
45
from matt.argtypes import BytesType, MerkleProofType
56
from matt.btctools.script import OP_CAT, OP_CHECKCONTRACTVERIFY, OP_DUP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_FROMALTSTACK, OP_IF, OP_NOTIF, OP_PICK, OP_ROLL, OP_ROT, OP_SHA256, OP_SWAP, OP_TOALTSTACK, OP_TRUE, CScript
6-
from matt.contracts import ClauseOutput, StandardClause, StandardAugmentedP2TR
7-
from matt.merkle import is_power_of_2, floor_lg
7+
from matt.contracts import ClauseOutput, StandardClause, StandardAugmentedP2TR, ContractState
8+
from matt.merkle import MerkleTree, is_power_of_2, floor_lg
9+
from matt.script_helpers import merkle_root
810

911

1012
class RAM(StandardAugmentedP2TR):
11-
def __init__(self, size: List[bytes]):
13+
@dataclass
14+
class State(ContractState):
15+
leaves: List[bytes]
16+
17+
def encode(self):
18+
return MerkleTree(self.leaves).root
19+
20+
def encoder_script(size: int):
21+
return merkle_root(size)
22+
23+
def __init__(self, size: int):
1224
assert is_power_of_2(size)
1325

1426
self.size = size
@@ -57,6 +69,19 @@ def __init__(self, size: List[bytes]):
5769
]
5870
)
5971

72+
def next_outputs_fn(args: dict, state: RAM.State):
73+
i: int = args["merkle_proof"].get_leaf_index()
74+
75+
return [
76+
ClauseOutput(
77+
n=-1,
78+
next_contract=self,
79+
next_state=self.State(
80+
leaves=state.leaves[:i] + [args["new_value"]] + state.leaves[i+1:]
81+
)
82+
)
83+
]
84+
6085
# witness: <h_1> <d_1> <h_2> <d_2> ... <h_n> <d_n> <x_old> <x_new> <root>
6186
write = StandardClause(
6287
name="write",
@@ -86,44 +111,44 @@ def __init__(self, size: List[bytes]):
86111
# TODO: seems too verbose, there should be a way of optimizing it
87112
# top of stack is now: <h> <x_old> <x_new> <d>
88113
OP_IF,
89-
# top of stack is now: <h> <x_old> <x_new>
90-
# right child: we want h || x
91-
2, OP_PICK,
92-
# top of stack is now: <h> <x_old> <x_new> <h>
93-
OP_SWAP,
94-
OP_CAT,
95-
OP_SHA256,
96-
# top of stack is now: <h> <x_old> <SHA(h || x_new)>
97-
98-
OP_SWAP,
99-
# top of stack is now: <h> <SHA(h || x_new)> <x_old>
100-
OP_ROT,
101-
# top of stack is now: <SHA(h || x_new)> <x_old> <h>
102-
OP_SWAP,
103-
# OP_CAT,
104-
# OP_SHA256,
105-
# # top of stack is now: <SHA(h || x_new)> <SHA(h || x_old)>
106-
107-
# OP_SWAP,
108-
# # top of stack is now: <SHA(h || x_old)> <SHA(h || x_new)>
114+
# top of stack is now: <h> <x_old> <x_new>
115+
# right child: we want h || x
116+
2, OP_PICK,
117+
# top of stack is now: <h> <x_old> <x_new> <h>
118+
OP_SWAP,
119+
OP_CAT,
120+
OP_SHA256,
121+
# top of stack is now: <h> <x_old> <SHA(h || x_new)>
122+
123+
OP_SWAP,
124+
# top of stack is now: <h> <SHA(h || x_new)> <x_old>
125+
OP_ROT,
126+
# top of stack is now: <SHA(h || x_new)> <x_old> <h>
127+
OP_SWAP,
128+
# OP_CAT,
129+
# OP_SHA256,
130+
# # top of stack is now: <SHA(h || x_new)> <SHA(h || x_old)>
131+
132+
# OP_SWAP,
133+
# # top of stack is now: <SHA(h || x_old)> <SHA(h || x_new)>
109134
OP_ELSE,
110-
# top of stack is now: <h> <x_old> <x_new>
111-
2, OP_PICK,
112-
# top of stack is now: <h> <x_old> <x_new> <h>
113-
OP_CAT,
114-
OP_SHA256,
115-
# top of stack is now: <h> <x_old> <SHA(x_new || h)>
116-
117-
OP_SWAP,
118-
OP_ROT,
119-
# top of stack is now: <SHA(x_new || h)> <x_old> <h>
120-
121-
# OP_CAT,
122-
# OP_SHA256,
123-
# # top of stack is now: <SHA(x_new || h)> <SHA(x_old || h)>
124-
125-
# OP_SWAP,
126-
# # top of stack is now: <SHA(x_old || h)> <SHA(x_new || h)>
135+
# top of stack is now: <h> <x_old> <x_new>
136+
2, OP_PICK,
137+
# top of stack is now: <h> <x_old> <x_new> <h>
138+
OP_CAT,
139+
OP_SHA256,
140+
# top of stack is now: <h> <x_old> <SHA(x_new || h)>
141+
142+
OP_SWAP,
143+
OP_ROT,
144+
# top of stack is now: <SHA(x_new || h)> <x_old> <h>
145+
146+
# OP_CAT,
147+
# OP_SHA256,
148+
# # top of stack is now: <SHA(x_new || h)> <SHA(x_old || h)>
149+
150+
# OP_SWAP,
151+
# # top of stack is now: <SHA(x_old || h)> <SHA(x_new || h)>
127152
OP_ENDIF,
128153

129154
# this is in common between the two branches, so we can put it here
@@ -144,7 +169,7 @@ def __init__(self, size: List[bytes]):
144169
# stack: <new_root>
145170

146171
# Check that new_root is committed in the next output,
147-
0, # index
172+
-1, # index
148173
0, # NUMS
149174
-1, # keep current taptree
150175
0, # default, preserve amount
@@ -158,13 +183,7 @@ def __init__(self, size: List[bytes]):
158183
('new_value', BytesType()),
159184
('merkle_root', BytesType()),
160185
],
161-
next_output_fn=lambda args: [
162-
ClauseOutput(
163-
n=0,
164-
next_contract=self,
165-
next_data=args["merkle_proof"].get_new_root_after_update(args["new_value"])
166-
)
167-
]
186+
next_outputs_fn=next_outputs_fn
168187
)
169188

170189
super().__init__(NUMS_KEY, [withdraw, write])

0 commit comments

Comments
 (0)