├── .gitignore ├── README.md ├── cashproof ├── __init__.py ├── cases.py ├── func.py ├── funcop.py ├── op.py ├── op_impl │ ├── __init__.py │ ├── assume.py │ ├── bitlogicops.py │ ├── controlops.py │ ├── cryptoops.py │ ├── generic.py │ ├── nop.py │ ├── numericops.py │ ├── pushops.py │ ├── spliceops.py │ └── stackops.py ├── opcodes.py ├── ops.py ├── parse_equiv_file.py ├── sort.py ├── stack.py └── statements.py ├── examples ├── bitlogic.equiv ├── cashscript.equiv ├── if.equiv ├── nop.equiv ├── nop_verify.equiv ├── numbers.equiv ├── spedn.equiv └── verify.equiv └── run.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .idea 3 | *.iml 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cashproof 2 | 3 | Usage: 4 | 5 | 1. install python 3 6 | 2. `pip install z3-solver` 7 | 3. `python run.py [filenames]`, examples can be found under `examples/` 8 | -------------------------------------------------------------------------------- /cashproof/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeOfPython/cashproof/ff92615faa338e025a9d3a5dd98112233f6daf76/cashproof/__init__.py -------------------------------------------------------------------------------- /cashproof/cases.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from itertools import zip_longest 3 | from typing import List, Sequence, Optional 4 | from io import StringIO 5 | 6 | from cashproof.ops import ScriptItem, If, AssumeBool, prove_equivalence_single 7 | 8 | 9 | @dataclass 10 | class Case: 11 | bools: List[bool] 12 | ops: List[ScriptItem] 13 | 14 | 15 | def split_cases(ops: Sequence[ScriptItem], cases: List[Case]): 16 | def add_items(current_cases, items, bools): 17 | new_cases = [] 18 | for current_case in current_cases: 19 | new_cases.append(Case(current_case.bools + bools, current_case.ops + items)) 20 | return new_cases 21 | for op in ops: 22 | if isinstance(op, If): 23 | then = split_cases(op.then, add_items(cases, [AssumeBool(True)], [True])) 24 | otherwise = split_cases(op.otherwise, add_items(cases, [AssumeBool(False)], [False])) 25 | cases = then + otherwise 26 | else: 27 | cases = add_items(cases, [op], []) 28 | return cases 29 | 30 | 31 | def prove_equivalence_cases(opcodes1: Sequence[ScriptItem], opcodes2: Sequence[ScriptItem], 32 | max_stackitem_size, full_script: bool) -> Optional[str]: 33 | cases1 = split_cases(opcodes1, [Case([], [])]) 34 | cases2 = split_cases(opcodes2, [Case([], [])]) 35 | s = StringIO() 36 | for i, (case1, case2) in enumerate(zip_longest(cases1, cases2)): 37 | if case1 is None or case2 is None or case1.bools != case2.bools: 38 | print('Equivalence is FALSE, OP_IF blocks don\'t match.', file=s) 39 | print('CashScript requires the outlining structure of OP_IF blocks to match.', file=s) 40 | print(f'However, the structure differs at OP_IF case number {i}.', file=s) 41 | if case1 is None or not case1.bools: 42 | print(f'The left side has no OP_IF case, while the right side does.', file=s) 43 | elif case2 is None or not case2.bools: 44 | print(f'The right side has no OP_IF case, while the left side does.', file=s) 45 | else: 46 | print(f'The two sides have a matching OP_IF, but the nesting differs.', file=s) 47 | print(f'The left side takes bools {case1.bools}, while the right side takes bools {case2.bools}.', 48 | file=s) 49 | return s.getvalue() 50 | for case1, case2 in zip(cases1, cases2): 51 | result = prove_equivalence_single(case1.ops, case2.ops, max_stackitem_size, full_script=full_script) 52 | if result is not None: 53 | return result 54 | return None 55 | -------------------------------------------------------------------------------- /cashproof/func.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Sequence, Callable, Dict 3 | 4 | import z3 5 | 6 | from cashproof.sort import Sort 7 | from cashproof.stack import VarNames 8 | 9 | Func = Callable[..., z3.Ast] 10 | 11 | 12 | class Funcs(ABC): 13 | @abstractmethod 14 | def define(self, 15 | name: str, 16 | var_names: VarNames, 17 | input_sorts: Sequence[Sort], 18 | output_sort: Sort) -> Func: 19 | pass 20 | 21 | @abstractmethod 22 | def statements(self) -> Sequence[z3.Ast]: 23 | pass 24 | 25 | 26 | class FuncsDefault(Funcs): 27 | def __init__(self) -> None: 28 | self._funcs: Dict[str, z3.Function] = {} 29 | self._statements = [] 30 | 31 | def define(self, 32 | name: str, 33 | var_names: VarNames, 34 | input_sorts: Sequence[Sort], 35 | output_sort: Sort) -> Func: 36 | if name in self._funcs: 37 | return self._funcs[name] 38 | func = z3.Function(name, *[input_sort.to_z3() for input_sort in input_sorts] + [output_sort.to_z3()]) 39 | self._funcs[name] = func 40 | return func 41 | 42 | def statements(self) -> Sequence[z3.Ast]: 43 | return self._statements 44 | -------------------------------------------------------------------------------- /cashproof/funcop.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Sequence 3 | 4 | from cashproof.func import Sort, Funcs, VarNames 5 | from cashproof.op import Op, OpVars, OpVarNames 6 | from cashproof.opcodes import Opcode 7 | from cashproof.stack import Stacks 8 | from cashproof.statements import Statements 9 | 10 | 11 | class FuncOp(ABC): 12 | @abstractmethod 13 | def input_sorts(self) -> Sequence[Sort]: 14 | pass 15 | 16 | @abstractmethod 17 | def output_sorts(self) -> Sequence[Sort]: 18 | pass 19 | 20 | @abstractmethod 21 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 22 | pass 23 | 24 | 25 | class OpFuncOp(Op): 26 | def __init__(self, opcode: Opcode, func_op: FuncOp) -> None: 27 | self._opcode = opcode 28 | self._func_op = func_op 29 | 30 | def opcode(self) -> Opcode: 31 | return self._opcode 32 | 33 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 34 | inputs = [] 35 | outputs = [] 36 | for input_sort in self._func_op.input_sorts(): 37 | inputs.append(stack.pop(input_sort)) 38 | for output_sort in self._func_op.output_sorts(): 39 | outputs.append(stack.push(var_names.new(), output_sort)) 40 | return OpVarNames(inputs, outputs) 41 | 42 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 43 | self._func_op.statements(statements, op_vars, var_names, funcs) 44 | -------------------------------------------------------------------------------- /cashproof/op.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Sequence, Union, Any 3 | 4 | import z3 5 | from dataclasses import dataclass 6 | 7 | from cashproof.func import Funcs 8 | from cashproof.opcodes import Opcode 9 | from cashproof.stack import Stacks, VarNames 10 | from cashproof.statements import Statements 11 | 12 | 13 | @dataclass 14 | class OpVarNames: 15 | inputs: Sequence[str] 16 | outputs: Sequence[str] 17 | data: Any = None 18 | 19 | 20 | @dataclass 21 | class OpVars: 22 | inputs: Sequence[z3.Ast] 23 | outputs: Sequence[z3.Ast] 24 | data: Any 25 | 26 | 27 | Ast = Union[z3.Ast, bool] 28 | 29 | 30 | class Op(ABC): 31 | @abstractmethod 32 | def opcode(self) -> Opcode: 33 | pass 34 | 35 | @abstractmethod 36 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 37 | pass 38 | 39 | @abstractmethod 40 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 41 | pass 42 | -------------------------------------------------------------------------------- /cashproof/op_impl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EyeOfPython/cashproof/ff92615faa338e025a9d3a5dd98112233f6daf76/cashproof/op_impl/__init__.py -------------------------------------------------------------------------------- /cashproof/op_impl/assume.py: -------------------------------------------------------------------------------- 1 | from cashproof.func import Funcs 2 | from cashproof.op import Op, OpVars, OpVarNames 3 | from cashproof.opcodes import Opcode 4 | from cashproof.sort import SortBool 5 | from cashproof.stack import VarNames, Stacks 6 | from cashproof.statements import Statements 7 | 8 | 9 | class OpAssumeBool(Op): 10 | def __init__(self, b: bool) -> None: 11 | self._bool = b 12 | 13 | def opcode(self) -> Opcode: 14 | return Opcode.OP_DROP 15 | 16 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 17 | b = stack.pop(SortBool()) 18 | return OpVarNames([b], []) 19 | 20 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 21 | b, = op_vars.inputs 22 | statements.assume(b == self._bool) 23 | -------------------------------------------------------------------------------- /cashproof/op_impl/bitlogicops.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from cashproof.func import Funcs 4 | from cashproof.op import Op, OpVars, OpVarNames 5 | from cashproof.opcodes import Opcode 6 | from cashproof.sort import SortString, SortBool 7 | from cashproof.stack import Stacks, VarNames 8 | from cashproof.statements import Statements 9 | 10 | import z3 11 | 12 | 13 | class OpEqual(Op): 14 | def opcode(self) -> Opcode: 15 | return Opcode.OP_EQUAL 16 | 17 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 18 | b = stack.pop(None) 19 | a = stack.pop(None) 20 | equality = stack.push(var_names.new(f'({a}=={b})'), SortBool()) 21 | stack.add_sort_equality(a, b) 22 | return OpVarNames([a, b], [equality]) 23 | 24 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 25 | a, b = op_vars.inputs 26 | equality, = op_vars.outputs 27 | statements.assume(equality == (a == b)) 28 | 29 | 30 | class OpEqualVerify(Op): 31 | def opcode(self) -> Opcode: 32 | return Opcode.OP_EQUALVERIFY 33 | 34 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 35 | b = stack.pop(None) 36 | a = stack.pop(None) 37 | stack.add_sort_equality(a, b) 38 | return OpVarNames([a, b], []) 39 | 40 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 41 | a, b = op_vars.inputs 42 | equality = z3.Const(var_names.new(), SortBool().to_z3()) 43 | statements.assume((a == b) == equality) 44 | statements.verify(equality) 45 | 46 | 47 | class OpBitLogic(Op): 48 | def __init__(self, opcode: Opcode, func: Callable) -> None: 49 | self._opcode = opcode 50 | self._func = func 51 | 52 | def opcode(self) -> Opcode: 53 | return self._opcode 54 | 55 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 56 | b = stack.pop(SortString()) 57 | a = stack.pop(SortString()) 58 | result = stack.push(var_names.new(f'bitop({a},{b})'), SortString()) 59 | return OpVarNames([a, b], [result]) 60 | 61 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 62 | a, b = op_vars.inputs 63 | result, = op_vars.outputs 64 | statements.assume(z3.Length(result) == z3.Length(b)) 65 | statements.assume(z3.Length(result) == z3.Length(a)) 66 | statements.assume(z3.Length(result) <= statements.max_stackitem_size()) 67 | for i in range(0, statements.max_stackitem_size()): 68 | a_i = z3.BitVec(var_names.new(f'bitop_a{i}({a},{b})'), 8) 69 | b_i = z3.BitVec(var_names.new(f'bitop_b{i}({a},{b})'), 8) 70 | statements.assume(z3.Implies( 71 | i < z3.Length(result), 72 | z3.And( 73 | result[i] == z3.Unit(self._func(a_i, b_i)), 74 | a[i] == z3.Unit(a_i), 75 | b[i] == z3.Unit(b_i), 76 | ), 77 | )) 78 | 79 | 80 | BIT_LOGIC_OPS = [ 81 | OpBitLogic(Opcode.OP_AND, lambda a, b: a & b), 82 | OpBitLogic(Opcode.OP_OR, lambda a, b: a | b), 83 | OpBitLogic(Opcode.OP_XOR, lambda a, b: a ^ b), 84 | OpEqual(), 85 | OpEqualVerify(), 86 | ] 87 | -------------------------------------------------------------------------------- /cashproof/op_impl/controlops.py: -------------------------------------------------------------------------------- 1 | from cashproof.func import Funcs 2 | from cashproof.op import Op, OpVars, OpVarNames 3 | from cashproof.opcodes import Opcode 4 | from cashproof.sort import SortBool 5 | from cashproof.stack import VarNames, Stacks 6 | from cashproof.statements import Statements 7 | 8 | 9 | class OpVerify(Op): 10 | def opcode(self) -> Opcode: 11 | return Opcode.OP_VERIFY 12 | 13 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 14 | return OpVarNames([stack.pop(SortBool())], []) 15 | 16 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 17 | verify, = op_vars.inputs 18 | statements.verify(verify) 19 | 20 | 21 | CONTROL_OPS = [ 22 | OpVerify(), 23 | ] 24 | -------------------------------------------------------------------------------- /cashproof/op_impl/cryptoops.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Callable 2 | 3 | import z3 4 | 5 | from cashproof.func import Funcs 6 | from cashproof.op import Op, OpVars, Ast, OpVarNames 7 | from cashproof.opcodes import Opcode 8 | from cashproof.sort import SortString, SortBool 9 | from cashproof.stack import VarNames, Stacks 10 | from cashproof.statements import Statements 11 | 12 | 13 | def secp256k1_verify(var_names: VarNames, funcs: Funcs) -> Callable[..., Ast]: 14 | return funcs.define('SECP256k1_VERIFY', var_names, [SortString(), SortString(), SortString()], SortBool()) 15 | 16 | 17 | def sha256(var_names: VarNames, funcs: Funcs) -> Callable[..., Ast]: 18 | return funcs.define('SHA256', var_names, [SortString()], SortString()) 19 | 20 | 21 | class OpCheckSig(Op): 22 | def __init__(self, opcode: Opcode, verify: bool): 23 | self._opcode = opcode 24 | self._verify = verify 25 | 26 | def opcode(self) -> Opcode: 27 | return self._opcode 28 | 29 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 30 | pubkey = stack.pop(SortString()) 31 | sig = stack.pop(SortString()) 32 | result = [] if self._verify else [stack.push(var_names.new(f'checksig({sig}, {pubkey})'), SortBool())] 33 | return OpVarNames([sig, pubkey], result) 34 | 35 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 36 | sig, pubkey = op_vars.inputs 37 | verify_func = secp256k1_verify(var_names, funcs) 38 | preimage = z3.Const(var_names.glob('PREIMAGE'), SortString().to_z3()) 39 | hash_func = sha256(var_names, funcs) 40 | data = hash_func(hash_func(preimage)) 41 | if self._verify: 42 | validity = z3.Const(var_names.new(), SortBool().to_z3()) 43 | statements.assume(validity == verify_func(sig, data, pubkey)) 44 | statements.verify(validity) 45 | else: 46 | result, = op_vars.outputs 47 | statements.assume(result == verify_func(sig, data, pubkey)) 48 | 49 | 50 | class OpCheckMultiSig(Op): 51 | def __init__(self, num_sigs: int, opcode: Opcode, verify: bool): 52 | self._num_sigs = num_sigs 53 | self._opcode = opcode 54 | self._verify = verify 55 | 56 | def opcode(self) -> Opcode: 57 | return self._opcode 58 | 59 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 60 | publickeys = [stack.pop(SortString()) for _ in range(self._num_sigs)] 61 | signatures = [stack.pop(SortString()) for _ in range(self._num_sigs)] 62 | dummy = stack.pop(SortString()) 63 | results = [] if \ 64 | self._verify else \ 65 | [stack.push(var_names.new(f'checkmultisig({signatures}, {publickeys})'), SortBool())] 66 | return OpVarNames([dummy] + list(reversed(signatures)) + list(reversed(publickeys)), results) 67 | 68 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 69 | raise NotImplemented('OP_CHECKMULTISIG is currently implemented incorrectly') 70 | _, *inputs = op_vars.inputs 71 | signatures = inputs[:len(inputs) // 2] 72 | publickeys = inputs[len(inputs) // 2:] 73 | preimage = z3.Const(var_names.glob('PREIMAGE'), SortString().to_z3()) 74 | verify_func = secp256k1_verify(var_names, funcs) 75 | hash_func = sha256(var_names, funcs) 76 | data = hash_func(hash_func(preimage)) 77 | if self._verify: 78 | validity = z3.Const(var_names.new(), SortBool().to_z3()) 79 | for sig, pubkey in zip(signatures, publickeys): 80 | validity 81 | statements.verify(verify_func(sig, data, pubkey)) 82 | else: 83 | result, = op_vars.outputs 84 | statements.assume( 85 | z3.And(*[result == verify_func(sig, data, pubkey) 86 | for sig, pubkey in zip(signatures, publickeys)]) 87 | ) 88 | 89 | 90 | class OpCheckDataSig(Op): 91 | def __init__(self, opcode: Opcode, verify: bool): 92 | self._opcode = opcode 93 | self._verify = verify 94 | 95 | def opcode(self) -> Opcode: 96 | return self._opcode 97 | 98 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 99 | pubkey = stack.pop(SortString()) 100 | msg = stack.pop(SortString()) 101 | sig = stack.pop(SortString()) 102 | results = [] if \ 103 | self._verify else \ 104 | [stack.push(var_names.new(f'checkdatasig({sig}, {msg}, {pubkey})'), SortBool())] 105 | return OpVarNames([sig, msg, pubkey], results) 106 | 107 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 108 | sig, msg, pubkey = op_vars.inputs 109 | func = secp256k1_verify(var_names, funcs) 110 | data = sha256(var_names, funcs)(msg) 111 | if self._verify: 112 | validity = z3.Const(var_names.new(), SortBool().to_z3()) 113 | statements.assume(validity == func(sig, data, pubkey)) 114 | statements.verify(validity) 115 | else: 116 | result, = op_vars.outputs 117 | statements.assume(result == func(sig, data, pubkey)) 118 | 119 | 120 | class OpHash(Op): 121 | def __init__(self, opcode: Opcode, hash_funcs: Sequence[str]): 122 | self._opcode = opcode 123 | self._hash_funcs = hash_funcs 124 | 125 | def opcode(self) -> Opcode: 126 | return self._opcode 127 | 128 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 129 | data = stack.pop(SortString()) 130 | name = '.'.join(self._hash_funcs) 131 | return OpVarNames([data], [stack.push(f'{name}({data})', SortString())]) 132 | 133 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 134 | data, = op_vars.inputs 135 | hashed, = op_vars.outputs 136 | for hash_func in reversed(self._hash_funcs): 137 | func = funcs.define(hash_func, var_names, [SortString()], SortString()) 138 | data = func(data) 139 | statements.assume(data == hashed) 140 | 141 | 142 | CRYPTO_OPS = [ 143 | OpHash(Opcode.OP_RIPEMD160, ['RIPEMD160']), 144 | OpHash(Opcode.OP_SHA1, ['SHA1']), 145 | OpHash(Opcode.OP_SHA256, ['SHA256']), 146 | OpHash(Opcode.OP_HASH160, ['RIPEMD160', 'SHA256']), 147 | OpHash(Opcode.OP_HASH256, ['SHA256', 'SHA256']), 148 | 149 | OpCheckSig(Opcode.OP_CHECKSIG, False), 150 | OpCheckSig(Opcode.OP_CHECKSIGVERIFY, True), 151 | OpCheckDataSig(Opcode.OP_CHECKDATASIG, False), 152 | OpCheckDataSig(Opcode.OP_CHECKDATASIGVERIFY, True), 153 | ] 154 | -------------------------------------------------------------------------------- /cashproof/op_impl/generic.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Callable 2 | 3 | import z3 4 | 5 | from cashproof.func import Funcs 6 | from cashproof.op import Op, OpVars, Ast, OpVarNames 7 | from cashproof.opcodes import Opcode 8 | from cashproof.sort import Sort, SortBool 9 | from cashproof.stack import VarNames, Stacks 10 | from cashproof.statements import Statements 11 | 12 | 13 | class OpGeneric(Op): 14 | def __init__(self, 15 | opcode: Opcode, 16 | input_sorts: Sequence[Sort], 17 | output_sort: Sort, 18 | func: Callable[..., Ast]) -> None: 19 | self._opcode = opcode 20 | self._input_sorts = input_sorts 21 | self._output_sorts = output_sort 22 | self._func = func 23 | 24 | def opcode(self) -> Opcode: 25 | return self._opcode 26 | 27 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 28 | input_names = [] 29 | for input_sort in self._input_sorts: 30 | input_names.append(stack.pop(input_sort)) 31 | result = stack.push(var_names.new(), self._output_sorts) 32 | return OpVarNames(list(reversed(input_names)), [result]) 33 | 34 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 35 | result, = op_vars.outputs 36 | statements.assume(result == self._func(*op_vars.inputs)) 37 | 38 | 39 | class OpGenericVerify(Op): 40 | def __init__(self, 41 | opcode: Opcode, 42 | input_sorts: Sequence[Sort], 43 | func: Callable[..., Ast]) -> None: 44 | self._opcode = opcode 45 | self._input_sorts = input_sorts 46 | self._func = func 47 | 48 | def opcode(self) -> Opcode: 49 | return self._opcode 50 | 51 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 52 | input_names = [] 53 | for input_sort in self._input_sorts: 54 | input_names.append(stack.pop(input_sort)) 55 | return OpVarNames(input_names, []) 56 | 57 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 58 | validity = z3.Const(var_names.new(), SortBool().to_z3()) 59 | statements.assume(validity == self._func(*op_vars.inputs)) 60 | statements.verify(validity) 61 | -------------------------------------------------------------------------------- /cashproof/op_impl/nop.py: -------------------------------------------------------------------------------- 1 | from cashproof.func import Funcs 2 | from cashproof.op import Op, OpVars, OpVarNames 3 | from cashproof.opcodes import Opcode 4 | from cashproof.sort import SortInt, SortBool 5 | from cashproof.stack import VarNames, Stacks 6 | from cashproof.statements import Statements 7 | 8 | 9 | class OpNop(Op): 10 | def __init__(self, opcode: Opcode) -> None: 11 | self._opcode = opcode 12 | 13 | def opcode(self) -> Opcode: 14 | return self._opcode 15 | 16 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 17 | return OpVarNames([], []) 18 | 19 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 20 | pass 21 | 22 | 23 | class OpNopVerify(Op): 24 | def __init__(self, opcode: Opcode) -> None: 25 | self._opcode = opcode 26 | 27 | def opcode(self) -> Opcode: 28 | return self._opcode 29 | 30 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 31 | in_val = stack.pop(SortInt()) 32 | out_val = stack.push(var_names.new(), SortInt()) 33 | return OpVarNames([in_val], [out_val]) 34 | 35 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 36 | in_val, = op_vars.inputs 37 | out_val, = op_vars.outputs 38 | statements.assume(in_val == out_val) 39 | verify = funcs.define(f'{self._opcode.name}_FUNC', var_names, [SortInt()], SortBool()) 40 | statements.verify(verify(in_val)) 41 | 42 | 43 | NOPS = [ 44 | OpNop(Opcode.OP_NOP), 45 | OpNop(Opcode.OP_NOP1), 46 | OpNopVerify(Opcode.OP_CHECKLOCKTIMEVERIFY), 47 | OpNopVerify(Opcode.OP_CHECKSEQUENCEVERIFY), 48 | OpNop(Opcode.OP_NOP4), 49 | OpNop(Opcode.OP_NOP5), 50 | OpNop(Opcode.OP_NOP6), 51 | OpNop(Opcode.OP_NOP7), 52 | OpNop(Opcode.OP_NOP8), 53 | OpNop(Opcode.OP_NOP9), 54 | OpNop(Opcode.OP_NOP10), 55 | ] 56 | -------------------------------------------------------------------------------- /cashproof/op_impl/numericops.py: -------------------------------------------------------------------------------- 1 | import z3 2 | 3 | from cashproof.op_impl.generic import OpGeneric, OpGenericVerify 4 | from cashproof.opcodes import Opcode 5 | from cashproof.sort import SortInt, SortBool 6 | 7 | 8 | NUMERIC_OPS = [ 9 | OpGeneric(Opcode.OP_1ADD, [SortInt()], SortInt(), lambda x: x + 1), 10 | OpGeneric(Opcode.OP_1SUB, [SortInt()], SortInt(), lambda x: x - 1), 11 | 12 | OpGeneric(Opcode.OP_NEGATE, [SortInt()], SortInt(), lambda x: -x), 13 | OpGeneric(Opcode.OP_ABS, [SortInt()], SortInt(), lambda x: z3.If(x > 0, x, -x)), 14 | OpGeneric(Opcode.OP_NOT, [SortBool()], SortBool(), lambda x: z3.Not(x)), 15 | OpGeneric(Opcode.OP_0NOTEQUAL, [SortInt()], SortBool(), lambda x: x != 0), 16 | 17 | OpGeneric(Opcode.OP_ADD, [SortInt(), SortInt()], SortInt(), lambda a, b: a + b), 18 | OpGeneric(Opcode.OP_SUB, [SortInt(), SortInt()], SortInt(), lambda a, b: a - b), 19 | # OpGeneric(Opcode.OP_MUL, lambda a, b: a * b), 20 | OpGeneric(Opcode.OP_DIV, [SortInt(), SortInt()], SortInt(), lambda a, b: a / b), 21 | OpGeneric(Opcode.OP_MOD, [SortInt(), SortInt()], SortInt(), lambda a, b: a % b), 22 | 23 | OpGenericVerify(Opcode.OP_NUMEQUALVERIFY, [SortInt(), SortInt()], lambda a, b: a == b), 24 | OpGeneric(Opcode.OP_BOOLAND, [SortBool(), SortBool()], SortBool(), lambda a, b: z3.And(a, b)), 25 | OpGeneric(Opcode.OP_BOOLOR, [SortBool(), SortBool()], SortBool(), lambda a, b: z3.Or(a, b)), 26 | OpGeneric(Opcode.OP_NUMEQUAL, [SortInt(), SortInt()], SortBool(), lambda a, b: a == b), 27 | OpGeneric(Opcode.OP_NUMNOTEQUAL, [SortInt(), SortInt()], SortBool(), lambda a, b: a != b), 28 | OpGeneric(Opcode.OP_LESSTHAN, [SortInt(), SortInt()], SortBool(), lambda a, b: a < b), 29 | OpGeneric(Opcode.OP_LESSTHANOREQUAL, [SortInt(), SortInt()], SortBool(), lambda a, b: a <= b), 30 | OpGeneric(Opcode.OP_GREATERTHAN, [SortInt(), SortInt()], SortBool(), lambda a, b: a > b), 31 | OpGeneric(Opcode.OP_GREATERTHANOREQUAL, [SortInt(), SortInt()], SortBool(), lambda a, b: a >= b), 32 | OpGeneric(Opcode.OP_MIN, [SortInt(), SortInt()], SortInt(), lambda a, b: z3.If(a > b, b, a)), 33 | OpGeneric(Opcode.OP_MAX, [SortInt(), SortInt()], SortInt(), lambda a, b: z3.If(a > b, a, b)), 34 | OpGeneric(Opcode.OP_WITHIN, [SortInt(), SortInt(), SortInt()], SortBool(), 35 | lambda x, lower, upper: z3.And(x >= lower, x < upper)), 36 | ] 37 | -------------------------------------------------------------------------------- /cashproof/op_impl/pushops.py: -------------------------------------------------------------------------------- 1 | import z3 2 | 3 | from cashproof.func import Funcs 4 | from cashproof.op import Op, OpVars, OpVarNames 5 | from cashproof.opcodes import Opcode 6 | from cashproof.sort import SortInt, SortString, SortBool 7 | from cashproof.stack import Stacks, VarNames 8 | from cashproof.statements import Statements 9 | 10 | 11 | class OpPushInt(Op): 12 | def __init__(self, opcode: Opcode, integer: int) -> None: 13 | self._opcode = opcode 14 | self._integer = integer 15 | 16 | def opcode(self) -> Opcode: 17 | return self._opcode 18 | 19 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 20 | return OpVarNames([], [stack.push(var_names.new(), SortInt())]) 21 | 22 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 23 | result, = op_vars.outputs 24 | statements.assume(result == self._integer) 25 | 26 | 27 | class OpPushString(Op): 28 | def __init__(self, opcode: Opcode, string: str) -> None: 29 | self._opcode = opcode 30 | self._string = string 31 | 32 | def opcode(self) -> Opcode: 33 | return self._opcode 34 | 35 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 36 | return OpVarNames([], [stack.push(var_names.new(), SortString())]) 37 | 38 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 39 | result, = op_vars.outputs 40 | statements.assume(result == z3.StringVal(self._string)) 41 | 42 | 43 | class OpPushBool(Op): 44 | def __init__(self, opcode: Opcode, boolean: bool) -> None: 45 | self._opcode = opcode 46 | self._boolean = boolean 47 | 48 | def opcode(self) -> Opcode: 49 | return self._opcode 50 | 51 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 52 | return OpVarNames([], [stack.push(var_names.new(), SortBool())]) 53 | 54 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 55 | result, = op_vars.outputs 56 | statements.assume(result == self._boolean) 57 | -------------------------------------------------------------------------------- /cashproof/op_impl/spliceops.py: -------------------------------------------------------------------------------- 1 | import z3 2 | 3 | from cashproof.func import Funcs 4 | from cashproof.op import Op, OpVarNames, OpVars 5 | from cashproof.opcodes import Opcode 6 | from cashproof.sort import SortString, SortInt 7 | from cashproof.stack import Stacks, VarNames 8 | from cashproof.statements import Statements 9 | 10 | 11 | class OpCat(Op): 12 | def opcode(self) -> Opcode: 13 | return Opcode.OP_CAT 14 | 15 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 16 | a = stack.pop(SortString()) 17 | b = stack.pop(SortString()) 18 | return OpVarNames([a, b], [stack.push(var_names.new(f'({a}+{b})'), SortString())]) 19 | 20 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 21 | a, b = op_vars.inputs 22 | result, = op_vars.outputs 23 | statements.assume(result == a + b) 24 | 25 | 26 | class OpSplit(Op): 27 | def opcode(self) -> Opcode: 28 | return Opcode.OP_SPLIT 29 | 30 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 31 | split_idx = stack.pop(SortInt()) 32 | string = stack.pop(SortString()) 33 | return OpVarNames( 34 | [split_idx, string], 35 | [stack.push(var_names.new(f'[{string},{split_idx}]_1'), SortString()), 36 | stack.push(var_names.new(f'[{string}_{split_idx}]_2'), SortString())], 37 | ) 38 | 39 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 40 | split_idx, string = op_vars.inputs 41 | result1, result2 = op_vars.outputs 42 | statements.assume(result1 == z3.SubString(string, 0, z3.BV2Int(split_idx))) 43 | statements.assume(result2 == z3.SubString(string, z3.BV2Int(split_idx), z3.Length(string))) 44 | 45 | 46 | def _add_num_encode_assumptions(statements: Statements, num, abs_num, string, last_byte, sign_bit): 47 | zero_bit = z3.BitVecVal(0, 1) 48 | 49 | statements.assume(abs_num == z3.If(num < 0, -num, num)) 50 | statements.assume(z3.If(z3.Length(string) > 0, 51 | z3.Unit(last_byte) == z3.SubString(string, z3.Length(string) - 1, 1), 52 | last_byte == 0)) 53 | statements.assume(z3.If(num < 0, sign_bit == 1, sign_bit == 0)) 54 | statements.assume(sign_bit == z3.Extract(7, 7, last_byte)) 55 | 56 | statements.assume(z3.Implies(z3.Length(string) == 0, num == 0)) 57 | statements.assume(z3.Implies(z3.Length(string) == 1, z3.And( 58 | string == z3.Unit(z3.Concat(sign_bit, z3.Extract(6, 0, abs_num))), 59 | abs_num <= 127, 60 | ))) 61 | statements.assume( 62 | z3.Implies(z3.Length(string) == 2, z3.And( 63 | string[0] == z3.Unit(z3.Extract(7, 0, abs_num)), 64 | string[1] == z3.Unit(z3.Concat(sign_bit, z3.Extract(14, 8, abs_num))), 65 | abs_num <= 32767, 66 | )) 67 | ) 68 | statements.assume( 69 | z3.Implies(z3.Length(string) == 3, z3.And( 70 | string[0] == z3.Unit(z3.Extract(7, 0, abs_num)), 71 | string[1] == z3.Unit(z3.Extract(15, 8, abs_num)), 72 | string[2] == z3.Unit(z3.Concat(sign_bit, z3.Extract(22, 16, abs_num))), 73 | abs_num <= 8388607, 74 | )) 75 | ) 76 | statements.assume( 77 | z3.Implies(z3.Length(string) == 4, z3.And( 78 | string[0] == z3.Unit(z3.Extract(7, 0, abs_num)), 79 | string[1] == z3.Unit(z3.Extract(15, 8, abs_num)), 80 | string[2] == z3.Unit(z3.Extract(23, 16, abs_num)), 81 | string[3] == z3.Unit(z3.Concat(sign_bit, z3.Extract(30, 24, abs_num))), 82 | abs_num <= 2147483647, 83 | )) 84 | ) 85 | statements.assume( 86 | z3.Implies(z3.Length(string) > 4, z3.And( 87 | string[0] == z3.Unit(z3.Extract(7, 0, abs_num)), 88 | string[1] == z3.Unit(z3.Extract(15, 8, abs_num)), 89 | string[2] == z3.Unit(z3.Extract(23, 16, abs_num)), 90 | string[3] == z3.Unit(z3.Concat(zero_bit, z3.Extract(30, 24, abs_num))), 91 | z3.InRe(z3.SubString(string, 4, z3.Length(string) - 5), z3.Star(z3.Re('\0'))), 92 | last_byte == z3.Concat(sign_bit, z3.BitVecVal(0, 7)), 93 | abs_num <= 2147483647, 94 | )) 95 | ) 96 | 97 | 98 | class OpNum2Bin(Op): 99 | def opcode(self) -> Opcode: 100 | return Opcode.OP_NUM2BIN 101 | 102 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 103 | size = stack.pop(SortInt()) 104 | num = stack.pop(SortInt()) 105 | return OpVarNames([num, size], [stack.push(var_names.new(f'num2bin_{num}_{size}'), SortString())]) 106 | 107 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 108 | num, size = op_vars.inputs 109 | string, = op_vars.outputs 110 | statements.assume(z3.Length(string) == z3.BV2Int(size)) 111 | 112 | abs_num = z3.Const(var_names.new(f'num2bin_abs_{num}_{size}'), SortInt().to_z3()) 113 | last_byte = z3.Const(var_names.new(f'num2bin_last_byte_{num}_{size}'), z3.BitVecSort(8)) 114 | sign_bit = z3.Const(var_names.new(f'num2bin_sign_{num}_{size}'), z3.BitVecSort(1)) 115 | _add_num_encode_assumptions(statements, num, abs_num, string, last_byte, sign_bit) 116 | 117 | 118 | class OpBin2Num(Op): 119 | def opcode(self) -> Opcode: 120 | return Opcode.OP_BIN2NUM 121 | 122 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 123 | string = stack.pop(SortString()) 124 | num = stack.push(var_names.new(f'bin2num_{string}'), SortInt()) 125 | return OpVarNames([string], [num]) 126 | 127 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 128 | string, = op_vars.inputs 129 | num, = op_vars.outputs 130 | abs_num = z3.Const(var_names.new(f'bin2num_abs_{string}'), SortInt().to_z3()) 131 | last_byte = z3.Const(var_names.new(f'bin2num_last_byte_{string}'), z3.BitVecSort(8)) 132 | sign_bit = z3.Const(var_names.new(f'bin2num_sign_{string}'), z3.BitVecSort(1)) 133 | _add_num_encode_assumptions(statements, num, abs_num, string, last_byte, sign_bit) 134 | 135 | 136 | class OpSize(Op): 137 | def opcode(self) -> Opcode: 138 | return Opcode.OP_SIZE 139 | 140 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 141 | string = stack.pop(SortString()) 142 | return OpVarNames([string], [stack.push(var_names.new(f'size_{string}'), SortInt())]) 143 | 144 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 145 | string, = op_vars.inputs 146 | size, = op_vars.outputs 147 | statements.assume(z3.Length(string) == z3.BV2Int(size)) 148 | 149 | 150 | SPLICE_OPS = [ 151 | OpCat(), 152 | OpSplit(), 153 | OpNum2Bin(), 154 | OpBin2Num(), 155 | OpSize(), 156 | ] 157 | -------------------------------------------------------------------------------- /cashproof/op_impl/stackops.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Sequence 3 | 4 | from cashproof.func import Funcs 5 | from cashproof.op import Op, OpVarNames, OpVars 6 | from cashproof.opcodes import Opcode 7 | from cashproof.sort import SortInt 8 | from cashproof.stack import Stacks, VarNames 9 | from cashproof.statements import Statements 10 | 11 | 12 | class StackOp(ABC): 13 | @abstractmethod 14 | def apply(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 15 | pass 16 | 17 | 18 | class StackOpMask(StackOp): 19 | def __init__(self, n_in: int, mask: Sequence[int]) -> None: 20 | self._n_in = n_in 21 | self._mask = mask 22 | 23 | def apply(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 24 | inputs = [] 25 | outputs = [] 26 | for _ in range(self._n_in): 27 | inputs.append(stack.pop(None)) 28 | for i in self._mask: 29 | outputs.append(inputs[i]) 30 | stack.push(inputs[i], None) 31 | return OpVarNames(list(reversed(inputs)), outputs) 32 | 33 | 34 | class StackOpToAltStack(StackOp): 35 | def apply(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 36 | top = stack.pop(None) 37 | stack.alt().push(top, None) 38 | return OpVarNames([top], []) 39 | 40 | 41 | class StackOpFromAltStack(StackOp): 42 | def apply(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 43 | return OpVarNames([], [stack.push(stack.alt().pop(None), None)]) 44 | 45 | 46 | class OpStackOp(Op): 47 | def __init__(self, opcode: Opcode, stack_op: StackOp) -> None: 48 | self._opcode = opcode 49 | self._stack_op = stack_op 50 | 51 | def opcode(self) -> Opcode: 52 | return self._opcode 53 | 54 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 55 | return self._stack_op.apply(stack, var_names) 56 | 57 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 58 | pass 59 | 60 | 61 | class OpPick(Op): 62 | def __init__(self, idx: int) -> None: 63 | self._idx = idx 64 | 65 | def opcode(self) -> Opcode: 66 | return Opcode.OP_PICK 67 | 68 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 69 | inputs = [stack.pop(None) for _ in range(self._idx + 1)] 70 | inputs_rev = list(reversed(inputs)) 71 | outputs = inputs_rev + [inputs_rev[0]] 72 | for output in outputs: 73 | stack.push(output, None) 74 | return OpVarNames(inputs_rev, outputs) 75 | 76 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 77 | pass 78 | 79 | def __repr__(self) -> str: 80 | return f'OpPick({self._idx})' 81 | 82 | 83 | class OpRoll(Op): 84 | def __init__(self, idx: int) -> None: 85 | self._idx = idx 86 | 87 | def opcode(self) -> Opcode: 88 | return Opcode.OP_ROLL 89 | 90 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 91 | inputs = [stack.pop(None) for _ in range(self._idx + 1)] 92 | inputs_rev = list(reversed(inputs)) 93 | outputs = inputs_rev[1:] + [inputs_rev[0]] 94 | for output in outputs: 95 | stack.push(output, None) 96 | return OpVarNames(inputs_rev, outputs) 97 | 98 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 99 | pass 100 | 101 | def __repr__(self) -> str: 102 | return f'OpRoll({self._idx})' 103 | 104 | 105 | class OpDepth(Op): 106 | def opcode(self) -> Opcode: 107 | return Opcode.OP_DEPTH 108 | 109 | def apply_stack(self, stack: Stacks, var_names: VarNames) -> OpVarNames: 110 | depth = stack.push(var_names.new(), SortInt()) 111 | return OpVarNames([], [depth], stack.depth()) 112 | 113 | def statements(self, statements: Statements, op_vars: OpVars, var_names: VarNames, funcs: Funcs) -> None: 114 | depth, = op_vars.outputs 115 | statements.assume(depth == op_vars.data) 116 | 117 | 118 | STACK_OPS = [ 119 | OpStackOp(Opcode.OP_DROP, StackOpMask(1, [])), 120 | OpStackOp(Opcode.OP_DUP, StackOpMask(1, [0, 0])), 121 | OpStackOp(Opcode.OP_NIP, StackOpMask(2, [0])), 122 | OpStackOp(Opcode.OP_OVER, StackOpMask(2, [1, 0, 1])), 123 | OpStackOp(Opcode.OP_ROT, StackOpMask(3, [1, 0, 2])), 124 | OpStackOp(Opcode.OP_SWAP, StackOpMask(2, [0, 1])), 125 | OpStackOp(Opcode.OP_TUCK, StackOpMask(2, [0, 1, 0])), 126 | OpStackOp(Opcode.OP_2DROP, StackOpMask(2, [])), 127 | OpStackOp(Opcode.OP_2DUP, StackOpMask(2, [1, 0, 1, 0])), 128 | OpStackOp(Opcode.OP_3DUP, StackOpMask(3, [2, 1, 0, 2, 1, 0])), 129 | OpStackOp(Opcode.OP_2OVER, StackOpMask(4, [3, 2, 1, 0, 3, 2])), 130 | OpStackOp(Opcode.OP_2ROT, StackOpMask(6, [3, 2, 1, 0, 5, 4])), 131 | OpStackOp(Opcode.OP_2SWAP, StackOpMask(4, [1, 0, 3, 2])), 132 | OpStackOp(Opcode.OP_TOALTSTACK, StackOpToAltStack()), 133 | OpStackOp(Opcode.OP_FROMALTSTACK, StackOpFromAltStack()), 134 | OpDepth(), 135 | ] 136 | -------------------------------------------------------------------------------- /cashproof/opcodes.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Opcode(Enum): 5 | OP_0 = 0x00 6 | OP_FALSE = OP_0 7 | # OP_PUSHDATA1 = 0x4c 8 | # OP_PUSHDATA2 = 0x4d 9 | # OP_PUSHDATA4 = 0x4e 10 | OP_1NEGATE = 0x4f 11 | OP_RESERVED = 0x50 12 | OP_1 = 0x51 13 | OP_TRUE = OP_1 14 | OP_2 = 0x52 15 | OP_3 = 0x53 16 | OP_4 = 0x54 17 | OP_5 = 0x55 18 | OP_6 = 0x56 19 | OP_7 = 0x57 20 | OP_8 = 0x58 21 | OP_9 = 0x59 22 | OP_10 = 0x5a 23 | OP_11 = 0x5b 24 | OP_12 = 0x5c 25 | OP_13 = 0x5d 26 | OP_14 = 0x5e 27 | OP_15 = 0x5f 28 | OP_16 = 0x60 29 | 30 | # control 31 | OP_NOP = 0x61 32 | OP_VER = 0x62 33 | # OP_IF = 0x63 34 | # OP_NOTIF = 0x64 35 | # OP_VERIF = 0x65 36 | # OP_VERNOTIF = 0x66 37 | # OP_ELSE = 0x67 38 | # OP_ENDIF = 0x68 39 | OP_VERIFY = 0x69 40 | OP_RETURN = 0x6a 41 | 42 | # stack ops 43 | OP_TOALTSTACK = 0x6b 44 | OP_FROMALTSTACK = 0x6c 45 | OP_2DROP = 0x6d 46 | OP_2DUP = 0x6e 47 | OP_3DUP = 0x6f 48 | OP_2OVER = 0x70 49 | OP_2ROT = 0x71 50 | OP_2SWAP = 0x72 51 | OP_IFDUP = 0x73 52 | OP_DEPTH = 0x74 53 | OP_DROP = 0x75 54 | OP_DUP = 0x76 55 | OP_NIP = 0x77 56 | OP_OVER = 0x78 57 | OP_PICK = 0x79 58 | OP_ROLL = 0x7a 59 | OP_ROT = 0x7b 60 | OP_SWAP = 0x7c 61 | OP_TUCK = 0x7d 62 | 63 | # splice ops 64 | OP_CAT = 0x7e 65 | OP_SPLIT = 0x7f # after monolith upgrade (May 2018) 66 | OP_NUM2BIN = 0x80 # after monolith upgrade (May 2018) 67 | OP_BIN2NUM = 0x81 # after monolith upgrade (May 2018) 68 | OP_SIZE = 0x82 69 | 70 | # bit logic 71 | OP_INVERT = 0x83 72 | OP_AND = 0x84 73 | OP_OR = 0x85 74 | OP_XOR = 0x86 75 | OP_EQUAL = 0x87 76 | OP_EQUALVERIFY = 0x88 77 | OP_RESERVED1 = 0x89 78 | OP_RESERVED2 = 0x8a 79 | 80 | # numeric 81 | OP_1ADD = 0x8b 82 | OP_1SUB = 0x8c 83 | OP_2MUL = 0x8d 84 | OP_2DIV = 0x8e 85 | OP_NEGATE = 0x8f 86 | OP_ABS = 0x90 87 | OP_NOT = 0x91 88 | OP_0NOTEQUAL = 0x92 89 | 90 | OP_ADD = 0x93 91 | OP_SUB = 0x94 92 | OP_MUL = 0x95 93 | OP_DIV = 0x96 94 | OP_MOD = 0x97 95 | OP_LSHIFT = 0x98 96 | OP_RSHIFT = 0x99 97 | 98 | OP_BOOLAND = 0x9a 99 | OP_BOOLOR = 0x9b 100 | OP_NUMEQUAL = 0x9c 101 | OP_NUMEQUALVERIFY = 0x9d 102 | OP_NUMNOTEQUAL = 0x9e 103 | OP_LESSTHAN = 0x9f 104 | OP_GREATERTHAN = 0xa0 105 | OP_LESSTHANOREQUAL = 0xa1 106 | OP_GREATERTHANOREQUAL = 0xa2 107 | OP_MIN = 0xa3 108 | OP_MAX = 0xa4 109 | 110 | OP_WITHIN = 0xa5 111 | 112 | # crypto 113 | OP_RIPEMD160 = 0xa6 114 | OP_SHA1 = 0xa7 115 | OP_SHA256 = 0xa8 116 | OP_HASH160 = 0xa9 117 | OP_HASH256 = 0xaa 118 | OP_CODESEPARATOR = 0xab 119 | OP_CHECKSIG = 0xac 120 | OP_CHECKSIGVERIFY = 0xad 121 | OP_CHECKMULTISIG = 0xae 122 | OP_CHECKMULTISIGVERIFY = 0xaf 123 | 124 | # expansion 125 | OP_NOP1 = 0xb0 126 | OP_CHECKLOCKTIMEVERIFY = 0xb1 127 | # OP_NOP2 = OP_CHECKLOCKTIMEVERIFY 128 | OP_CHECKSEQUENCEVERIFY = 0xb2 129 | # OP_NOP3 = OP_CHECKSEQUENCEVERIFY 130 | OP_NOP4 = 0xb3 131 | OP_NOP5 = 0xb4 132 | OP_NOP6 = 0xb5 133 | OP_NOP7 = 0xb6 134 | OP_NOP8 = 0xb7 135 | OP_NOP9 = 0xb8 136 | OP_NOP10 = 0xb9 137 | 138 | # More crypto 139 | OP_CHECKDATASIG = 0xba 140 | OP_CHECKDATASIGVERIFY = 0xbb 141 | 142 | # The first op_code value after all defined opcodes 143 | FIRST_UNDEFINED_OP_VALUE = OP_CHECKDATASIGVERIFY + 1 144 | 145 | # multi-byte opcodes 146 | OP_PREFIX_BEGIN = 0xf0 147 | OP_PREFIX_END = 0xf7 148 | 149 | # template matching params 150 | OP_SMALLINTEGER = 0xfa 151 | OP_PUBKEYS = 0xfb 152 | OP_PUBKEYHASH = 0xfd 153 | OP_PUBKEY = 0xfe 154 | 155 | OP_INVALIDOPCODE = 0xff 156 | -------------------------------------------------------------------------------- /cashproof/ops.py: -------------------------------------------------------------------------------- 1 | from ast import literal_eval 2 | from dataclasses import dataclass 3 | from typing import Sequence, Union, Tuple, Optional, TextIO 4 | from io import StringIO 5 | 6 | import z3 7 | 8 | from cashproof.func import Funcs, FuncsDefault 9 | from cashproof.op import Op, OpVars 10 | from cashproof.op_impl.assume import OpAssumeBool 11 | from cashproof.op_impl.bitlogicops import BIT_LOGIC_OPS 12 | from cashproof.op_impl.controlops import CONTROL_OPS 13 | from cashproof.op_impl.cryptoops import CRYPTO_OPS, OpCheckMultiSig 14 | from cashproof.op_impl.nop import NOPS 15 | from cashproof.op_impl.numericops import NUMERIC_OPS 16 | from cashproof.op_impl.pushops import OpPushInt, OpPushString, OpPushBool 17 | from cashproof.op_impl.spliceops import SPLICE_OPS 18 | from cashproof.op_impl.stackops import STACK_OPS, OpPick, OpRoll 19 | from cashproof.opcodes import Opcode 20 | from cashproof.sort import Sort, SortUnknown, SortBool 21 | from cashproof.stack import Stacks, StackStrict, VarNamesIdentity, VarNames, VarNamesPrefix 22 | from cashproof.statements import StatementsDefault, Statements 23 | 24 | OPS = { 25 | op.opcode(): op 26 | for op in STACK_OPS + SPLICE_OPS + CONTROL_OPS + BIT_LOGIC_OPS + NUMERIC_OPS + CRYPTO_OPS + NOPS 27 | } 28 | 29 | 30 | ScriptItem = Union[Opcode, int, str, bytes, bool, 'If', 'AssumeBool'] 31 | 32 | 33 | @dataclass 34 | class If: 35 | then: Sequence[ScriptItem] 36 | otherwise: Sequence[ScriptItem] 37 | 38 | 39 | @dataclass 40 | class AssumeBool: 41 | top: bool 42 | 43 | 44 | @dataclass 45 | class TransformedOps: 46 | conditions: Sequence[Tuple[str, bool]] 47 | expected_inputs: Sequence[z3.Ast] 48 | expected_input_names: Sequence[str] 49 | expected_input_sorts: Sequence[Sort] 50 | outputs: Sequence[z3.Ast] 51 | output_sorts: Sequence[Sort] 52 | 53 | 54 | def parse_int_op(opcode: Opcode) -> Optional[int]: 55 | if Opcode.OP_1NEGATE == opcode: 56 | return -1 57 | if Opcode.OP_0 == opcode: 58 | return 0 59 | if Opcode.OP_1.value <= opcode.value <= Opcode.OP_16.value: 60 | return opcode.value - Opcode.OP_1.value + 1 61 | return None 62 | 63 | 64 | def parse_script_item(script_item: ScriptItem, max_stackitem_size: int) -> Op: 65 | if isinstance(script_item, Opcode): 66 | parsed_int = parse_int_op(script_item) 67 | if parsed_int is not None: 68 | return OpPushInt(..., parsed_int) 69 | return OPS[script_item] 70 | elif isinstance(script_item, bool): 71 | return OpPushBool(script_item, script_item) 72 | elif isinstance(script_item, int): 73 | return OpPushInt(script_item, script_item) 74 | elif isinstance(script_item, (str, bytes)): 75 | if len(script_item) > max_stackitem_size: 76 | raise ValueError(f'Stack item exceeds limit: len({script_item}) > {max_stackitem_size}') 77 | return OpPushString(script_item, script_item) 78 | elif isinstance(script_item, AssumeBool): 79 | return OpAssumeBool(script_item.top) 80 | else: 81 | raise ValueError(f'Unknown script item: {script_item}') 82 | 83 | 84 | def parse_script(script: Sequence[ScriptItem], max_stackitem_size: int) -> Sequence[Op]: 85 | ops = [] 86 | prev_item: int = None 87 | for script_item in script: 88 | if script_item == Opcode.OP_PICK or script_item == Opcode.OP_ROLL: 89 | if isinstance(prev_item, Opcode): 90 | prev_item = parse_int_op(prev_item) 91 | assert isinstance(prev_item, int) 92 | ops.pop() 93 | ops.append(OpPick(prev_item) if script_item == Opcode.OP_PICK else OpRoll(prev_item)) 94 | elif script_item == Opcode.OP_CHECKMULTISIG or script_item == Opcode.OP_CHECKMULTISIGVERIFY: 95 | if isinstance(prev_item, Opcode): 96 | prev_item = parse_int_op(prev_item) 97 | assert isinstance(prev_item, int) 98 | ops.pop() 99 | ops.append(OpCheckMultiSig(prev_item, script_item, script_item == Opcode.OP_CHECKMULTISIGVERIFY)) 100 | else: 101 | ops.append(parse_script_item(script_item, max_stackitem_size)) 102 | prev_item = script_item 103 | return ops 104 | 105 | 106 | def pretty_print_script(script: Sequence) -> str: 107 | strs = [] 108 | for item in script: 109 | if isinstance(item, Opcode): 110 | strs.append(item.name) 111 | elif isinstance(item, (int, z3.BitVecNumRef)): 112 | strs.append(str(item)) 113 | elif isinstance(item, (bool, z3.BoolRef)): 114 | item_str = str(item) 115 | if item_str == 'True': 116 | strs.append('OP_TRUE') 117 | else: 118 | strs.append('OP_FALSE') 119 | elif isinstance(item, str): 120 | if item.isprintable(): 121 | strs.append(repr(item)) 122 | else: 123 | strs.append(f'0x{item.encode().hex()}') 124 | elif isinstance(item, (bytes, z3.SeqRef)): 125 | if isinstance(item, z3.SeqRef): 126 | seq_str = item.as_string().replace("'", r"\'") 127 | item = literal_eval(f"b'{seq_str}'") 128 | try: 129 | s = item.decode() 130 | except: 131 | pass 132 | else: 133 | if s.isprintable(): 134 | strs.append(repr(s)) 135 | continue 136 | strs.append(f'0x{item.hex()}') 137 | elif isinstance(item, If): 138 | strs.append('OP_IF') 139 | strs.append(pretty_print_script(item.then)) 140 | strs.append('OP_ELSE') 141 | strs.append(pretty_print_script(item.otherwise)) 142 | strs.append('OP_ENDIF') 143 | elif isinstance(item, AssumeBool): 144 | strs.append(f'') 145 | else: 146 | strs.append(str(item)) 147 | return ' '.join(strs) 148 | 149 | 150 | def clean_nop(t: TransformedOps) -> TransformedOps: 151 | n_nops = 0 152 | for expected_input, output in zip(reversed(t.expected_inputs), t.outputs): 153 | if expected_input is not output: 154 | break 155 | n_nops += 1 156 | input_slice = slice(None) if not n_nops else slice(None, -n_nops) 157 | return TransformedOps( 158 | conditions=t.conditions, 159 | expected_inputs=t.expected_inputs[input_slice], 160 | expected_input_names=t.expected_input_names[input_slice], 161 | expected_input_sorts=t.expected_input_sorts[input_slice], 162 | outputs=t.outputs[n_nops:], 163 | output_sorts=t.output_sorts[n_nops:], 164 | ) 165 | 166 | 167 | def reconcile_inout(t1: TransformedOps, t2: TransformedOps) -> Tuple[TransformedOps, TransformedOps]: 168 | if len(t1.outputs) - len(t2.outputs) == len(t1.expected_inputs) - len(t2.expected_inputs): 169 | if len(t1.outputs) > len(t2.outputs): 170 | n = len(t1.outputs) - len(t2.outputs) 171 | return t1, TransformedOps( 172 | conditions=t2.conditions, 173 | expected_inputs=list(t1.expected_inputs[:n]) + list(t2.expected_inputs), 174 | expected_input_names=list(t1.expected_input_names[:n]) + list(t2.expected_input_names), 175 | expected_input_sorts=list(t1.expected_input_sorts[:n]) + list(t2.expected_input_sorts), 176 | outputs=list(t2.outputs) + list(t1.expected_inputs[-n:]), 177 | output_sorts=list(t2.output_sorts) + list(t1.expected_input_sorts[-n:]), 178 | ) 179 | if len(t2.outputs) > len(t1.outputs): 180 | n = len(t2.outputs) - len(t1.outputs) 181 | return TransformedOps( 182 | conditions=t1.conditions, 183 | expected_inputs=list(t2.expected_inputs[:n]) + list(t1.expected_inputs), 184 | expected_input_names=list(t2.expected_input_names[:n]) + list(t1.expected_input_names), 185 | expected_input_sorts=list(t2.expected_input_sorts[:n]) + list(t1.expected_input_sorts), 186 | outputs=list(t1.outputs) + list(t2.expected_inputs[-n:]), 187 | output_sorts=list(t1.output_sorts) + list(t2.expected_input_sorts[-n:]), 188 | ), t2 189 | return t1, t2 190 | 191 | 192 | def check_sorts(msg: str, sorts1: Sequence[Sort], sorts2: Sequence[Sort], 193 | opcodes1: Sequence[ScriptItem], opcodes2: Sequence[ScriptItem]): 194 | s = StringIO() 195 | if any(s1 != s2 and s1 != SortUnknown() and s2 != SortUnknown() 196 | for s1, s2 in zip(sorts1, sorts2)): 197 | print('Equivalence is FALSE.', msg, file=s) 198 | print('Left script:', file=s) 199 | print(end='A: ', file=s) 200 | print(pretty_print_script(opcodes1), file=s) 201 | print('Right script:', file=s) 202 | print(end='B: ', file=s) 203 | print(pretty_print_script(opcodes2), file=s) 204 | print(file=s) 205 | print(' | Left | Right', file=s) 206 | print('----------------+-----------', file=s) 207 | for i, (s1, s2) in enumerate(zip(sorts1, sorts2)): 208 | if s1 != s2 and s1 != SortUnknown() and s2 != SortUnknown(): 209 | print(end='=> ', file=s) 210 | else: 211 | print(end=' ', file=s) 212 | print('{} |{:^10}|{:^10}'.format(i, s1.to_c(), s2.to_c()), file=s) 213 | return s.getvalue() 214 | return None 215 | 216 | 217 | def print_verify_state(s: TextIO, opcodes: Sequence[Op], statements: Statements, model: z3.ModelRef): 218 | verify_map = { 219 | idx: model.get_interp(var) 220 | for idx, var in zip(statements.verify_opcode_indices(), statements.verify_statements()) 221 | } 222 | for idx, opcode in enumerate(opcodes): 223 | if idx in verify_map: 224 | print(end=f'<{pretty_print_script([opcode.opcode()])}={verify_map[idx]}> ', file=s) 225 | else: 226 | print(pretty_print_script([opcode.opcode()]), end=' ', file=s) 227 | print(file=s) 228 | 229 | 230 | def prove_equivalence_single(opcodes1: Sequence[ScriptItem], opcodes2: Sequence[ScriptItem], 231 | max_stackitem_size, verify=True, full_script: bool=False) -> Optional[str]: 232 | input_vars = VarNamesIdentity() 233 | funcs = FuncsDefault() 234 | statements1 = StatementsDefault(max_stackitem_size) 235 | statements2 = StatementsDefault(max_stackitem_size) 236 | 237 | ops1 = parse_script(opcodes1, max_stackitem_size) 238 | ops2 = parse_script(opcodes2, max_stackitem_size) 239 | 240 | t1 = transform_ops(ops1, 241 | statements1, input_vars, VarNamesPrefix('a_', input_vars), funcs, full_script) 242 | t2 = transform_ops(ops2, 243 | statements2, input_vars, VarNamesPrefix('b_', input_vars), funcs, full_script) 244 | 245 | if not full_script: 246 | t1 = clean_nop(t1) 247 | t2 = clean_nop(t2) 248 | t1, t2 = reconcile_inout(t1, t2) 249 | 250 | s = StringIO() 251 | 252 | assumptions = z3.And( 253 | *list(statements1.assumed_statements()) + list(statements2.assumed_statements()) + list(funcs.statements()) 254 | ) 255 | 256 | if full_script: 257 | if len(t1.outputs) == 0: # enforce cleanstack 258 | solver = z3.Solver() 259 | claim = z3.Implies(assumptions, z3.Not(z3.And(*statements1.verify_statements()))) 260 | solver.add(z3.Not(claim)) 261 | unspendable1 = solver.check() == z3.unsat 262 | else: 263 | unspendable1 = True 264 | if len(t2.outputs) == 0: 265 | solver = z3.Solver() 266 | claim = z3.Implies(assumptions, z3.Not(z3.And(*statements2.verify_statements()))) 267 | solver.add(z3.Not(claim)) 268 | unspendable2 = solver.check() == z3.unsat 269 | else: 270 | unspendable2 = True 271 | if unspendable1 and unspendable2: 272 | return None 273 | 274 | if len(t1.outputs) != len(t2.outputs): 275 | print('Equivalence is FALSE. The two scripts produce a different number of outputs.', file=s) 276 | print('Left script:', file=s) 277 | print(end='A: ', file=s) 278 | print(pretty_print_script(opcodes1), file=s) 279 | print('Right script:', file=s) 280 | print(end='B: ', file=s) 281 | print(pretty_print_script(opcodes2), file=s) 282 | print(f'The left script produces {len(t1.outputs)} outputs, ' 283 | f'while the right script produces {len(t2.outputs)}', file=s) 284 | return s.getvalue() 285 | if len(t1.expected_input_names) != len(t2.expected_input_names): 286 | print('Equivalence is FALSE. The two scripts take a different number of inputs.', file=s) 287 | print('Left script:', file=s) 288 | print(end='A: ', file=s) 289 | print(pretty_print_script(opcodes1), file=s) 290 | print('Right script:', file=s) 291 | print(end='B: ', file=s) 292 | print(pretty_print_script(opcodes2), file=s) 293 | print(f'The left script takes {len(t1.expected_input_names)} inputs, ' 294 | f'while the right script takes {len(t2.expected_input_names)}', file=s) 295 | return s.getvalue() 296 | result_sorts = check_sorts('The two scripts take different datatypes as inputs.', 297 | t1.expected_input_sorts, t2.expected_input_sorts, opcodes1, opcodes2) 298 | if result_sorts is not None: 299 | return result_sorts 300 | result_sorts = check_sorts('The two script produce different datatypes as outputs.', 301 | t1.output_sorts, t2.output_sorts, opcodes1, opcodes2) 302 | if result_sorts is not None: 303 | return result_sorts 304 | 305 | problem = z3.And(*[a == b for a, b in zip(t1.outputs, t2.outputs)]) 306 | if verify: 307 | problem = z3.And( 308 | problem, 309 | z3.And(*statements1.verify_statements()) == z3.And(*statements2.verify_statements()), 310 | ) 311 | claim = z3.Implies(assumptions, problem) 312 | 313 | solver = z3.Solver() 314 | solver.add(z3.Not(claim)) 315 | r = solver.check() 316 | if r == z3.unsat: 317 | return None 318 | needs_verbose = False 319 | if r == z3.unknown: 320 | print('Equivalence is UNKNOWN.', file=s) 321 | print('Z3 is unable to determine whether the scripts are equivalent.', file=s) 322 | else: 323 | print('Equivalence is FALSE, and CashProof can prove it mathematically.', file=s) 324 | print('Left script:', file=s) 325 | print(end='A: ', file=s) 326 | print(pretty_print_script(opcodes1), file=s) 327 | print('Right script:', file=s) 328 | print(end='B: ', file=s) 329 | print(pretty_print_script(opcodes2), file=s) 330 | print('CashProof found a COUNTEREXAMPLE:', file=s) 331 | print('Consider the following inputs:', file=s) 332 | print(end='I: ', file=s) 333 | print(pretty_print_script([solver.model().get_interp(input_var) for input_var in t1.expected_inputs]), file=s) 334 | output1 = pretty_print_script([solver.model().get_interp(output_var) for output_var in t1.outputs]) 335 | output2 = pretty_print_script([solver.model().get_interp(output_var) for output_var in t2.outputs]) 336 | if output1 == output2: 337 | print('While the scripts produce the same output:', file=s) 338 | else: 339 | print('The two scripts produce different outputs:', file=s) 340 | print(end='A(I) = ', file=s) 341 | print(output1, file=s) 342 | print(end='B(I) = ', file=s) 343 | print(output2, file=s) 344 | 345 | if output1 == output2: 346 | print('Other invariants, such as OP_VERIFY differ.', file=s) 347 | print('Invariants can be introduced by OP_VERIFY, OP_EQUALVERIFY, OP_NUMEQUALVERIFY, OP_CHECKSIGVERIFY, \n' 348 | 'OP_CHECKMULTISIGVERIFY, OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY and OP_CHECKDATASIGVERIFY.', 349 | file=s) 350 | print('Script A:', file=s) 351 | print_verify_state(s, ops1, statements1, solver.model()) 352 | print('Script B:', file=s) 353 | print_verify_state(s, ops2, statements2, solver.model()) 354 | if needs_verbose: 355 | print('-'*20, file=s) 356 | print('model:\n', solver.model(), file=s) 357 | if needs_verbose: 358 | print('-'*20, file=s) 359 | print('assumptions:\n', assumptions, file=s) 360 | print('-'*20, file=s) 361 | print('problem:\n', problem, file=s) 362 | print('-'*20, file=s) 363 | print('sorts:', file=s) 364 | for input_name, input_sort in zip(t1.expected_input_names, t1.expected_input_sorts): 365 | print(input_name, ' : ', input_sort, file=s) 366 | return s.getvalue() 367 | 368 | 369 | def transform_ops(ops: Sequence[Op], statements: Statements, input_vars: VarNames, stack_vars: VarNames, funcs: Funcs, 370 | full_script: bool): 371 | stack = StackStrict(input_vars) 372 | altstack = StackStrict(input_vars) 373 | stacks = Stacks(stack, altstack) 374 | op_var_names_list = [] 375 | for op in ops: 376 | op_var_names_list.append( 377 | op.apply_stack(stacks, stack_vars) 378 | ) 379 | sorts = stack.solve_all() 380 | vars_z3 = {} 381 | unknown = SortUnknown() 382 | for op_var_names in op_var_names_list: 383 | for var_name in list(op_var_names.inputs) + list(op_var_names.outputs): 384 | if var_name not in vars_z3: 385 | vars_z3[var_name] = z3.Const(var_name, sorts.get(var_name, unknown).to_z3()) 386 | op_vars_list = [] 387 | for op_var_names in op_var_names_list: 388 | op_vars_list.append( 389 | OpVars( 390 | [vars_z3[input_name] for input_name in op_var_names.inputs], 391 | [vars_z3[output_name] for output_name in op_var_names.outputs], 392 | op_var_names.data, 393 | ) 394 | ) 395 | for op, op_vars in zip(ops, op_vars_list): 396 | op.statements(statements, op_vars, stack_vars, funcs) 397 | statements.next_opcode() 398 | if full_script: 399 | if stacks.depth() == 0: 400 | raise ValueError('No final stack item on stack') 401 | statements.verify(z3.Const(stacks.pop(SortBool()), SortBool().to_z3())) 402 | 403 | return TransformedOps( 404 | conditions=[], 405 | expected_inputs=[vars_z3[var] for var in stack.input_var_names()], 406 | expected_input_names=stack.input_var_names(), 407 | expected_input_sorts=[sorts.get(var, unknown) for var in stack.input_var_names()], 408 | outputs=[vars_z3[var] for var in stack.output_var_names()], 409 | output_sorts=[sorts.get(var, unknown) for var in stack.output_var_names()], 410 | ) 411 | -------------------------------------------------------------------------------- /cashproof/parse_equiv_file.py: -------------------------------------------------------------------------------- 1 | from ast import literal_eval 2 | from dataclasses import dataclass 3 | from typing import Sequence 4 | 5 | from cashproof.opcodes import Opcode 6 | import re 7 | 8 | from cashproof.ops import If 9 | 10 | reg_num = re.compile('^-?\d+$') 11 | 12 | 13 | @dataclass 14 | class Equivalence: 15 | inverted: bool 16 | sides: list 17 | max_stackitem_size: int 18 | full_script: bool 19 | 20 | 21 | def parse_ops(raw_ops: Sequence[str], start: int, depth=0): 22 | ops = [] 23 | i = start - 1 24 | while i + 1 < len(raw_ops): 25 | i += 1 26 | op = raw_ops[i] 27 | op = op.strip() 28 | if not op: 29 | continue 30 | if reg_num.match(op) is not None: 31 | ops.append(int(op)) 32 | elif op == 'OP_IF' or op == 'OP_NOTIF': 33 | then, stop = parse_ops(raw_ops, i + 1, depth + 1) 34 | otherwise, stop = parse_ops(raw_ops, stop + 1, depth + 1) 35 | if op == 'OP_IF': 36 | ops.append(If(then, otherwise)) 37 | else: 38 | ops.append(If(otherwise, then)) 39 | if stop is None: 40 | raise ValueError() 41 | i = stop 42 | elif op == 'OP_ELSE' or op == 'OP_ENDIF': 43 | return ops, i 44 | elif op.startswith('0x'): 45 | ops.append(bytes.fromhex(op[2:])) 46 | elif op[:1] == '"' and op[-1:] == '"': 47 | ops.append(literal_eval(op)) 48 | elif op[:1] == "'" and op[-1:] == "'": 49 | ops.append(literal_eval(op)) 50 | elif op == 'OP_TRUE': 51 | ops.append(True) 52 | elif op == 'OP_FALSE': 53 | ops.append(False) 54 | else: 55 | ops.append(Opcode[op]) 56 | return ops, None 57 | 58 | 59 | def parse_equiv(src: str): 60 | src = ' '.join(line for line in src.splitlines() if not line.strip().startswith('#')) 61 | 62 | equivalences = src.split(';') 63 | parsed_equivalences = [] 64 | max_stackitem_size = 520 65 | full_script = False 66 | for equivalence in equivalences: 67 | equivalence = equivalence.strip() 68 | if not equivalence: 69 | continue 70 | if equivalence.startswith('!'): 71 | if equivalence.startswith('!max_stackitem_size='): 72 | max_stackitem_size = int(equivalence[len('!max_stackitem_size='):]) 73 | continue 74 | if equivalence.startswith('!full_script='): 75 | full_script = literal_eval(equivalence[len('!full_script='):]) 76 | continue 77 | if '<=>' in equivalence: 78 | sides = equivalence.split('<=>') 79 | inverted = False 80 | elif '' in equivalence: 81 | sides = equivalence.split('') 82 | inverted = True 83 | else: 84 | print('invalid equivalence:', equivalence) 85 | quit() 86 | return 87 | sides = [[op for op in side.split()] for side in sides] 88 | parsed_sides = [] 89 | for side in sides: 90 | ops, _ = parse_ops(side, 0) 91 | parsed_sides.append(ops) 92 | if parsed_sides: 93 | parsed_equivalences.append(Equivalence(inverted=inverted, 94 | sides=parsed_sides, 95 | max_stackitem_size=max_stackitem_size, 96 | full_script=full_script)) 97 | return parsed_equivalences 98 | -------------------------------------------------------------------------------- /cashproof/sort.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | import z3 4 | 5 | 6 | class Sort(ABC): 7 | @abstractmethod 8 | def to_z3(self) -> z3.SortRef: 9 | pass 10 | 11 | @abstractmethod 12 | def to_c(self) -> str: 13 | pass 14 | 15 | 16 | class _TypeEq: 17 | def __eq__(self, other): 18 | return type(self) == type(other) 19 | 20 | def __repr__(self) -> str: 21 | return type(self).__name__ + '()' 22 | 23 | 24 | class SortInt(Sort, _TypeEq): 25 | def to_z3(self) -> z3.SortRef: 26 | return z3.BitVecSort(32) 27 | 28 | def to_c(self) -> str: 29 | return 'int' 30 | 31 | 32 | class SortBool(Sort, _TypeEq): 33 | def to_z3(self) -> z3.SortRef: 34 | return z3.BoolSort() 35 | 36 | def to_c(self) -> str: 37 | return 'bool' 38 | 39 | 40 | class SortString(Sort, _TypeEq): 41 | def to_z3(self) -> z3.SortRef: 42 | return z3.StringSort() 43 | 44 | def to_c(self) -> str: 45 | return 'string' 46 | 47 | 48 | class SortUnknown(Sort, _TypeEq): 49 | def to_z3(self) -> z3.SortRef: 50 | return z3.DeclareSort('unknown') 51 | 52 | def to_c(self) -> str: 53 | return 'any' 54 | -------------------------------------------------------------------------------- /cashproof/stack.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Optional, Dict, Set 3 | 4 | from cashproof.sort import Sort 5 | 6 | 7 | class VarNames(ABC): 8 | @abstractmethod 9 | def new(self, name: str=None) -> str: 10 | pass 11 | 12 | @abstractmethod 13 | def glob(self, name: str) -> str: 14 | pass 15 | 16 | 17 | class Stack(ABC): 18 | @abstractmethod 19 | def pop(self, expected_sort: Optional[Sort]) -> str: 20 | pass 21 | 22 | @abstractmethod 23 | def push(self, var_name: str, sort: Optional[Sort]) -> str: 24 | pass 25 | 26 | @abstractmethod 27 | def depth(self) -> int: 28 | pass 29 | 30 | @abstractmethod 31 | def add_sort_equality(self, a: str, b: str) -> None: 32 | pass 33 | 34 | @abstractmethod 35 | def copy(self) -> 'Stack': 36 | pass 37 | 38 | @abstractmethod 39 | def solve_all(self) -> Dict[str, Sort]: 40 | pass 41 | 42 | @abstractmethod 43 | def input_var_names(self) -> List[str]: 44 | pass 45 | 46 | @abstractmethod 47 | def output_var_names(self) -> List[str]: 48 | pass 49 | 50 | 51 | class Stacks(Stack): 52 | def __init__(self, stack: Stack, alt_stack: Stack) -> None: 53 | self._stack = stack 54 | self._alt_stack = alt_stack 55 | 56 | def pop(self, expected_sort: Optional[Sort]) -> str: 57 | return self._stack.pop(expected_sort) 58 | 59 | def push(self, var_name: str, sort: Optional[Sort]) -> str: 60 | return self._stack.push(var_name, sort) 61 | 62 | def depth(self) -> int: 63 | return self._stack.depth() 64 | 65 | def stack(self) -> Stack: 66 | return self._stack 67 | 68 | def alt(self) -> Stack: 69 | return self._alt_stack 70 | 71 | def add_sort_equality(self, a: str, b: str) -> None: 72 | return self._stack.add_sort_equality(a, b) 73 | 74 | def copy(self) -> 'Stacks': 75 | return Stacks(self._stack.copy(), self._alt_stack.copy()) 76 | 77 | def solve_all(self) -> Dict[str, Sort]: 78 | return self._stack.solve_all() 79 | 80 | def input_var_names(self) -> List[str]: 81 | return self._stack.input_var_names() 82 | 83 | def output_var_names(self) -> List[str]: 84 | return self._stack.output_var_names() 85 | 86 | 87 | class StackStrict(Stack): 88 | def __init__(self, input_var_gen: VarNames) -> None: 89 | self._var_sorts: Dict[str, Sort] = {} 90 | self._equalities: Dict[str, Set[str]] = {} 91 | self._var_names: List[str] = [] 92 | self._input_var_names: List[str] = [] 93 | self._input_var_gen = input_var_gen 94 | 95 | def pop(self, expected_sort: Optional[Sort]) -> str: 96 | if not self._var_names: 97 | name = self._input_var_gen.new(f'input_{len(self._input_var_names)}') 98 | if expected_sort is not None: 99 | self._set_sort(name, expected_sort) 100 | self._input_var_names.append(name) 101 | return name 102 | else: 103 | top = self._var_names.pop() 104 | if expected_sort is not None: 105 | self._set_sort(top, expected_sort) 106 | return top 107 | 108 | def _set_sort(self, var_name: str, sort: Sort): 109 | if var_name in self._var_sorts and self._var_sorts[var_name] != sort: 110 | raise ValueError(f'Inconsistent sorts for {var_name}: {self._var_sorts[var_name]} != {sort}') 111 | self._var_sorts[var_name] = sort 112 | 113 | def add_sort_equality(self, a: str, b: str) -> None: 114 | self._equalities.setdefault(a, set()).add(b) 115 | self._equalities.setdefault(b, set()).add(a) 116 | 117 | def solve_all(self) -> Dict[str, Sort]: 118 | while True: 119 | intersection = self._equalities.keys() & self._var_sorts.keys() 120 | if not intersection: 121 | return self._var_sorts 122 | for a in intersection: 123 | equals = self._equalities[a] 124 | sort = self._var_sorts[a] 125 | for b in equals: 126 | self._set_sort(b, sort) 127 | del self._equalities[a] 128 | 129 | def push(self, var_name: str, sort: Optional[Sort]) -> str: 130 | self._var_names.append(var_name) 131 | if sort is not None: 132 | self._set_sort(var_name, sort) 133 | return var_name 134 | 135 | def depth(self) -> int: 136 | return len(self._var_names) 137 | 138 | def input_var_names(self) -> List[str]: 139 | return self._input_var_names 140 | 141 | def output_var_names(self) -> List[str]: 142 | return self._var_names 143 | 144 | def copy(self) -> 'Stack': 145 | new = StackStrict(self._input_var_gen) 146 | new._var_sorts = self._var_sorts.copy() 147 | new._equalities = {k: v.copy() for k, v in self._equalities.items()} 148 | new._var_names = self._var_names.copy() 149 | new._input_var_names = self._input_var_names.copy() 150 | return new 151 | 152 | 153 | class VarNamesIdentity(VarNames): 154 | counter = 0 155 | 156 | def new(self, name: str = None) -> str: 157 | if name is not None: 158 | return name 159 | else: 160 | VarNamesIdentity.counter += 1 161 | return f'v{VarNamesIdentity.counter}' 162 | 163 | def glob(self, name: str) -> str: 164 | return name 165 | 166 | 167 | class VarNamesPrefix(VarNames): 168 | def __init__(self, prefix: str, var_names: VarNames) -> None: 169 | self._prefix = prefix 170 | self._var_names = var_names 171 | 172 | def new(self, name: str = None) -> str: 173 | name = self._var_names.new(name) 174 | return f'{self._prefix}{name}' 175 | 176 | def glob(self, name: str) -> str: 177 | return self._var_names.glob(name) 178 | 179 | 180 | def test_stack(): 181 | from cashproof.sort import SortInt 182 | stack = StackStrict(VarNamesIdentity()) 183 | a = stack.pop(None) 184 | b = stack.pop(None) 185 | c = stack.pop(None) 186 | d = stack.pop(None) 187 | stack.push(d, SortInt()) 188 | stack.add_sort_equality(a, b) 189 | stack.add_sort_equality(b, c) 190 | stack.add_sort_equality(c, d) 191 | stack.solve_all() 192 | print(stack._var_sorts) 193 | 194 | 195 | if __name__ == '__main__': 196 | import sys 197 | sys.path.insert(0, '..') 198 | test_stack() 199 | -------------------------------------------------------------------------------- /cashproof/statements.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from dataclasses import dataclass 3 | from typing import Sequence, Union, List, Optional 4 | 5 | import z3 6 | 7 | Ast = Union[z3.Ast, bool] 8 | 9 | 10 | class Statements(ABC): 11 | @abstractmethod 12 | def max_stackitem_size(self) -> int: 13 | pass 14 | 15 | @abstractmethod 16 | def assume(self, ast: Ast) -> 'Statements': 17 | pass 18 | 19 | @abstractmethod 20 | def verify(self, ast: Ast) -> 'Statements': 21 | pass 22 | 23 | @abstractmethod 24 | def assumed_statements(self) -> Sequence[Ast]: 25 | pass 26 | 27 | @abstractmethod 28 | def verify_statements(self) -> Sequence[Ast]: 29 | pass 30 | 31 | @abstractmethod 32 | def verify_opcode_indices(self) -> Sequence[int]: 33 | pass 34 | 35 | @abstractmethod 36 | def next_opcode(self) -> None: 37 | pass 38 | 39 | 40 | @dataclass 41 | class Stmts: 42 | statements: List[Ast] 43 | verify_statements: List[Ast] 44 | verify_opcode_indices: List[int] 45 | 46 | 47 | @dataclass 48 | class StmtIf: 49 | condition: Ast 50 | then: Stmts 51 | otherwise: Optional[Stmts] 52 | 53 | 54 | class StatementsDefault(Statements): 55 | def __init__(self, max_stackitem_size) -> None: 56 | self._max_stackitem_size = max_stackitem_size 57 | self._current_stmts: Stmts = Stmts([], [], []) 58 | self._opcode_index = 0 59 | 60 | def max_stackitem_size(self) -> int: 61 | return self._max_stackitem_size 62 | 63 | def assume(self, ast: Ast) -> 'Statements': 64 | self._current_stmts.statements.append(ast) 65 | return self 66 | 67 | def verify(self, ast: Ast) -> 'Statements': 68 | self._current_stmts.verify_statements.append(ast) 69 | self._current_stmts.verify_opcode_indices.append(self._opcode_index) 70 | return self 71 | 72 | def assumed_statements(self) -> Sequence[Ast]: 73 | return list(self._current_stmts.statements) 74 | 75 | def verify_statements(self) -> Sequence[Ast]: 76 | return list(self._current_stmts.verify_statements) 77 | 78 | def next_opcode(self) -> None: 79 | self._opcode_index += 1 80 | 81 | def verify_opcode_indices(self) -> Sequence[int]: 82 | return list(self._current_stmts.verify_opcode_indices) 83 | -------------------------------------------------------------------------------- /examples/bitlogic.equiv: -------------------------------------------------------------------------------- 1 | !max_stackitem_size=8; 2 | OP_AND OP_AND <=> OP_TOALTSTACK OP_AND OP_FROMALTSTACK OP_AND; 3 | OP_OR OP_OR <=> OP_TOALTSTACK OP_OR OP_FROMALTSTACK OP_OR; 4 | OP_OR <=> OP_SWAP OP_OR; 5 | OP_DUP OP_AND <=> ; 6 | !max_stackitem_size=7; 7 | OP_DUP OP_XOR OP_XOR <=> OP_DROP; 8 | 9 | !max_stackitem_size=3; 10 | 11 | 0x03 0x02 OP_AND <=> 0x02; 12 | 0x03 0x02 OP_AND 0x03; 13 | 0x0303 0x0202 OP_AND <=> 0x0202; 14 | 0x0303 0x0202 OP_AND 0x0303; 15 | 0x030303 0x020202 OP_AND <=> 0x020202; 16 | 0x030303 0x020202 OP_AND 0x030303; 17 | 18 | !max_stackitem_size=4; 19 | 0x03030303 0x02020202 OP_AND <=> 0x02020202; 20 | !max_stackitem_size=3; 21 | 22 | 0x05 0x03 OP_OR <=> 0x07; 23 | 0x05 0x03 OP_OR 0x08; 24 | 0x0505 0x0303 OP_OR <=> 0x0707; 25 | 0x0505 0x0303 OP_OR 0x0808; 26 | 0x050505 0x030303 OP_OR <=> 0x070707; 27 | 0x050505 0x030303 OP_OR 0x080807; 28 | 29 | 0x05 0x03 OP_XOR <=> 0x06; 30 | 0x05 0x03 OP_XOR 0x08; 31 | 0x0505 0x0303 OP_XOR <=> 0x0606; 32 | 0x0505 0x0303 OP_XOR 0x0808; 33 | 0x050505 0x030303 OP_XOR <=> 0x060606; 34 | 0x050505 0x030303 OP_XOR 0x080808; 35 | -------------------------------------------------------------------------------- /examples/cashscript.equiv: -------------------------------------------------------------------------------- 1 | # OP_NOT OP_IF <=> OP_NOTIF; 2 | 1 OP_ADD <=> OP_1ADD; 3 | 1 OP_SUB <=> OP_1SUB; 4 | 0 OP_EQUAL OP_NOT <=> OP_0NOTEQUAL; 5 | OP_NUMEQUAL OP_NOT <=> OP_NUMNOTEQUAL; 6 | OP_SHA256 OP_SHA256 <=> OP_HASH256; 7 | OP_SHA256 OP_RIPEMD160 <=> OP_HASH160; 8 | 2 OP_PICK 2 OP_PICK 2 OP_PICK <=> OP_3DUP; 9 | 2 OP_PICK 1 OP_PICK 3 OP_PICK <=> OP_3DUP OP_SWAP; 10 | 1 OP_PICK 1 OP_PICK <=> OP_2DUP; 11 | 0 OP_PICK 2 OP_PICK <=> OP_2DUP OP_SWAP; 12 | 3 OP_PICK 3 OP_PICK <=> OP_2OVER; 13 | 2 OP_PICK 4 OP_PICK <=> OP_2OVER OP_SWAP; 14 | 2 OP_ROLL 2 OP_ROLL <=> OP_ROT OP_ROT; 15 | 3 OP_ROLL 3 OP_ROLL <=> OP_2SWAP; 16 | 2 OP_ROLL 3 OP_ROLL <=> OP_2SWAP OP_SWAP; 17 | 5 OP_ROLL 5 OP_ROLL <=> OP_2ROT; 18 | 4 OP_ROLL 5 OP_ROLL <=> OP_2ROT OP_SWAP; 19 | 0 OP_PICK <=> OP_DUP; 20 | 1 OP_PICK <=> OP_OVER; 21 | 2 OP_ROLL <=> OP_ROT; 22 | 1 OP_ROLL <=> OP_SWAP; 23 | OP_DROP OP_DROP <=> OP_2DROP; 24 | OP_EQUAL OP_VERIFY <=> OP_EQUALVERIFY; 25 | OP_NUMEQUAL OP_VERIFY <=> OP_NUMEQUALVERIFY; 26 | OP_CHECKSIG OP_VERIFY <=> OP_CHECKSIGVERIFY; 27 | 3 OP_CHECKMULTISIG OP_VERIFY <=> 3 OP_CHECKMULTISIGVERIFY; 28 | OP_CHECKDATASIG OP_VERIFY <=> OP_CHECKDATASIGVERIFY; 29 | -------------------------------------------------------------------------------- /examples/if.equiv: -------------------------------------------------------------------------------- 1 | OP_IF 1 OP_ADD OP_ELSE 1 OP_SUB OP_ENDIF <=> OP_IF OP_1ADD OP_ELSE OP_1SUB OP_ENDIF; 2 | -------------------------------------------------------------------------------- /examples/nop.equiv: -------------------------------------------------------------------------------- 1 | OP_1 OP_ROLL OP_1 OP_ROLL 1 OP_ADD OP_DUP OP_DROP <=> OP_1ADD; 2 | OP_DUP OP_DROP 1 OP_ADD OP_1 OP_ROLL OP_1 OP_ROLL <=> OP_1ADD; 3 | -------------------------------------------------------------------------------- /examples/nop_verify.equiv: -------------------------------------------------------------------------------- 1 | 12 OP_CHECKLOCKTIMEVERIFY <=> 5 7 OP_ADD OP_CHECKLOCKTIMEVERIFY; 2 | 12 OP_CHECKLOCKTIMEVERIFY OP_DROP 5 8 OP_ADD OP_CHECKLOCKTIMEVERIFY OP_DROP; 3 | 12 OP_CHECKSEQUENCEVERIFY <=> 5 7 OP_ADD OP_CHECKSEQUENCEVERIFY; 4 | 12 OP_CHECKSEQUENCEVERIFY 5 7 OP_ADD OP_CHECKLOCKTIMEVERIFY; 5 | -------------------------------------------------------------------------------- /examples/numbers.equiv: -------------------------------------------------------------------------------- 1 | OP_NUM2BIN OP_BIN2NUM OP_1ADD <=> OP_DROP OP_1ADD; 2 | "" OP_BIN2NUM <=> 0; 3 | 0x00 OP_BIN2NUM <=> 0; 4 | 0x01 OP_BIN2NUM <=> 1; 5 | 0x10 OP_BIN2NUM <=> 16; 6 | 0x81 OP_BIN2NUM <=> -1; 7 | 0x7f OP_BIN2NUM <=> 127; 8 | 0xff OP_BIN2NUM <=> -127; 9 | 0x0000 OP_BIN2NUM <=> 0; 10 | 0x0100 OP_BIN2NUM <=> 1; 11 | 0x0101 OP_BIN2NUM <=> 257; 12 | 0x0180 OP_BIN2NUM <=> -1; 13 | 0xff7f OP_BIN2NUM <=> 32767; 14 | 0xffff OP_BIN2NUM <=> -32767; 15 | 0x000000 OP_BIN2NUM <=> 0; 16 | 0x010000 OP_BIN2NUM <=> 1; 17 | 0x010000 OP_BIN2NUM 2; 18 | 0x010100 OP_BIN2NUM <=> 257; 19 | 0x010101 OP_BIN2NUM <=> 65793; 20 | 0x010080 OP_BIN2NUM <=> -1; 21 | 0x010080 OP_BIN2NUM -2; 22 | 0xffff7f OP_BIN2NUM <=> 8388607; 23 | 0xffffff OP_BIN2NUM <=> -8388607; 24 | 0x010181 OP_BIN2NUM <=> -65793; 25 | 0x00000000 OP_BIN2NUM <=> 0; 26 | 0x01000000 OP_BIN2NUM <=> 1; 27 | 0x01000000 OP_BIN2NUM 2; 28 | 0x01010101 OP_BIN2NUM <=> 16843009; 29 | 0x01000080 OP_BIN2NUM <=> -1; 30 | 0x01000080 OP_BIN2NUM -2; 31 | 0x01010181 OP_BIN2NUM <=> -16843009; 32 | 0xffffff7f OP_BIN2NUM <=> 2147483647; 33 | 0xffffffff OP_BIN2NUM <=> -2147483647; 34 | 0x0000000000 OP_BIN2NUM <=> 0; 35 | 0x0100000000 OP_BIN2NUM <=> 1; 36 | 0x0100000080 OP_BIN2NUM <=> -1; 37 | 0x0100000080 OP_BIN2NUM -2; 38 | 0x0101010100 OP_BIN2NUM <=> 16843009; 39 | 0x0101010180 OP_BIN2NUM <=> -16843009; 40 | 0x0101010180 OP_BIN2NUM -16843008; 41 | 0xffffff7f00 OP_BIN2NUM <=> 2147483647; 42 | 0xffffff7f80 OP_BIN2NUM <=> -2147483647; 43 | 0x000000000000 OP_BIN2NUM <=> 0; 44 | 0x010000000000 OP_BIN2NUM <=> 1; 45 | 0x010000000080 OP_BIN2NUM <=> -1; 46 | 0x010000000080 OP_BIN2NUM -2; 47 | 0x010101010000 OP_BIN2NUM <=> 16843009; 48 | 0x010101010080 OP_BIN2NUM <=> -16843009; 49 | 0xffffff7f0000 OP_BIN2NUM <=> 2147483647; 50 | 0xffffff7f0080 OP_BIN2NUM <=> -2147483647; 51 | 0xffffff7f0080 OP_BIN2NUM -2147483648; 52 | 0x00000000000000 OP_BIN2NUM <=> 0; 53 | 0x01000000000000 OP_BIN2NUM <=> 1; 54 | 0x01000000000080 OP_BIN2NUM <=> -1; 55 | 0x01000000000080 OP_BIN2NUM -2; 56 | 0x01010101000000 OP_BIN2NUM <=> 16843009; 57 | 0x01010101000080 OP_BIN2NUM <=> -16843009; 58 | 0xffffff7f000000 OP_BIN2NUM <=> 2147483647; 59 | 0xffffff7f000080 OP_BIN2NUM <=> -2147483647; 60 | 61 | 0 0 OP_NUM2BIN <=> ""; 62 | 0 1 OP_NUM2BIN <=> 0x00; 63 | 1 1 OP_NUM2BIN <=> 0x01; 64 | 16 1 OP_NUM2BIN <=> 0x10; 65 | -1 1 OP_NUM2BIN <=> 0x81; 66 | 127 1 OP_NUM2BIN <=> 0x7f; 67 | -127 1 OP_NUM2BIN <=> 0xff; 68 | 0 2 OP_NUM2BIN <=> 0x0000; 69 | 1 2 OP_NUM2BIN <=> 0x0100; 70 | 257 2 OP_NUM2BIN <=> 0x0101; 71 | -1 2 OP_NUM2BIN <=> 0x0180; 72 | 32767 2 OP_NUM2BIN <=> 0xff7f; 73 | -32767 2 OP_NUM2BIN <=> 0xffff; 74 | 0 3 OP_NUM2BIN <=> 0x000000; 75 | 1 3 OP_NUM2BIN <=> 0x010000; 76 | 2 3 OP_NUM2BIN 0x010000; 77 | 257 3 OP_NUM2BIN <=> 0x010100; 78 | 65793 3 OP_NUM2BIN <=> 0x010101; 79 | -1 3 OP_NUM2BIN <=> 0x010080; 80 | -2 3 OP_NUM2BIN 0x010080; 81 | 8388607 3 OP_NUM2BIN <=> 0xffff7f; 82 | -8388607 3 OP_NUM2BIN <=> 0xffffff; 83 | -65793 3 OP_NUM2BIN <=> 0x010181; 84 | 0 4 OP_NUM2BIN <=> 0x00000000; 85 | 1 4 OP_NUM2BIN <=> 0x01000000; 86 | 2 4 OP_NUM2BIN 0x01000000; 87 | 16843009 4 OP_NUM2BIN <=> 0x01010101; 88 | -1 4 OP_NUM2BIN <=> 0x01000080; 89 | -2 4 OP_NUM2BIN 0x01000080; 90 | -16843009 4 OP_NUM2BIN <=> 0x01010181; 91 | 2147483647 4 OP_NUM2BIN <=> 0xffffff7f; 92 | -2147483647 4 OP_NUM2BIN <=> 0xffffffff; 93 | 0 5 OP_NUM2BIN <=> 0x0000000000; 94 | 1 5 OP_NUM2BIN <=> 0x0100000000; 95 | -1 5 OP_NUM2BIN <=> 0x0100000080; 96 | -2 5 OP_NUM2BIN 0x0100000080; 97 | 16843009 5 OP_NUM2BIN <=> 0x0101010100; 98 | -16843009 5 OP_NUM2BIN <=> 0x0101010180; 99 | -16843008 5 OP_NUM2BIN 0x0101010180; 100 | 2147483647 5 OP_NUM2BIN <=> 0xffffff7f00; 101 | -2147483647 5 OP_NUM2BIN <=> 0xffffff7f80; 102 | 0 6 OP_NUM2BIN <=> 0x000000000000; 103 | 1 6 OP_NUM2BIN <=> 0x010000000000; 104 | -1 6 OP_NUM2BIN <=> 0x010000000080; 105 | -2 6 OP_NUM2BIN 0x010000000080; 106 | 16843009 6 OP_NUM2BIN <=> 0x010101010000; 107 | -16843009 6 OP_NUM2BIN <=> 0x010101010080; 108 | 2147483647 6 OP_NUM2BIN <=> 0xffffff7f0000; 109 | -2147483647 6 OP_NUM2BIN <=> 0xffffff7f0080; 110 | -2147483648 6 OP_NUM2BIN 0xffffff7f0080; 111 | 0 7 OP_NUM2BIN <=> 0x00000000000000; 112 | 1 7 OP_NUM2BIN <=> 0x01000000000000; 113 | -1 7 OP_NUM2BIN <=> 0x01000000000080; 114 | -2 7 OP_NUM2BIN 0x01000000000080; 115 | 16843009 7 OP_NUM2BIN <=> 0x01010101000000; 116 | -16843009 7 OP_NUM2BIN <=> 0x01010101000080; 117 | 2147483647 7 OP_NUM2BIN <=> 0xffffff7f000000; 118 | -2147483647 7 OP_NUM2BIN <=> 0xffffff7f000080; 119 | -------------------------------------------------------------------------------- /examples/spedn.equiv: -------------------------------------------------------------------------------- 1 | OP_CHECKSIG OP_VERIFY <=> OP_CHECKSIGVERIFY; 2 | 3 OP_CHECKMULTISIG OP_VERIFY <=> 3 OP_CHECKMULTISIGVERIFY; 3 | OP_CHECKDATASIG OP_VERIFY <=> OP_CHECKDATASIGVERIFY; 4 | OP_FALSE OP_EQUAL OP_NOT <=> OP_0NOTEQUAL; 5 | OP_EQUAL OP_VERIFY <=> OP_EQUALVERIFY; 6 | OP_NUMEQUAL OP_VERIFY <=> OP_NUMEQUALVERIFY; 7 | # OP_NOT OP_IF <=> OP_NOTIF; 8 | OP_FALSE OP_PICK <=> OP_DUP; 9 | OP_TRUE OP_PICK <=> OP_OVER; 10 | OP_OVER OP_OVER <=> OP_2DUP; 11 | OP_TRUE OP_ADD <=> OP_1ADD; 12 | OP_TRUE OP_SUB <=> OP_1SUB; 13 | 2 OP_PICK 2 OP_PICK 2 OP_PICK <=> OP_3DUP; 14 | 3 OP_PICK 3 OP_PICK <=> OP_2OVER; 15 | OP_DROP OP_DROP <=> OP_2DROP; 16 | 2 OP_ROLL <=> OP_ROT; 17 | -------------------------------------------------------------------------------- /examples/verify.equiv: -------------------------------------------------------------------------------- 1 | OP_VERIFY <=> OP_DUP OP_VERIFY OP_VERIFY; 2 | OP_VERIFY OP_DUP OP_NOT OP_VERIFY OP_VERIFY; 3 | !full_script=True; 4 | OP_CHECKSIGVERIFY OP_TRUE <=> OP_CHECKSIG; 5 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from cashproof.cases import prove_equivalence_cases 2 | from cashproof.ops import pretty_print_script 3 | from cashproof.parse_equiv_file import parse_equiv 4 | 5 | import sys 6 | 7 | 8 | def main(): 9 | filenames = sys.argv[1:] 10 | equivalences = [] 11 | for filename in filenames: 12 | src = open(filename, 'r').read() 13 | equivalences.extend(parse_equiv(src)) 14 | for equivalence in equivalences: 15 | left, right = equivalence.sides 16 | result = prove_equivalence_cases(left, right, equivalence.max_stackitem_size, equivalence.full_script) 17 | if (result is None and not equivalence.inverted) or (result is not None and equivalence.inverted): 18 | print(end='.') 19 | else: 20 | print() 21 | print('Equivalence FAILED:') 22 | print('Tried to prove:') 23 | print(pretty_print_script(left), '<=>', pretty_print_script(right)) 24 | print('-'*80) 25 | print(result) 26 | print() 27 | 28 | 29 | if __name__ == '__main__': 30 | main() 31 | --------------------------------------------------------------------------------