tx-engine 0.6.6__cp312-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tx_engine/__init__.py +13 -0
- tx_engine/engine/__init__.py +0 -0
- tx_engine/engine/context.py +116 -0
- tx_engine/engine/decode_op.py +38 -0
- tx_engine/engine/engine_types.py +7 -0
- tx_engine/engine/op_code_names.py +108 -0
- tx_engine/engine/op_codes.py +122 -0
- tx_engine/engine/util.py +109 -0
- tx_engine/interface/__init__.py +0 -0
- tx_engine/interface/blockchain_interface.py +79 -0
- tx_engine/interface/interface_factory.py +24 -0
- tx_engine/interface/mock_interface.py +108 -0
- tx_engine/interface/rpc_interface.py +272 -0
- tx_engine/interface/verify_script.py +129 -0
- tx_engine/interface/woc.py +125 -0
- tx_engine/interface/woc_interface.py +92 -0
- tx_engine/tx/__init__.py +0 -0
- tx_engine/tx/sighash.py +19 -0
- tx_engine/tx_engine.cp312-win_amd64.pyd +0 -0
- tx_engine-0.6.6.dist-info/METADATA +164 -0
- tx_engine-0.6.6.dist-info/RECORD +24 -0
- tx_engine-0.6.6.dist-info/WHEEL +4 -0
- tx_engine-0.6.6.dist-info/licenses/LICENSE +21 -0
- tx_engine-0.6.6.dist-info/licenses/LICENSE-rust-sv +21 -0
tx_engine/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is a tx_engine doc str
|
|
3
|
+
"""
|
|
4
|
+
# noqa: F401 - 'x' - imported but unused
|
|
5
|
+
|
|
6
|
+
from tx_engine.tx_engine import Tx, TxIn, TxOut, Script, Wallet, p2pkh_script, hash160, hash256d, address_to_public_key_hash, public_key_to_address # noqa: F401
|
|
7
|
+
from tx_engine.tx_engine import sig_hash_preimage, sig_hash, wif_to_bytes # noqa: F401
|
|
8
|
+
from tx_engine.engine.context import Context # noqa: F401
|
|
9
|
+
from tx_engine.engine.util import encode_num, decode_num # noqa: F401
|
|
10
|
+
from tx_engine.tx.sighash import SIGHASH # noqa: F401
|
|
11
|
+
from tx_engine.interface.interface_factory import interface_factory # noqa: F401
|
|
12
|
+
from tx_engine.interface.woc_interface import WoCInterface # noqa: F401
|
|
13
|
+
from tx_engine.interface.mock_interface import MockInterface # noqa: F401
|
|
File without changes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from tx_engine.tx_engine import py_script_eval, Script
|
|
4
|
+
from tx_engine.engine.util import decode_num
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from .engine_types import Commands, Stack, StackElement
|
|
8
|
+
from .op_codes import OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def decode_element(elem: StackElement) -> int:
|
|
12
|
+
try:
|
|
13
|
+
retval = decode_num(bytes(elem))
|
|
14
|
+
except RuntimeError as e:
|
|
15
|
+
print(f"runtime error {e}")
|
|
16
|
+
retval = elem # type: ignore[assignment]
|
|
17
|
+
print(f"elem={elem}, retval={retval}, type={type(retval)}") # type: ignore[str-bytes-safe]
|
|
18
|
+
return retval
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cmds_as_bytes(cmds: Commands) -> bytes:
|
|
22
|
+
""" Given commands return bytes - prior to passing to Rust
|
|
23
|
+
"""
|
|
24
|
+
retval = bytearray()
|
|
25
|
+
for c in cmds:
|
|
26
|
+
if isinstance(c, int):
|
|
27
|
+
retval += c.to_bytes(1, byteorder='big')
|
|
28
|
+
elif isinstance(c, list):
|
|
29
|
+
retval += cmds_as_bytes(c)
|
|
30
|
+
else:
|
|
31
|
+
# If we have a byte array without a preceeding length, add it, if less than 0x4c
|
|
32
|
+
# Otherwise would expect OP_PUSHDATA preceeding
|
|
33
|
+
if len(c) < 0x4c:
|
|
34
|
+
if len(retval) == 0:
|
|
35
|
+
retval += len(c).to_bytes(1, byteorder='big')
|
|
36
|
+
elif not retval[-1] in [OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4] and retval[-1] != len(c):
|
|
37
|
+
retval += len(c).to_bytes(1, byteorder='big')
|
|
38
|
+
retval += c
|
|
39
|
+
return bytes(retval)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Context:
|
|
43
|
+
""" This class captures an execution context for the script
|
|
44
|
+
"""
|
|
45
|
+
def __init__(self, script: None | Script = None, cmds: None | Commands = None, ip_limit: None | int = None, z: None | bytes = None):
|
|
46
|
+
self.cmds: Commands
|
|
47
|
+
self.ip_limit: Optional[int]
|
|
48
|
+
self.z: Optional[bytes]
|
|
49
|
+
self.stack: Stack = []
|
|
50
|
+
self.alt_stack: Stack = []
|
|
51
|
+
self.raw_stack: Stack = []
|
|
52
|
+
self.raw_alt_stack: Stack = []
|
|
53
|
+
|
|
54
|
+
if script:
|
|
55
|
+
self.cmds = script.get_commands()
|
|
56
|
+
elif cmds:
|
|
57
|
+
self.cmds = cmds[:]
|
|
58
|
+
else:
|
|
59
|
+
self.cmds = []
|
|
60
|
+
|
|
61
|
+
self.ip_limit = ip_limit if ip_limit else None
|
|
62
|
+
self.z = z if z else None
|
|
63
|
+
|
|
64
|
+
def set_commands(self, cmds: Commands) -> None:
|
|
65
|
+
self.cmds = cmds[:]
|
|
66
|
+
|
|
67
|
+
def reset_stacks(self) -> None:
|
|
68
|
+
self.stack = []
|
|
69
|
+
self.alt_stack = []
|
|
70
|
+
self.raw_stack = []
|
|
71
|
+
self.raw_alt_stack = []
|
|
72
|
+
|
|
73
|
+
def evaluate_core(self, quiet: bool = False) -> bool:
|
|
74
|
+
""" evaluate_core calls the interpreter and returns the stacks
|
|
75
|
+
if quiet is true, dont print exceptions
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
cmds = cmds_as_bytes(self.cmds)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
if not quiet:
|
|
81
|
+
print(f"cmds_as_bytes exception '{e}'")
|
|
82
|
+
return False
|
|
83
|
+
try:
|
|
84
|
+
(self.raw_stack, self.raw_alt_stack) = py_script_eval(cmds, self.ip_limit, self.z)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
if not quiet:
|
|
87
|
+
print(f"script_eval exception '{e}'")
|
|
88
|
+
return False
|
|
89
|
+
else:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def evaluate(self, quiet: bool = False) -> bool:
|
|
93
|
+
""" evaluate calls Evaluate_core and checks the stack has the correct value on return
|
|
94
|
+
if quiet is true, dont print exceptions
|
|
95
|
+
"""
|
|
96
|
+
if not self.evaluate_core(quiet):
|
|
97
|
+
return False
|
|
98
|
+
self.stack = [decode_element(s) for s in self.raw_stack]
|
|
99
|
+
self.alt_stack = [decode_element(s) for s in self.raw_alt_stack]
|
|
100
|
+
if len(self.stack) == 0:
|
|
101
|
+
return False
|
|
102
|
+
if self.stack[-1] == 0: # was b""
|
|
103
|
+
return False
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
def get_stack(self) -> Stack:
|
|
107
|
+
""" Return the data stack as human readable
|
|
108
|
+
"""
|
|
109
|
+
self.stack = [decode_element(s) for s in self.raw_stack]
|
|
110
|
+
return self.stack
|
|
111
|
+
|
|
112
|
+
def get_altstack(self):
|
|
113
|
+
""" Return the get_altstack as human readable
|
|
114
|
+
"""
|
|
115
|
+
self.alt_stack = [decode_element(s) for s in self.raw_alt_stack]
|
|
116
|
+
return self.alt_stack
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from .util import encode_num
|
|
4
|
+
from .op_code_names import OP_CODE_NAMES
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Create a dictionary of OP_DUP -> 118, DUP -> 118
|
|
8
|
+
OPS_STANDARD = {v: k for (k, v) in OP_CODE_NAMES.items()}
|
|
9
|
+
SHORT_TO_LONG_OP = {k.split("_")[1]: v for (k, v) in OPS_STANDARD.items()}
|
|
10
|
+
ALL_OPS = {**OPS_STANDARD, **SHORT_TO_LONG_OP}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def decode_op(op: str) -> Union[int, bytes]:
|
|
14
|
+
""" Given an op as string convert it to parsable value
|
|
15
|
+
e.g. "OP_2" -> 0x52
|
|
16
|
+
"""
|
|
17
|
+
op = op.strip()
|
|
18
|
+
if op[:2] == "0x":
|
|
19
|
+
b: bytes = bytes.fromhex(op[2:])
|
|
20
|
+
return b
|
|
21
|
+
|
|
22
|
+
elif op in ALL_OPS:
|
|
23
|
+
n: int = ALL_OPS[op]
|
|
24
|
+
return n
|
|
25
|
+
|
|
26
|
+
else:
|
|
27
|
+
n = eval(op)
|
|
28
|
+
if isinstance(n, int):
|
|
29
|
+
x: bytes = encode_num(n)
|
|
30
|
+
return x
|
|
31
|
+
elif isinstance(n, str):
|
|
32
|
+
y = n.encode("utf-8")
|
|
33
|
+
return y
|
|
34
|
+
elif isinstance(n, bytes):
|
|
35
|
+
return n
|
|
36
|
+
else:
|
|
37
|
+
# have not captured conversion
|
|
38
|
+
assert 1 == 2 # should not get here
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
OP_CODE_NAMES: Dict[int, str] = {
|
|
4
|
+
0: "OP_0",
|
|
5
|
+
76: "OP_PUSHDATA1",
|
|
6
|
+
77: "OP_PUSHDATA2",
|
|
7
|
+
78: "OP_PUSHDATA4",
|
|
8
|
+
79: "OP_1NEGATE",
|
|
9
|
+
80: "OP_RESERVED",
|
|
10
|
+
81: "OP_1",
|
|
11
|
+
82: "OP_2",
|
|
12
|
+
83: "OP_3",
|
|
13
|
+
84: "OP_4",
|
|
14
|
+
85: "OP_5",
|
|
15
|
+
86: "OP_6",
|
|
16
|
+
87: "OP_7",
|
|
17
|
+
88: "OP_8",
|
|
18
|
+
89: "OP_9",
|
|
19
|
+
90: "OP_10",
|
|
20
|
+
91: "OP_11",
|
|
21
|
+
92: "OP_12",
|
|
22
|
+
93: "OP_13",
|
|
23
|
+
94: "OP_14",
|
|
24
|
+
95: "OP_15",
|
|
25
|
+
96: "OP_16",
|
|
26
|
+
97: "OP_NOP",
|
|
27
|
+
98: "OP_VER",
|
|
28
|
+
99: "OP_IF",
|
|
29
|
+
100: "OP_NOTIF",
|
|
30
|
+
101: "OP_VERIF",
|
|
31
|
+
102: "OP_NOTVERIF",
|
|
32
|
+
103: "OP_ELSE",
|
|
33
|
+
104: "OP_ENDIF",
|
|
34
|
+
105: "OP_VERIFY",
|
|
35
|
+
106: "OP_RETURN",
|
|
36
|
+
107: "OP_TOALTSTACK",
|
|
37
|
+
108: "OP_FROMALTSTACK",
|
|
38
|
+
109: "OP_2DROP",
|
|
39
|
+
110: "OP_2DUP",
|
|
40
|
+
111: "OP_3DUP",
|
|
41
|
+
112: "OP_2OVER",
|
|
42
|
+
113: "OP_2ROT",
|
|
43
|
+
114: "OP_2SWAP",
|
|
44
|
+
115: "OP_IFDUP",
|
|
45
|
+
116: "OP_DEPTH",
|
|
46
|
+
117: "OP_DROP",
|
|
47
|
+
118: "OP_DUP",
|
|
48
|
+
119: "OP_NIP",
|
|
49
|
+
120: "OP_OVER",
|
|
50
|
+
121: "OP_PICK",
|
|
51
|
+
122: "OP_ROLL",
|
|
52
|
+
123: "OP_ROT",
|
|
53
|
+
124: "OP_SWAP",
|
|
54
|
+
125: "OP_TUCK",
|
|
55
|
+
126: "OP_CAT",
|
|
56
|
+
127: "OP_SPLIT",
|
|
57
|
+
128: "OP_NUM2BIN",
|
|
58
|
+
129: "OP_BIN2NUM",
|
|
59
|
+
130: "OP_SIZE",
|
|
60
|
+
131: "OP_INVERT",
|
|
61
|
+
132: "OP_AND",
|
|
62
|
+
133: "OP_OR",
|
|
63
|
+
134: "OP_XOR",
|
|
64
|
+
135: "OP_EQUAL",
|
|
65
|
+
136: "OP_EQUALVERIFY",
|
|
66
|
+
137: "OP_RESERVED1",
|
|
67
|
+
138: "OP_RESERVED2",
|
|
68
|
+
139: "OP_1ADD",
|
|
69
|
+
140: "OP_1SUB",
|
|
70
|
+
141: "OP_2MUL", # disabled
|
|
71
|
+
142: "OP_2DIV", # disabled
|
|
72
|
+
143: "OP_NEGATE",
|
|
73
|
+
144: "OP_ABS",
|
|
74
|
+
145: "OP_NOT",
|
|
75
|
+
146: "OP_0NOTEQUAL",
|
|
76
|
+
147: "OP_ADD",
|
|
77
|
+
148: "OP_SUB",
|
|
78
|
+
149: "OP_MUL",
|
|
79
|
+
150: "OP_DIV",
|
|
80
|
+
151: "OP_MOD",
|
|
81
|
+
152: "OP_LSHIFT",
|
|
82
|
+
153: "OP_RSHIFT",
|
|
83
|
+
154: "OP_BOOLAND",
|
|
84
|
+
155: "OP_BOOLOR",
|
|
85
|
+
156: "OP_NUMEQUAL",
|
|
86
|
+
157: "OP_NUMEQUALVERIFY",
|
|
87
|
+
158: "OP_NUMNOTEQUAL",
|
|
88
|
+
159: "OP_LESSTHAN",
|
|
89
|
+
160: "OP_GREATERTHAN",
|
|
90
|
+
161: "OP_LESSTHANOREQUAL",
|
|
91
|
+
162: "OP_GREATERTHANOREQUAL",
|
|
92
|
+
163: "OP_MIN",
|
|
93
|
+
164: "OP_MAX",
|
|
94
|
+
165: "OP_WITHIN",
|
|
95
|
+
166: "OP_RIPEMD160",
|
|
96
|
+
167: "OP_SHA1",
|
|
97
|
+
168: "OP_SHA256",
|
|
98
|
+
169: "OP_HASH160",
|
|
99
|
+
170: "OP_HASH256",
|
|
100
|
+
171: "OP_CODESEPARATOR",
|
|
101
|
+
172: "OP_CHECKSIG",
|
|
102
|
+
173: "OP_CHECKSIGVERIFY",
|
|
103
|
+
174: "OP_CHECKMULTISIG",
|
|
104
|
+
175: "OP_CHECKMULTISIGVERIFY",
|
|
105
|
+
176: "OP_NOP1",
|
|
106
|
+
177: "OP_CHECKLOCKTIMEVERIFY",
|
|
107
|
+
178: "OP_CHECKSEQUENCEVERIFY"
|
|
108
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Used to convert OP name to code
|
|
2
|
+
|
|
3
|
+
OP_0 = 0
|
|
4
|
+
OP_FALSE = 0
|
|
5
|
+
OP_PUSHDATA1 = 0x4C
|
|
6
|
+
OP_PUSHDATA2 = 0x4D
|
|
7
|
+
OP_PUSHDATA4 = 0x4E
|
|
8
|
+
OP_1NEGATE = 0x4F
|
|
9
|
+
OP_RESERVED = 0x50
|
|
10
|
+
OP_TRUE = 0x51
|
|
11
|
+
OP_1 = 0x51
|
|
12
|
+
OP_2 = 0x52
|
|
13
|
+
OP_3 = 0x53
|
|
14
|
+
OP_4 = 0x54
|
|
15
|
+
OP_5 = 0x55
|
|
16
|
+
OP_6 = 0x56
|
|
17
|
+
OP_7 = 0x57
|
|
18
|
+
OP_8 = 0x58
|
|
19
|
+
OP_9 = 0x59
|
|
20
|
+
OP_10 = 0x5A
|
|
21
|
+
OP_11 = 0x5B
|
|
22
|
+
OP_12 = 0x5C
|
|
23
|
+
OP_13 = 0x5D
|
|
24
|
+
OP_14 = 0x5E
|
|
25
|
+
OP_15 = 0x5F
|
|
26
|
+
OP_16 = 0x60
|
|
27
|
+
|
|
28
|
+
# Control
|
|
29
|
+
OP_NOP = 0x61
|
|
30
|
+
OP_VER = 0x62
|
|
31
|
+
OP_IF = 0x63
|
|
32
|
+
OP_NOTIF = 0x64
|
|
33
|
+
OP_VERIF = 0x65
|
|
34
|
+
OP_VERNOTIF = 0x66
|
|
35
|
+
OP_ELSE = 0x67
|
|
36
|
+
OP_ENDIF = 0x68
|
|
37
|
+
OP_VERIFY = 0x69
|
|
38
|
+
OP_RETURN = 0x6A
|
|
39
|
+
|
|
40
|
+
# stack ops
|
|
41
|
+
OP_TOALTSTACK = 0x6B
|
|
42
|
+
OP_FROMALTSTACK = 0x6C
|
|
43
|
+
OP_2DROP = 0x6D
|
|
44
|
+
OP_2DUP = 0x6E
|
|
45
|
+
OP_3DUP = 0x6F
|
|
46
|
+
OP_2OVER = 0x70
|
|
47
|
+
OP_2ROT = 0x71
|
|
48
|
+
OP_2SWAP = 0x72
|
|
49
|
+
OP_IFDUP = 0x73
|
|
50
|
+
OP_DEPTH = 0x74
|
|
51
|
+
OP_DROP = 0x75
|
|
52
|
+
OP_DUP = 0x76
|
|
53
|
+
OP_NIP = 0x77
|
|
54
|
+
OP_OVER = 0x78
|
|
55
|
+
OP_PICK = 0x79
|
|
56
|
+
OP_ROLL = 0x7A
|
|
57
|
+
OP_ROT = 0x7B
|
|
58
|
+
OP_SWAP = 0x7C
|
|
59
|
+
OP_TUCK = 0x7D
|
|
60
|
+
|
|
61
|
+
# Splice ops - BSV
|
|
62
|
+
OP_CAT = 0x7E
|
|
63
|
+
OP_SPLIT = 0x7F
|
|
64
|
+
OP_NUM2BIN = 0x80
|
|
65
|
+
OP_BIN2NUM = 0x81
|
|
66
|
+
OP_SIZE = 0x82
|
|
67
|
+
# 0x83{131} .. 0x86{134} - transaction invalid for non BSV
|
|
68
|
+
|
|
69
|
+
# Bit logic - BSV
|
|
70
|
+
OP_INVERT = 0x83
|
|
71
|
+
OP_AND = 0x84
|
|
72
|
+
OP_OR = 0x85
|
|
73
|
+
OP_XOR = 0x86
|
|
74
|
+
OP_EQUAL = 0x87
|
|
75
|
+
OP_EQUALVERIFY = 0x88
|
|
76
|
+
OP_RESERVED1 = 0x89
|
|
77
|
+
OP_RESERVED2 = 0x8A
|
|
78
|
+
|
|
79
|
+
# Artithmetic
|
|
80
|
+
OP_1ADD = 0x8B
|
|
81
|
+
OP_1SUB = 0x8C
|
|
82
|
+
OP_2MUL = 0x8D # Disabled
|
|
83
|
+
OP_2DIV = 0x8E # Disabled
|
|
84
|
+
OP_NEGATE = 0x8F
|
|
85
|
+
OP_ABS = 0x90
|
|
86
|
+
OP_NOT = 0x91
|
|
87
|
+
OP_0NOTEQUAL = 0x92
|
|
88
|
+
OP_ADD = 0x93
|
|
89
|
+
OP_SUB = 0x94
|
|
90
|
+
OP_MUL = 0x95
|
|
91
|
+
OP_DIV = 0x96
|
|
92
|
+
OP_MOD = 0x97
|
|
93
|
+
OP_LSHIFT = 0x98
|
|
94
|
+
OP_RSHIFT = 0x99
|
|
95
|
+
|
|
96
|
+
OP_BOOLAND = 0x9A
|
|
97
|
+
OP_BOOLOR = 0x9B
|
|
98
|
+
OP_NUMEQUAL = 0x9C
|
|
99
|
+
OP_NUMEQUALVERIFY = 0x9D
|
|
100
|
+
OP_NUMNOTEQUAL = 0x9E
|
|
101
|
+
OP_LESSTHAN = 0x9F
|
|
102
|
+
OP_GREATERTHAN = 0xA0
|
|
103
|
+
OP_LESSTHANOREQUAL = 0xA1
|
|
104
|
+
OP_GREATERTHANOREQUAL = 0xA2
|
|
105
|
+
OP_MIN = 0xA3
|
|
106
|
+
OP_MAX = 0xA4
|
|
107
|
+
OP_WITHIN = 0xA5
|
|
108
|
+
OP_RIPEMD160 = 0xA6
|
|
109
|
+
OP_SHA1 = 0xA7
|
|
110
|
+
OP_SHA256 = 0xA8
|
|
111
|
+
OP_HASH160 = 0xA9
|
|
112
|
+
OP_HASH256 = 0xAA
|
|
113
|
+
OP_CODESEPARATOR = 0xAB
|
|
114
|
+
OP_CHECKSIG = 0xAC
|
|
115
|
+
OP_CHECKSIGVERIFY = 0xAD
|
|
116
|
+
OP_CHECKMULTISIG = 0xAE
|
|
117
|
+
OP_CHECKMULTISIGVERIFY = 0xAF
|
|
118
|
+
|
|
119
|
+
# Expansion
|
|
120
|
+
OP_NOP1 = 0xB0
|
|
121
|
+
OP_CHECKLOCKTIMEVERIFY = 0xB1
|
|
122
|
+
OP_CHECKSEQUENCEVERIFY = 0xB2
|
tx_engine/engine/util.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from .engine_types import StackElement
|
|
4
|
+
|
|
5
|
+
# Maximum script number length before Genesis (equal to CScriptNum::MAXIMUM_ELEMENT_SIZE)
|
|
6
|
+
MAX_SCRIPT_NUM_LENGTH_BEFORE_GENESIS = 4
|
|
7
|
+
# Maximum script number length after Genesis
|
|
8
|
+
MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS = 750 * 1000
|
|
9
|
+
# Maximum size that we are using
|
|
10
|
+
MAXIMUM_ELEMENT_SIZE = MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS
|
|
11
|
+
|
|
12
|
+
# Curve constants
|
|
13
|
+
# perhaps we can source these from the secp256k1 library rather than here?
|
|
14
|
+
PRIME_INT = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
|
15
|
+
GROUP_ORDER_INT = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
|
16
|
+
HALF_GROUP_ORDER_INT = GROUP_ORDER_INT // 2
|
|
17
|
+
Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
|
|
18
|
+
Gx_bytes = bytes.fromhex('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798')
|
|
19
|
+
Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
|
|
20
|
+
GUncompressed = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def encode_num(num: int) -> bytes:
|
|
24
|
+
""" Encode a number, return a bytearray in little endian
|
|
25
|
+
"""
|
|
26
|
+
if num == 0:
|
|
27
|
+
return b""
|
|
28
|
+
abs_num = abs(num)
|
|
29
|
+
negative = num < 0
|
|
30
|
+
result = bytearray()
|
|
31
|
+
while abs_num:
|
|
32
|
+
result.append(abs_num & 0xFF)
|
|
33
|
+
abs_num >>= 8
|
|
34
|
+
if result[-1] & 0x80:
|
|
35
|
+
if negative:
|
|
36
|
+
result.append(0x80)
|
|
37
|
+
else:
|
|
38
|
+
result.append(0)
|
|
39
|
+
elif negative:
|
|
40
|
+
result[-1] |= 0x80
|
|
41
|
+
return bytes(result)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_minimally_encoded(element, max_element_size=MAXIMUM_ELEMENT_SIZE) -> bool:
|
|
45
|
+
""" Determines if an element is minimally encoded, returns True if it is.
|
|
46
|
+
Code copied from SV codebase for details see:
|
|
47
|
+
file: int_serialization.h, function: IsMinimallyEncoded, line: 98
|
|
48
|
+
"""
|
|
49
|
+
if isinstance(element, int):
|
|
50
|
+
return True
|
|
51
|
+
size = len(element)
|
|
52
|
+
if size > max_element_size:
|
|
53
|
+
return False
|
|
54
|
+
if size > 0:
|
|
55
|
+
elem = element[::-1]
|
|
56
|
+
if elem[0] & 0x7f == 0:
|
|
57
|
+
if size <= 1 or (elem[1] & 0x80 == 0):
|
|
58
|
+
return False
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def decode_num(element: StackElement, check_encoding=False) -> int:
|
|
63
|
+
""" Take a byte(array), return a number
|
|
64
|
+
"""
|
|
65
|
+
if element == b"":
|
|
66
|
+
return 0
|
|
67
|
+
|
|
68
|
+
if check_encoding and not is_minimally_encoded(element):
|
|
69
|
+
if isinstance(element, bytes):
|
|
70
|
+
raise ValueError(f"Value is not minimally encoded: {element.hex()}")
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Value is not minimally encoded: {element}")
|
|
73
|
+
|
|
74
|
+
if isinstance(element, int):
|
|
75
|
+
return element
|
|
76
|
+
|
|
77
|
+
elif isinstance(element, bytes):
|
|
78
|
+
try:
|
|
79
|
+
b = element
|
|
80
|
+
big_endian = b[::-1]
|
|
81
|
+
except TypeError:
|
|
82
|
+
# TypeError: 'int' object is not subscriptable
|
|
83
|
+
return int(element)
|
|
84
|
+
if big_endian[0] & 0x80:
|
|
85
|
+
negative = True
|
|
86
|
+
result = big_endian[0] & 0x7F
|
|
87
|
+
else:
|
|
88
|
+
negative = False
|
|
89
|
+
result = big_endian[0]
|
|
90
|
+
for c in big_endian[1:]:
|
|
91
|
+
result <<= 8
|
|
92
|
+
result += c
|
|
93
|
+
if negative:
|
|
94
|
+
return -result
|
|
95
|
+
else:
|
|
96
|
+
return result
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(f"Value is of unknown type: {element} {type(element)}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def insert_num(val: int) -> List[int]:
|
|
102
|
+
""" This function is used to insert numbers into script
|
|
103
|
+
"""
|
|
104
|
+
val_as_bytes = bytearray(encode_num(val))
|
|
105
|
+
length = len(val_as_bytes)
|
|
106
|
+
assert length < 0x4c, "Length of number too long, need to encode using OP_PUSHDATA"
|
|
107
|
+
# Insert the length
|
|
108
|
+
val_as_bytes.insert(0, length)
|
|
109
|
+
return list(val_as_bytes)
|
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, Optional, MutableMapping, Any, List
|
|
3
|
+
|
|
4
|
+
ConfigType = MutableMapping[str, Any]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BlockchainInterface(ABC):
|
|
8
|
+
""" This is a BlockchainInterface abstract base class
|
|
9
|
+
This will need to be extended with the used methods
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def set_config(self, config: ConfigType):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def get_utxo(self, address: str):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get_block_count(self) -> int:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_raw_transaction(self, txid: str) -> Optional[str]:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def get_transaction(self, txid: str) -> Dict:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def broadcast_tx(self, tx: str):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def is_testnet(self) -> bool:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def get_balance(self, address) -> int:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
""" abstract method definition for get_best_block_hash
|
|
45
|
+
"""
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def get_best_block_hash(self) -> str:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
""" abstract method definition to define the get_tx_out call to an RPC SV node
|
|
51
|
+
"""
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get_tx_out(self, txid: str, txindex: int) -> Dict:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
""" abstract method definition to define an interface for getblock
|
|
57
|
+
"""
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def get_block(self, blockhash: str) -> Dict:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
""" absract method definition for merkle proof retrieval
|
|
63
|
+
"""
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def get_merkle_proof(self, block_hash: str, tx_id: str) -> str:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
''' abstract method definition for getting block headers from WoC
|
|
69
|
+
'''
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def get_block_header(self, block_hash: str) -> Dict:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
''' abstract method definition for executing verify script.
|
|
75
|
+
This call is not available from WoC
|
|
76
|
+
'''
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def verifyscript(self, scripts: list, stopOnFirstInvalid: bool = True, totalTimeout: int = 100) -> List[Any]:
|
|
79
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .blockchain_interface import ConfigType
|
|
2
|
+
from .mock_interface import MockInterface
|
|
3
|
+
from .woc_interface import WoCInterface
|
|
4
|
+
from .rpc_interface import RPCInterface
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
INTERFACE_MAPPING = {
|
|
8
|
+
"mock": MockInterface,
|
|
9
|
+
"woc": WoCInterface,
|
|
10
|
+
"rpc": RPCInterface,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InterfaceFactory:
|
|
15
|
+
""" A class for creating interfaces to the BSV blockchain """
|
|
16
|
+
|
|
17
|
+
def set_config(self, config: ConfigType) -> MockInterface | WoCInterface | RPCInterface:
|
|
18
|
+
interface_type = config['interface_type']
|
|
19
|
+
interface = INTERFACE_MAPPING[interface_type]() # type: ignore[abstract]
|
|
20
|
+
interface.set_config(config)
|
|
21
|
+
return interface # type: ignore[return-value]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
interface_factory = InterfaceFactory()
|