tx-engine 0.7.4__cp314-cp314-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 +14 -0
- tx_engine/engine/__init__.py +0 -0
- tx_engine/engine/context.py +91 -0
- tx_engine/engine/cryptography_utils.py +46 -0
- tx_engine/engine/engine_types.py +9 -0
- tx_engine/engine/op_code_names.py +110 -0
- tx_engine/engine/op_codes.py +126 -0
- tx_engine/engine/util.py +109 -0
- tx_engine/interface/__init__.py +0 -0
- tx_engine/interface/blockchain_interface.py +84 -0
- tx_engine/interface/interface_factory.py +28 -0
- tx_engine/interface/mock_interface.py +109 -0
- tx_engine/interface/rpc_interface.py +287 -0
- tx_engine/interface/verify_script.py +131 -0
- tx_engine/interface/woc.py +126 -0
- tx_engine/interface/woc_interface.py +98 -0
- tx_engine/tx/__init__.py +0 -0
- tx_engine/tx/sighash.py +21 -0
- tx_engine/tx_engine.cp314-win_amd64.pyd +0 -0
- tx_engine-0.7.4.dist-info/METADATA +352 -0
- tx_engine-0.7.4.dist-info/RECORD +24 -0
- tx_engine-0.7.4.dist-info/WHEEL +4 -0
- tx_engine-0.7.4.dist-info/licenses/LICENSE +21 -0
- tx_engine-0.7.4.dist-info/licenses/LICENSE-rust-sv +21 -0
tx_engine/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
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, Stack, 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_preimage_checksig_index, sig_hash, sig_hash_checksig_index, wif_to_bytes, bytes_to_wif, wif_from_pw_nonce # 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
|
|
14
|
+
from tx_engine.engine.cryptography_utils import create_wallet_from_pem_bytes, create_pem_from_wallet # noqa: F401
|
|
File without changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
""" This is the execution context for the script
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
|
|
6
|
+
from tx_engine.tx_engine import py_script_eval_pystack, Script, Stack
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Context:
|
|
10
|
+
""" This class captures an execution context for the script
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, script: None | Script = None, ip_start: None | int = None, ip_limit: None | int = None, z: None | bytes = None):
|
|
13
|
+
""" Intial setup
|
|
14
|
+
"""
|
|
15
|
+
self.ip_start: Optional[int]
|
|
16
|
+
self.ip_limit: Optional[int]
|
|
17
|
+
self.z: Optional[bytes]
|
|
18
|
+
self.stack: Stack = Stack()
|
|
19
|
+
self.alt_stack: Stack = Stack()
|
|
20
|
+
|
|
21
|
+
if script:
|
|
22
|
+
self.cmds = script.get_commands()
|
|
23
|
+
else:
|
|
24
|
+
self.cmds = []
|
|
25
|
+
|
|
26
|
+
self.ip_start = ip_start if ip_start else None
|
|
27
|
+
self.ip_limit = ip_limit if ip_limit else None
|
|
28
|
+
self.z = z if z else None
|
|
29
|
+
|
|
30
|
+
def set_commands(self, script: Script) -> None:
|
|
31
|
+
""" Set the commands
|
|
32
|
+
"""
|
|
33
|
+
self.cmds = script.get_commands()
|
|
34
|
+
|
|
35
|
+
def reset_stacks(self) -> None:
|
|
36
|
+
""" Reset the stacks
|
|
37
|
+
"""
|
|
38
|
+
self.stack = Stack()
|
|
39
|
+
self.alt_stack = Stack()
|
|
40
|
+
|
|
41
|
+
def evaluate_core(self, quiet: bool = False) -> bool:
|
|
42
|
+
""" evaluate_core calls the interpreter and returns the stacks
|
|
43
|
+
if quiet is true, dont print exceptions
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
(self.stack, self.alt_stack, finish_loc) = py_script_eval_pystack(self.cmds, self.ip_start, self.ip_limit, self.z, self.stack, self.alt_stack)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
if not quiet:
|
|
50
|
+
print(f"script_eval exception '{e}'")
|
|
51
|
+
return False
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
def evaluate(self, quiet: bool = False) -> bool:
|
|
55
|
+
""" evaluate calls Evaluate_core and checks the stack has the correct value on return
|
|
56
|
+
if quiet is true, dont print exceptions
|
|
57
|
+
"""
|
|
58
|
+
if not self.evaluate_core(quiet):
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
if self.stack.size() == 0:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
# if the size is 1, then check the top element for either empty or zero
|
|
65
|
+
if self.stack.size() == 1:
|
|
66
|
+
# no entry or 0 => false.
|
|
67
|
+
if self.get_stack() == Stack([[]]) or self.get_stack() == Stack([[0]]):
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
if self.get_stack()[0] == [0] or self.get_stack()[0] == []:
|
|
71
|
+
return False
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
def get_stack(self) -> Stack:
|
|
75
|
+
""" Return the data stack as human readable
|
|
76
|
+
"""
|
|
77
|
+
return self.stack
|
|
78
|
+
|
|
79
|
+
def get_altstack(self) -> Stack:
|
|
80
|
+
""" Return the get_altstack as human readable
|
|
81
|
+
"""
|
|
82
|
+
return self.alt_stack
|
|
83
|
+
|
|
84
|
+
def get_stack_hex(self) -> List[str]:
|
|
85
|
+
return self.stack.get_stack_hex()
|
|
86
|
+
|
|
87
|
+
def set_ip_start(self, start: int) -> None:
|
|
88
|
+
self.ip_start = start
|
|
89
|
+
|
|
90
|
+
def set_ip_limit(self, limit: int) -> None:
|
|
91
|
+
self.ip_limit = limit
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from tx_engine import Wallet
|
|
2
|
+
|
|
3
|
+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
|
4
|
+
from cryptography.hazmat.backends import default_backend
|
|
5
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
6
|
+
from cryptography.hazmat.primitives import serialization
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def bigint_to_private_key(private_key_int: int) -> ec.EllipticCurvePrivateKey:
|
|
10
|
+
'''Convert a BigInt to an ECDSA private key.
|
|
11
|
+
'''
|
|
12
|
+
return ec.derive_private_key(private_key_int, ec.SECP256K1())
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_wallet_from_pem_file(pem_file_path: str, network: str) -> Wallet:
|
|
16
|
+
# Load the PEM file
|
|
17
|
+
with open(pem_file_path, 'rb') as pem_file:
|
|
18
|
+
pem_data = pem_file.read()
|
|
19
|
+
|
|
20
|
+
return create_wallet_from_pem_bytes(pem_data, network)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_wallet_from_pem_bytes(pem_data: bytes, network: str) -> Wallet:
|
|
24
|
+
# Load the private key from the PEM data
|
|
25
|
+
private_key = load_pem_private_key(pem_data, password=None, backend=default_backend())
|
|
26
|
+
assert isinstance(private_key, ec.EllipticCurvePrivateKey)
|
|
27
|
+
|
|
28
|
+
# Extract the private numbers (this includes the scalar/private key value)
|
|
29
|
+
private_numbers = private_key.private_numbers()
|
|
30
|
+
|
|
31
|
+
# The scalar value of the private key (as an integer)
|
|
32
|
+
private_key_scalar = private_numbers.private_value
|
|
33
|
+
|
|
34
|
+
# Convert the scalar value to bytes
|
|
35
|
+
private_key_bytes = private_key_scalar.to_bytes((private_key_scalar.bit_length() + 7) // 8, byteorder='big')
|
|
36
|
+
|
|
37
|
+
return Wallet.from_bytes(network, private_key_bytes)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def create_pem_from_wallet(user_wallet: Wallet) -> str:
|
|
41
|
+
ec_pri_key: ec.EllipticCurvePrivateKey = bigint_to_private_key(user_wallet.to_int())
|
|
42
|
+
pem = ec_pri_key.private_bytes(encoding=serialization.Encoding.PEM,
|
|
43
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
44
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
45
|
+
)
|
|
46
|
+
return pem.decode('utf-8')
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
""" OP code number to name mapping
|
|
2
|
+
"""
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
OP_CODE_NAMES: Dict[int, str] = {
|
|
6
|
+
0: "OP_0",
|
|
7
|
+
76: "OP_PUSHDATA1",
|
|
8
|
+
77: "OP_PUSHDATA2",
|
|
9
|
+
78: "OP_PUSHDATA4",
|
|
10
|
+
79: "OP_1NEGATE",
|
|
11
|
+
80: "OP_RESERVED",
|
|
12
|
+
81: "OP_1",
|
|
13
|
+
82: "OP_2",
|
|
14
|
+
83: "OP_3",
|
|
15
|
+
84: "OP_4",
|
|
16
|
+
85: "OP_5",
|
|
17
|
+
86: "OP_6",
|
|
18
|
+
87: "OP_7",
|
|
19
|
+
88: "OP_8",
|
|
20
|
+
89: "OP_9",
|
|
21
|
+
90: "OP_10",
|
|
22
|
+
91: "OP_11",
|
|
23
|
+
92: "OP_12",
|
|
24
|
+
93: "OP_13",
|
|
25
|
+
94: "OP_14",
|
|
26
|
+
95: "OP_15",
|
|
27
|
+
96: "OP_16",
|
|
28
|
+
97: "OP_NOP",
|
|
29
|
+
98: "OP_VER",
|
|
30
|
+
99: "OP_IF",
|
|
31
|
+
100: "OP_NOTIF",
|
|
32
|
+
101: "OP_VERIF",
|
|
33
|
+
102: "OP_NOTVERIF",
|
|
34
|
+
103: "OP_ELSE",
|
|
35
|
+
104: "OP_ENDIF",
|
|
36
|
+
105: "OP_VERIFY",
|
|
37
|
+
106: "OP_RETURN",
|
|
38
|
+
107: "OP_TOALTSTACK",
|
|
39
|
+
108: "OP_FROMALTSTACK",
|
|
40
|
+
109: "OP_2DROP",
|
|
41
|
+
110: "OP_2DUP",
|
|
42
|
+
111: "OP_3DUP",
|
|
43
|
+
112: "OP_2OVER",
|
|
44
|
+
113: "OP_2ROT",
|
|
45
|
+
114: "OP_2SWAP",
|
|
46
|
+
115: "OP_IFDUP",
|
|
47
|
+
116: "OP_DEPTH",
|
|
48
|
+
117: "OP_DROP",
|
|
49
|
+
118: "OP_DUP",
|
|
50
|
+
119: "OP_NIP",
|
|
51
|
+
120: "OP_OVER",
|
|
52
|
+
121: "OP_PICK",
|
|
53
|
+
122: "OP_ROLL",
|
|
54
|
+
123: "OP_ROT",
|
|
55
|
+
124: "OP_SWAP",
|
|
56
|
+
125: "OP_TUCK",
|
|
57
|
+
126: "OP_CAT",
|
|
58
|
+
127: "OP_SPLIT",
|
|
59
|
+
128: "OP_NUM2BIN",
|
|
60
|
+
129: "OP_BIN2NUM",
|
|
61
|
+
130: "OP_SIZE",
|
|
62
|
+
131: "OP_INVERT",
|
|
63
|
+
132: "OP_AND",
|
|
64
|
+
133: "OP_OR",
|
|
65
|
+
134: "OP_XOR",
|
|
66
|
+
135: "OP_EQUAL",
|
|
67
|
+
136: "OP_EQUALVERIFY",
|
|
68
|
+
137: "OP_RESERVED1",
|
|
69
|
+
138: "OP_RESERVED2",
|
|
70
|
+
139: "OP_1ADD",
|
|
71
|
+
140: "OP_1SUB",
|
|
72
|
+
141: "OP_2MUL",
|
|
73
|
+
142: "OP_2DIV",
|
|
74
|
+
143: "OP_NEGATE",
|
|
75
|
+
144: "OP_ABS",
|
|
76
|
+
145: "OP_NOT",
|
|
77
|
+
146: "OP_0NOTEQUAL",
|
|
78
|
+
147: "OP_ADD",
|
|
79
|
+
148: "OP_SUB",
|
|
80
|
+
149: "OP_MUL",
|
|
81
|
+
150: "OP_DIV",
|
|
82
|
+
151: "OP_MOD",
|
|
83
|
+
152: "OP_LSHIFT",
|
|
84
|
+
153: "OP_RSHIFT",
|
|
85
|
+
154: "OP_BOOLAND",
|
|
86
|
+
155: "OP_BOOLOR",
|
|
87
|
+
156: "OP_NUMEQUAL",
|
|
88
|
+
157: "OP_NUMEQUALVERIFY",
|
|
89
|
+
158: "OP_NUMNOTEQUAL",
|
|
90
|
+
159: "OP_LESSTHAN",
|
|
91
|
+
160: "OP_GREATERTHAN",
|
|
92
|
+
161: "OP_LESSTHANOREQUAL",
|
|
93
|
+
162: "OP_GREATERTHANOREQUAL",
|
|
94
|
+
163: "OP_MIN",
|
|
95
|
+
164: "OP_MAX",
|
|
96
|
+
165: "OP_WITHIN",
|
|
97
|
+
166: "OP_RIPEMD160",
|
|
98
|
+
167: "OP_SHA1",
|
|
99
|
+
168: "OP_SHA256",
|
|
100
|
+
169: "OP_HASH160",
|
|
101
|
+
170: "OP_HASH256",
|
|
102
|
+
171: "OP_CODESEPARATOR",
|
|
103
|
+
172: "OP_CHECKSIG",
|
|
104
|
+
173: "OP_CHECKSIGVERIFY",
|
|
105
|
+
174: "OP_CHECKMULTISIG",
|
|
106
|
+
175: "OP_CHECKMULTISIGVERIFY",
|
|
107
|
+
176: "OP_NOP1",
|
|
108
|
+
177: "OP_CHECKLOCKTIMEVERIFY",
|
|
109
|
+
178: "OP_CHECKSEQUENCEVERIFY"
|
|
110
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
""" OP codes as first class Python constants
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from typing import Final
|
|
5
|
+
# Used to convert OP name to code
|
|
6
|
+
|
|
7
|
+
OP_0: Final = 0
|
|
8
|
+
OP_FALSE: Final = 0
|
|
9
|
+
OP_PUSHDATA1: Final = 0x4C
|
|
10
|
+
OP_PUSHDATA2: Final = 0x4D
|
|
11
|
+
OP_PUSHDATA4: Final = 0x4E
|
|
12
|
+
OP_1NEGATE: Final = 0x4F
|
|
13
|
+
OP_RESERVED: Final = 0x50
|
|
14
|
+
OP_TRUE: Final = 0x51
|
|
15
|
+
OP_1: Final = 0x51
|
|
16
|
+
OP_2: Final = 0x52
|
|
17
|
+
OP_3: Final = 0x53
|
|
18
|
+
OP_4: Final = 0x54
|
|
19
|
+
OP_5: Final = 0x55
|
|
20
|
+
OP_6: Final = 0x56
|
|
21
|
+
OP_7: Final = 0x57
|
|
22
|
+
OP_8: Final = 0x58
|
|
23
|
+
OP_9: Final = 0x59
|
|
24
|
+
OP_10: Final = 0x5A
|
|
25
|
+
OP_11: Final = 0x5B
|
|
26
|
+
OP_12: Final = 0x5C
|
|
27
|
+
OP_13: Final = 0x5D
|
|
28
|
+
OP_14: Final = 0x5E
|
|
29
|
+
OP_15: Final = 0x5F
|
|
30
|
+
OP_16: Final = 0x60
|
|
31
|
+
|
|
32
|
+
# Control
|
|
33
|
+
OP_NOP: Final = 0x61
|
|
34
|
+
OP_VER: Final = 0x62
|
|
35
|
+
OP_IF: Final = 0x63
|
|
36
|
+
OP_NOTIF: Final = 0x64
|
|
37
|
+
OP_VERIF: Final = 0x65
|
|
38
|
+
OP_VERNOTIF: Final = 0x66
|
|
39
|
+
OP_ELSE: Final = 0x67
|
|
40
|
+
OP_ENDIF: Final = 0x68
|
|
41
|
+
OP_VERIFY: Final = 0x69
|
|
42
|
+
OP_RETURN: Final = 0x6A
|
|
43
|
+
|
|
44
|
+
# stack ops
|
|
45
|
+
OP_TOALTSTACK: Final = 0x6B
|
|
46
|
+
OP_FROMALTSTACK: Final = 0x6C
|
|
47
|
+
OP_2DROP: Final = 0x6D
|
|
48
|
+
OP_2DUP: Final = 0x6E
|
|
49
|
+
OP_3DUP: Final = 0x6F
|
|
50
|
+
OP_2OVER: Final = 0x70
|
|
51
|
+
OP_2ROT: Final = 0x71
|
|
52
|
+
OP_2SWAP: Final = 0x72
|
|
53
|
+
OP_IFDUP: Final = 0x73
|
|
54
|
+
OP_DEPTH: Final = 0x74
|
|
55
|
+
OP_DROP: Final = 0x75
|
|
56
|
+
OP_DUP: Final = 0x76
|
|
57
|
+
OP_NIP: Final = 0x77
|
|
58
|
+
OP_OVER: Final = 0x78
|
|
59
|
+
OP_PICK: Final = 0x79
|
|
60
|
+
OP_ROLL: Final = 0x7A
|
|
61
|
+
OP_ROT: Final = 0x7B
|
|
62
|
+
OP_SWAP: Final = 0x7C
|
|
63
|
+
OP_TUCK: Final = 0x7D
|
|
64
|
+
|
|
65
|
+
# Splice ops - BSV
|
|
66
|
+
OP_CAT: Final = 0x7E
|
|
67
|
+
OP_SPLIT: Final = 0x7F
|
|
68
|
+
OP_NUM2BIN: Final = 0x80
|
|
69
|
+
OP_BIN2NUM: Final = 0x81
|
|
70
|
+
OP_SIZE: Final = 0x82
|
|
71
|
+
# 0x83{131} .. 0x86{134} - transaction invalid for non BSV
|
|
72
|
+
|
|
73
|
+
# Bit logic - BSV
|
|
74
|
+
OP_INVERT: Final = 0x83
|
|
75
|
+
OP_AND: Final = 0x84
|
|
76
|
+
OP_OR: Final = 0x85
|
|
77
|
+
OP_XOR: Final = 0x86
|
|
78
|
+
OP_EQUAL: Final = 0x87
|
|
79
|
+
OP_EQUALVERIFY: Final = 0x88
|
|
80
|
+
OP_RESERVED1: Final = 0x89
|
|
81
|
+
OP_RESERVED2: Final = 0x8A
|
|
82
|
+
|
|
83
|
+
# Artithmetic
|
|
84
|
+
OP_1ADD: Final = 0x8B
|
|
85
|
+
OP_1SUB: Final = 0x8C
|
|
86
|
+
OP_2MUL: Final = 0x8D
|
|
87
|
+
OP_2DIV: Final = 0x8E
|
|
88
|
+
OP_NEGATE: Final = 0x8F
|
|
89
|
+
OP_ABS: Final = 0x90
|
|
90
|
+
OP_NOT: Final = 0x91
|
|
91
|
+
OP_0NOTEQUAL: Final = 0x92
|
|
92
|
+
OP_ADD: Final = 0x93
|
|
93
|
+
OP_SUB: Final = 0x94
|
|
94
|
+
OP_MUL: Final = 0x95
|
|
95
|
+
OP_DIV: Final = 0x96
|
|
96
|
+
OP_MOD: Final = 0x97
|
|
97
|
+
OP_LSHIFT: Final = 0x98
|
|
98
|
+
OP_RSHIFT: Final = 0x99
|
|
99
|
+
|
|
100
|
+
OP_BOOLAND: Final = 0x9A
|
|
101
|
+
OP_BOOLOR: Final = 0x9B
|
|
102
|
+
OP_NUMEQUAL: Final = 0x9C
|
|
103
|
+
OP_NUMEQUALVERIFY: Final = 0x9D
|
|
104
|
+
OP_NUMNOTEQUAL: Final = 0x9E
|
|
105
|
+
OP_LESSTHAN: Final = 0x9F
|
|
106
|
+
OP_GREATERTHAN: Final = 0xA0
|
|
107
|
+
OP_LESSTHANOREQUAL: Final = 0xA1
|
|
108
|
+
OP_GREATERTHANOREQUAL: Final = 0xA2
|
|
109
|
+
OP_MIN: Final = 0xA3
|
|
110
|
+
OP_MAX: Final = 0xA4
|
|
111
|
+
OP_WITHIN: Final = 0xA5
|
|
112
|
+
OP_RIPEMD160: Final = 0xA6
|
|
113
|
+
OP_SHA1: Final = 0xA7
|
|
114
|
+
OP_SHA256: Final = 0xA8
|
|
115
|
+
OP_HASH160: Final = 0xA9
|
|
116
|
+
OP_HASH256: Final = 0xAA
|
|
117
|
+
OP_CODESEPARATOR: Final = 0xAB
|
|
118
|
+
OP_CHECKSIG: Final = 0xAC
|
|
119
|
+
OP_CHECKSIGVERIFY: Final = 0xAD
|
|
120
|
+
OP_CHECKMULTISIG: Final = 0xAE
|
|
121
|
+
OP_CHECKMULTISIGVERIFY: Final = 0xAF
|
|
122
|
+
|
|
123
|
+
# Expansion
|
|
124
|
+
OP_NOP1: Final = 0xB0
|
|
125
|
+
OP_CHECKLOCKTIMEVERIFY: Final = 0xB1
|
|
126
|
+
OP_CHECKSEQUENCEVERIFY: Final = 0xB2
|
tx_engine/engine/util.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
""" Engine utility scripts and constants
|
|
2
|
+
"""
|
|
3
|
+
from typing import List, Final
|
|
4
|
+
|
|
5
|
+
from .engine_types import StackElement
|
|
6
|
+
|
|
7
|
+
# Maximum script number length before Genesis (equal to CScriptNum::MAXIMUM_ELEMENT_SIZE)
|
|
8
|
+
MAX_SCRIPT_NUM_LENGTH_BEFORE_GENESIS: Final = 4
|
|
9
|
+
# Maximum script number length after Genesis
|
|
10
|
+
MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS: Final = 750 * 1000
|
|
11
|
+
# Maximum size that we are using
|
|
12
|
+
MAXIMUM_ELEMENT_SIZE: Final = MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS
|
|
13
|
+
|
|
14
|
+
# Curve constants
|
|
15
|
+
# perhaps we can source these from the secp256k1 library rather than here?
|
|
16
|
+
PRIME_INT: Final = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
|
17
|
+
GROUP_ORDER_INT: Final = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
|
|
18
|
+
HALF_GROUP_ORDER_INT: Final = GROUP_ORDER_INT // 2
|
|
19
|
+
Gx: Final = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
|
|
20
|
+
Gx_bytes: Final = bytes.fromhex('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798')
|
|
21
|
+
Gy: Final = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
|
|
22
|
+
GUncompressed: Final = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def encode_num(num: int) -> bytes:
|
|
26
|
+
""" Encode a number, return a bytearray in little endian
|
|
27
|
+
"""
|
|
28
|
+
if num == 0:
|
|
29
|
+
return b""
|
|
30
|
+
abs_num = abs(num)
|
|
31
|
+
negative = num < 0
|
|
32
|
+
result = bytearray()
|
|
33
|
+
while abs_num:
|
|
34
|
+
result.append(abs_num & 0xFF)
|
|
35
|
+
abs_num >>= 8
|
|
36
|
+
if result[-1] & 0x80:
|
|
37
|
+
if negative:
|
|
38
|
+
result.append(0x80)
|
|
39
|
+
else:
|
|
40
|
+
result.append(0)
|
|
41
|
+
elif negative:
|
|
42
|
+
result[-1] |= 0x80
|
|
43
|
+
return bytes(result)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_minimally_encoded(element, max_element_size=MAXIMUM_ELEMENT_SIZE) -> bool:
|
|
47
|
+
""" Determines if an element is minimally encoded, returns True if it is.
|
|
48
|
+
Code copied from SV codebase for details see:
|
|
49
|
+
file: int_serialization.h, function: IsMinimallyEncoded, line: 98
|
|
50
|
+
"""
|
|
51
|
+
if isinstance(element, int):
|
|
52
|
+
return True
|
|
53
|
+
size = len(element)
|
|
54
|
+
if size > max_element_size:
|
|
55
|
+
return False
|
|
56
|
+
if size > 0:
|
|
57
|
+
elem = element[::-1]
|
|
58
|
+
if elem[0] & 0x7f == 0:
|
|
59
|
+
if size <= 1 or (elem[1] & 0x80 == 0):
|
|
60
|
+
return False
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def decode_num(element: StackElement, check_encoding=False) -> int:
|
|
65
|
+
""" Take a byte(array), return a number
|
|
66
|
+
"""
|
|
67
|
+
if element == b"":
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
if check_encoding and not is_minimally_encoded(element):
|
|
71
|
+
if isinstance(element, bytes):
|
|
72
|
+
raise ValueError(f"Value is not minimally encoded: {element.hex()}")
|
|
73
|
+
raise ValueError(f"Value is not minimally encoded: {element}")
|
|
74
|
+
|
|
75
|
+
if isinstance(element, int):
|
|
76
|
+
return element
|
|
77
|
+
|
|
78
|
+
if isinstance(element, bytes):
|
|
79
|
+
try:
|
|
80
|
+
b = element
|
|
81
|
+
big_endian = b[::-1]
|
|
82
|
+
except TypeError:
|
|
83
|
+
# TypeError: 'int' object is not subscriptable
|
|
84
|
+
return int(element)
|
|
85
|
+
if big_endian[0] & 0x80:
|
|
86
|
+
negative = True
|
|
87
|
+
result = big_endian[0] & 0x7F
|
|
88
|
+
else:
|
|
89
|
+
negative = False
|
|
90
|
+
result = big_endian[0]
|
|
91
|
+
for c in big_endian[1:]:
|
|
92
|
+
result <<= 8
|
|
93
|
+
result += c
|
|
94
|
+
if negative:
|
|
95
|
+
return -result
|
|
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,84 @@
|
|
|
1
|
+
""" This contains the base class for all blockchain interfaces
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Dict, Optional, MutableMapping, Any, List
|
|
6
|
+
|
|
7
|
+
ConfigType = MutableMapping[str, Any]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BlockchainInterface(ABC):
|
|
11
|
+
""" This is a BlockchainInterface abstract base class
|
|
12
|
+
This will need to be extended with the used methods
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def set_config(self, config: ConfigType):
|
|
17
|
+
""" Configures the interface based on the provided config
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get_utxo(self, address: str):
|
|
22
|
+
""" Given the address returns the associated UTXO
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def get_block_count(self) -> int:
|
|
27
|
+
""" Returns the current block height
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get_raw_transaction(self, txid: str) -> Optional[str]:
|
|
32
|
+
""" Given the txid return the transaction
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def get_transaction(self, txid: str) -> Dict:
|
|
37
|
+
""" Given the txid return the transaction as a dictionary
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def broadcast_tx(self, tx: str):
|
|
42
|
+
""" Broadcast a transaction
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def is_testnet(self) -> bool:
|
|
47
|
+
""" Return true if the interface is operating on testnet
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def get_balance(self, address) -> int:
|
|
52
|
+
""" Return the balance associated with the provided address
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def get_best_block_hash(self) -> str:
|
|
57
|
+
""" Return the current best block hash
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def get_tx_out(self, txid: str, txindex: int) -> Dict:
|
|
62
|
+
""" abstract method definition to define the get_tx_out call to an RPC SV node
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def get_block(self, blockhash: str) -> Dict:
|
|
67
|
+
""" Given the blockhash return the block as a dictionary
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def get_merkle_proof(self, block_hash: str, tx_id: str) -> str:
|
|
72
|
+
""" Given the blockhash and tx_id return the merkle proof
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def get_block_header(self, block_hash: str) -> Dict:
|
|
77
|
+
""" Given the block hash return the block header
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def verifyscript(self, scripts: list, stop_on_first_invalid: bool = True, timeout: int = 100) -> List[Any]:
|
|
82
|
+
""" Given an script and context, verify the script
|
|
83
|
+
This call is only available from local RPC interface
|
|
84
|
+
"""
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
""" This creates interfaces to the BSV blockchain
|
|
2
|
+
"""
|
|
3
|
+
from .blockchain_interface import ConfigType
|
|
4
|
+
from .mock_interface import MockInterface
|
|
5
|
+
from .woc_interface import WoCInterface
|
|
6
|
+
from .rpc_interface import RPCInterface
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
INTERFACE_MAPPING = {
|
|
10
|
+
"mock": MockInterface,
|
|
11
|
+
"woc": WoCInterface,
|
|
12
|
+
"rpc": RPCInterface,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InterfaceFactory:
|
|
17
|
+
""" A class for creating interfaces to the BSV blockchain """
|
|
18
|
+
|
|
19
|
+
def set_config(self, config: ConfigType) -> MockInterface | WoCInterface | RPCInterface:
|
|
20
|
+
""" Given a config returns the required configured Interface
|
|
21
|
+
"""
|
|
22
|
+
interface_type = config['interface_type']
|
|
23
|
+
interface = INTERFACE_MAPPING[interface_type]() # type: ignore[abstract]
|
|
24
|
+
interface.set_config(config)
|
|
25
|
+
return interface # type: ignore[return-value]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
interface_factory = InterfaceFactory()
|