tapehash 0.1.0__tar.gz
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.
- tapehash-0.1.0/.gitignore +5 -0
- tapehash-0.1.0/PKG-INFO +168 -0
- tapehash-0.1.0/dox.md +71 -0
- tapehash-0.1.0/license +18 -0
- tapehash-0.1.0/pyproject.toml +32 -0
- tapehash-0.1.0/readme.md +151 -0
- tapehash-0.1.0/tapehash/__init__.py +16 -0
- tapehash-0.1.0/tapehash/algos.py +184 -0
- tapehash-0.1.0/tapehash/cli.py +270 -0
- tapehash-0.1.0/tapehash/work.py +52 -0
tapehash-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tapehash
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simple proof-of-work system that uses many hash algorithms.
|
|
5
|
+
Project-URL: Homepage, https://github.com/k98kurz/tapehash
|
|
6
|
+
Project-URL: Repository, https://github.com/k98kurz/tapehash
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/k98kurz/tapehash/issues
|
|
8
|
+
Author-email: k98kurz <k98kurz@gmail.com>
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Security :: Cryptography
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# tapehash
|
|
19
|
+
|
|
20
|
+
Tapehash is a slapdash proof-of-work system that uses a lot of different hash
|
|
21
|
+
algorithms and an opcode execution system for ASIC-resistance. This is meant to
|
|
22
|
+
be a good-enough solution for proof-of-concept systems.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install tapehash
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
An overview follows, but full documentation can be found
|
|
31
|
+
[here](https://github.com/k98kurz/tapehash/blob/master/dox.md), generated
|
|
32
|
+
automatically by [autodox](https://pypi.org/project/autodox).
|
|
33
|
+
|
|
34
|
+
## Functions
|
|
35
|
+
|
|
36
|
+
This package contains three tuneable hashing functions:
|
|
37
|
+
|
|
38
|
+
- `tapehash1(preimage: bytes, code_size: int = 20) -> bytes:`
|
|
39
|
+
- `tapehash2(preimage: bytes, tape_size_multiplier: int = 2) -> bytes:`
|
|
40
|
+
- `tapehash3(preimage: bytes, code_size: int = 64, tape_size_multiplier: int = 2) -> bytes:`
|
|
41
|
+
|
|
42
|
+
All three have defaul parameters tuned to require about 0.25-0.3 ms per hash
|
|
43
|
+
(roughly 250-300x as long as sha256 on the reference hardware).
|
|
44
|
+
|
|
45
|
+
It also includes the following proof-of-work functions:
|
|
46
|
+
|
|
47
|
+
- `work(state: HasNonceProtocol, serialize: Callable, difficulty: int, hash_algo: Callable) -> HasNonceProtocol:`
|
|
48
|
+
- `calculate_difficulty(val: bytes) -> int:`
|
|
49
|
+
- `calculate_target(difficulty: int) -> int:`
|
|
50
|
+
- `check_difficulty(val: bytes, difficulty: int) -> bool:`
|
|
51
|
+
|
|
52
|
+
Difficulty is calculated by dividing `2**256` by the big-endian int value of a
|
|
53
|
+
hash, which produces the expected average number of tries to find that hash or
|
|
54
|
+
better (smaller). It is a linear difficulty metric. A target for a given
|
|
55
|
+
difficulty level can then be calculated by dividing `2**256` by the difficulty.
|
|
56
|
+
The `work` function first calculates this target, then increments the `nonce`
|
|
57
|
+
property of `state` until the `hash_algo(serialize(state))` is less than the
|
|
58
|
+
calculated target (unsigned int comparison).
|
|
59
|
+
|
|
60
|
+
## Use
|
|
61
|
+
|
|
62
|
+
To use for proof-of-work/hashcash, create a class that follows the
|
|
63
|
+
`HasNonceProtocol` contract (must have a settable `nonce` property) and a
|
|
64
|
+
serialization function, then invoke `work` to find a nonce that meets the
|
|
65
|
+
difficulty threshold. To check a received value, serialize it, hash it, and then
|
|
66
|
+
call `check_difficulty`.
|
|
67
|
+
|
|
68
|
+
For example, it could be used to filter out spam as in the original hashcash proposal:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from dataclasses import dataclass, field
|
|
72
|
+
from tapehash import tapehash3, work, check_difficulty, calculate_difficulty
|
|
73
|
+
from packify import pack, unpack
|
|
74
|
+
|
|
75
|
+
_default_diff_threshold = 128 # average number of attempts to find a nonce
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class Message:
|
|
79
|
+
message: str = field()
|
|
80
|
+
nonce: int = field(default=0)
|
|
81
|
+
def pack(self) -> bytes:
|
|
82
|
+
return pack((self.message, self.nonce))
|
|
83
|
+
@classmethod
|
|
84
|
+
def unpack(cls, data: bytes) -> 'Message':
|
|
85
|
+
return cls(*unpack(data))
|
|
86
|
+
def find_nonce(self, difficulty: int = _default_diff_threshold):
|
|
87
|
+
work(self, lambda m: m.pack(), difficulty, tapehash3)
|
|
88
|
+
def hash(self) -> bytes:
|
|
89
|
+
return tapehash3(self.pack())
|
|
90
|
+
def check(self, difficulty: int = _default_diff_threshold) -> bool:
|
|
91
|
+
return check_difficulty(self.hash(), difficulty)
|
|
92
|
+
def difficulty(self) -> int:
|
|
93
|
+
return calculate_difficulty(self.hash())
|
|
94
|
+
@classmethod
|
|
95
|
+
def make(cls, msg: str, difficulty: int = _default_diff_threshold) -> 'Message':
|
|
96
|
+
m = cls(msg)
|
|
97
|
+
m.find_nonce(difficulty)
|
|
98
|
+
return m
|
|
99
|
+
|
|
100
|
+
def filter(messages: list[Message], difficulty: int = _default_diff_threshold) -> list[Message]:
|
|
101
|
+
"""Filter out spam messages that do not reach the difficulty threshold."""
|
|
102
|
+
return [m for m in messages if m.check(difficulty)]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then, a message that has the required difficulty can be generated by calling
|
|
106
|
+
`Message.make`, and a batch of received messages can be verified and filtered
|
|
107
|
+
using the `filter` function.
|
|
108
|
+
|
|
109
|
+
This example uses my serialization package [packify](https://pypi.org/project/packify)
|
|
110
|
+
for convenience and brevity. Any serialization would do, and a real system would
|
|
111
|
+
benefit from an optimized serialization function rather than a generalized one.
|
|
112
|
+
|
|
113
|
+
## CLI
|
|
114
|
+
|
|
115
|
+
Installing this package provides three CLI commands:
|
|
116
|
+
|
|
117
|
+
- tapehash1
|
|
118
|
+
- tapehash2
|
|
119
|
+
- tapehash3
|
|
120
|
+
|
|
121
|
+
These three commands function in the same way, with only the algorithm tuning
|
|
122
|
+
parameters being somewhat different:
|
|
123
|
+
|
|
124
|
+
- All 3 accept a `--help` or `-h` flag to print help text
|
|
125
|
+
- All 3 accept a preimage from stdin, through a `--preimage {string}` parameter,
|
|
126
|
+
or through a `--file {path}` parameter
|
|
127
|
+
- NB: `cat file.txt | tapehashX` is equivalent to `tapehashX --file file.txt`
|
|
128
|
+
- All 3 accept a `--from:hex` flag that will parse the preimage as hexadecimal
|
|
129
|
+
- All 3 accept these optional output flags (only 1 at a time):
|
|
130
|
+
- `--to:raw` will output the digest as raw bytes
|
|
131
|
+
- `--difficulty` will output the calculated difficulty score of the hash digest
|
|
132
|
+
- `--check {int}` will output 1 if the digest meets the difficulty threshold
|
|
133
|
+
{int} or 0 (and an exit code of 2) if it did not
|
|
134
|
+
- Default behavior is to print the hexadecimal digest
|
|
135
|
+
- tapehash1 and tapehash3 accept `--code_size {int}` or `-cs {int}` tuning
|
|
136
|
+
parameter (default=20 for tapehash1 and 64 for tapehash3)
|
|
137
|
+
- tapehash2 and tapehash3 accept `--tape_size_multiplier {int}` or `-tsm {int}`
|
|
138
|
+
tuning parameter (default=2)
|
|
139
|
+
|
|
140
|
+
An improper invocation will result in an exit value of 1. A proper invocation
|
|
141
|
+
will result in an exit code of 0 in all cases except when `--check {int}` exits
|
|
142
|
+
with 2.
|
|
143
|
+
|
|
144
|
+
## Testing
|
|
145
|
+
|
|
146
|
+
Testing has thus far been done manually. I may add a unit test suite at some
|
|
147
|
+
point, but it seems unnecessary for now.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
ISC License
|
|
152
|
+
|
|
153
|
+
Copyleft (c) 2025 Jonathan Voss (k98kurz)
|
|
154
|
+
|
|
155
|
+
Permission to use, copy, modify, and/or distribute this software
|
|
156
|
+
for any purpose with or without fee is hereby granted, provided
|
|
157
|
+
that the above copyleft notice and this permission notice appear in
|
|
158
|
+
all copies.
|
|
159
|
+
|
|
160
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
161
|
+
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
162
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
163
|
+
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
164
|
+
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
165
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
166
|
+
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
167
|
+
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
168
|
+
|
tapehash-0.1.0/dox.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# tapehash
|
|
2
|
+
|
|
3
|
+
## Classes
|
|
4
|
+
|
|
5
|
+
### `HasNonceProtocol(Protocol)`
|
|
6
|
+
|
|
7
|
+
The HasNonceProtocol requires that an implementation has a settable nonce
|
|
8
|
+
property.
|
|
9
|
+
|
|
10
|
+
#### Properties
|
|
11
|
+
|
|
12
|
+
- nonce
|
|
13
|
+
|
|
14
|
+
## Functions
|
|
15
|
+
|
|
16
|
+
### `tapehash1(preimage: bytes, code_size: int = 20) -> bytes:`
|
|
17
|
+
|
|
18
|
+
Runs the tapehash 1 algorithm on the preimage and returns a 32-byte hash.
|
|
19
|
+
Computational complexity is tuneable via the `code_size` parameter.
|
|
20
|
+
|
|
21
|
+
### `tapehash2(preimage: bytes, tape_size_multiplier: int = 2) -> bytes:`
|
|
22
|
+
|
|
23
|
+
Runs the tapehash2 algorithm on the preimage and returns a 32-byte hash. Memory
|
|
24
|
+
complexity can be tuned via the `tape_size_multiplier` parameter.
|
|
25
|
+
|
|
26
|
+
### `tapehash3(preimage: bytes, code_size: int = 64, tape_size_multiplier: int = 2) -> bytes:`
|
|
27
|
+
|
|
28
|
+
Runs the tapehash3 algorithm on the preimage and returns a 32-byte hash.
|
|
29
|
+
Computational complexity is tuneable via the `code_size` parameter. Memory
|
|
30
|
+
complexity is tuneable via the `tape_size_multiplier` parameter.
|
|
31
|
+
|
|
32
|
+
### `license():`
|
|
33
|
+
|
|
34
|
+
Copyright (c) 2025 Jonathan Voss (k98kurz) Permission to use, copy, modify,
|
|
35
|
+
and/or distribute this software for any purpose with or without fee is hereby
|
|
36
|
+
granted, provided that the above copyright notice and this permission notice
|
|
37
|
+
appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
|
|
38
|
+
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
39
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
40
|
+
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
|
41
|
+
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
42
|
+
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
|
|
43
|
+
USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
44
|
+
|
|
45
|
+
### `work(state: HasNonceProtocol, serialize: Callable, difficulty: int, hash_algo: Callable) -> HasNonceProtocol:`
|
|
46
|
+
|
|
47
|
+
Continually increments `state.nonce` until the difficulty score of
|
|
48
|
+
`hash_algo(serialize(state))` >= target, then returns the updated state.
|
|
49
|
+
|
|
50
|
+
### `calculate_difficulty(val: bytes) -> int:`
|
|
51
|
+
|
|
52
|
+
Calculates the difficulty of a hash by dividing 2**256 (max int) by the supplied
|
|
53
|
+
val interpreted as a big-endian unsigned int. This provides a linear metric that
|
|
54
|
+
represents the expected amount of work (hashes) that have to be computed on
|
|
55
|
+
average to reach the given hash val or better (lower).
|
|
56
|
+
|
|
57
|
+
### `calculate_target(difficulty: int) -> int:`
|
|
58
|
+
|
|
59
|
+
Calculates the target value that a hash must be below to meet the difficulty
|
|
60
|
+
threshold.
|
|
61
|
+
|
|
62
|
+
### `check_difficulty(val: bytes, difficulty: int) -> bool:`
|
|
63
|
+
|
|
64
|
+
Returns True if the val has a difficulty score greater than or equal to the
|
|
65
|
+
supplied difficulty, otherwise False.
|
|
66
|
+
|
|
67
|
+
### `version() -> str:`
|
|
68
|
+
|
|
69
|
+
Returns the current library version.
|
|
70
|
+
|
|
71
|
+
|
tapehash-0.1.0/license
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyleft (c) 2025 Jonathan Voss (k98kurz)
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software
|
|
6
|
+
for any purpose with or without fee is hereby granted, provided
|
|
7
|
+
that the above copyleft notice and this permission notice appear in
|
|
8
|
+
all copies.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
11
|
+
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
12
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
13
|
+
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
14
|
+
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
15
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
16
|
+
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
17
|
+
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
18
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tapehash"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="k98kurz", email="k98kurz@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "Simple proof-of-work system that uses many hash algorithms."
|
|
12
|
+
readme = "readme.md"
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: ISC License (ISCL)",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Topic :: Security :: Cryptography",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
"Homepage" = "https://github.com/k98kurz/tapehash"
|
|
25
|
+
"Repository" = "https://github.com/k98kurz/tapehash"
|
|
26
|
+
"Bug Tracker" = "https://github.com/k98kurz/tapehash/issues"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
tapehash1 = "tapehash.cli:run_tapehash1"
|
|
30
|
+
tapehash2 = "tapehash.cli:run_tapehash2"
|
|
31
|
+
tapehash3 = "tapehash.cli:run_tapehash3"
|
|
32
|
+
|
tapehash-0.1.0/readme.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# tapehash
|
|
2
|
+
|
|
3
|
+
Tapehash is a slapdash proof-of-work system that uses a lot of different hash
|
|
4
|
+
algorithms and an opcode execution system for ASIC-resistance. This is meant to
|
|
5
|
+
be a good-enough solution for proof-of-concept systems.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install tapehash
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
An overview follows, but full documentation can be found
|
|
14
|
+
[here](https://github.com/k98kurz/tapehash/blob/master/dox.md), generated
|
|
15
|
+
automatically by [autodox](https://pypi.org/project/autodox).
|
|
16
|
+
|
|
17
|
+
## Functions
|
|
18
|
+
|
|
19
|
+
This package contains three tuneable hashing functions:
|
|
20
|
+
|
|
21
|
+
- `tapehash1(preimage: bytes, code_size: int = 20) -> bytes:`
|
|
22
|
+
- `tapehash2(preimage: bytes, tape_size_multiplier: int = 2) -> bytes:`
|
|
23
|
+
- `tapehash3(preimage: bytes, code_size: int = 64, tape_size_multiplier: int = 2) -> bytes:`
|
|
24
|
+
|
|
25
|
+
All three have defaul parameters tuned to require about 0.25-0.3 ms per hash
|
|
26
|
+
(roughly 250-300x as long as sha256 on the reference hardware).
|
|
27
|
+
|
|
28
|
+
It also includes the following proof-of-work functions:
|
|
29
|
+
|
|
30
|
+
- `work(state: HasNonceProtocol, serialize: Callable, difficulty: int, hash_algo: Callable) -> HasNonceProtocol:`
|
|
31
|
+
- `calculate_difficulty(val: bytes) -> int:`
|
|
32
|
+
- `calculate_target(difficulty: int) -> int:`
|
|
33
|
+
- `check_difficulty(val: bytes, difficulty: int) -> bool:`
|
|
34
|
+
|
|
35
|
+
Difficulty is calculated by dividing `2**256` by the big-endian int value of a
|
|
36
|
+
hash, which produces the expected average number of tries to find that hash or
|
|
37
|
+
better (smaller). It is a linear difficulty metric. A target for a given
|
|
38
|
+
difficulty level can then be calculated by dividing `2**256` by the difficulty.
|
|
39
|
+
The `work` function first calculates this target, then increments the `nonce`
|
|
40
|
+
property of `state` until the `hash_algo(serialize(state))` is less than the
|
|
41
|
+
calculated target (unsigned int comparison).
|
|
42
|
+
|
|
43
|
+
## Use
|
|
44
|
+
|
|
45
|
+
To use for proof-of-work/hashcash, create a class that follows the
|
|
46
|
+
`HasNonceProtocol` contract (must have a settable `nonce` property) and a
|
|
47
|
+
serialization function, then invoke `work` to find a nonce that meets the
|
|
48
|
+
difficulty threshold. To check a received value, serialize it, hash it, and then
|
|
49
|
+
call `check_difficulty`.
|
|
50
|
+
|
|
51
|
+
For example, it could be used to filter out spam as in the original hashcash proposal:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from dataclasses import dataclass, field
|
|
55
|
+
from tapehash import tapehash3, work, check_difficulty, calculate_difficulty
|
|
56
|
+
from packify import pack, unpack
|
|
57
|
+
|
|
58
|
+
_default_diff_threshold = 128 # average number of attempts to find a nonce
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class Message:
|
|
62
|
+
message: str = field()
|
|
63
|
+
nonce: int = field(default=0)
|
|
64
|
+
def pack(self) -> bytes:
|
|
65
|
+
return pack((self.message, self.nonce))
|
|
66
|
+
@classmethod
|
|
67
|
+
def unpack(cls, data: bytes) -> 'Message':
|
|
68
|
+
return cls(*unpack(data))
|
|
69
|
+
def find_nonce(self, difficulty: int = _default_diff_threshold):
|
|
70
|
+
work(self, lambda m: m.pack(), difficulty, tapehash3)
|
|
71
|
+
def hash(self) -> bytes:
|
|
72
|
+
return tapehash3(self.pack())
|
|
73
|
+
def check(self, difficulty: int = _default_diff_threshold) -> bool:
|
|
74
|
+
return check_difficulty(self.hash(), difficulty)
|
|
75
|
+
def difficulty(self) -> int:
|
|
76
|
+
return calculate_difficulty(self.hash())
|
|
77
|
+
@classmethod
|
|
78
|
+
def make(cls, msg: str, difficulty: int = _default_diff_threshold) -> 'Message':
|
|
79
|
+
m = cls(msg)
|
|
80
|
+
m.find_nonce(difficulty)
|
|
81
|
+
return m
|
|
82
|
+
|
|
83
|
+
def filter(messages: list[Message], difficulty: int = _default_diff_threshold) -> list[Message]:
|
|
84
|
+
"""Filter out spam messages that do not reach the difficulty threshold."""
|
|
85
|
+
return [m for m in messages if m.check(difficulty)]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then, a message that has the required difficulty can be generated by calling
|
|
89
|
+
`Message.make`, and a batch of received messages can be verified and filtered
|
|
90
|
+
using the `filter` function.
|
|
91
|
+
|
|
92
|
+
This example uses my serialization package [packify](https://pypi.org/project/packify)
|
|
93
|
+
for convenience and brevity. Any serialization would do, and a real system would
|
|
94
|
+
benefit from an optimized serialization function rather than a generalized one.
|
|
95
|
+
|
|
96
|
+
## CLI
|
|
97
|
+
|
|
98
|
+
Installing this package provides three CLI commands:
|
|
99
|
+
|
|
100
|
+
- tapehash1
|
|
101
|
+
- tapehash2
|
|
102
|
+
- tapehash3
|
|
103
|
+
|
|
104
|
+
These three commands function in the same way, with only the algorithm tuning
|
|
105
|
+
parameters being somewhat different:
|
|
106
|
+
|
|
107
|
+
- All 3 accept a `--help` or `-h` flag to print help text
|
|
108
|
+
- All 3 accept a preimage from stdin, through a `--preimage {string}` parameter,
|
|
109
|
+
or through a `--file {path}` parameter
|
|
110
|
+
- NB: `cat file.txt | tapehashX` is equivalent to `tapehashX --file file.txt`
|
|
111
|
+
- All 3 accept a `--from:hex` flag that will parse the preimage as hexadecimal
|
|
112
|
+
- All 3 accept these optional output flags (only 1 at a time):
|
|
113
|
+
- `--to:raw` will output the digest as raw bytes
|
|
114
|
+
- `--difficulty` will output the calculated difficulty score of the hash digest
|
|
115
|
+
- `--check {int}` will output 1 if the digest meets the difficulty threshold
|
|
116
|
+
{int} or 0 (and an exit code of 2) if it did not
|
|
117
|
+
- Default behavior is to print the hexadecimal digest
|
|
118
|
+
- tapehash1 and tapehash3 accept `--code_size {int}` or `-cs {int}` tuning
|
|
119
|
+
parameter (default=20 for tapehash1 and 64 for tapehash3)
|
|
120
|
+
- tapehash2 and tapehash3 accept `--tape_size_multiplier {int}` or `-tsm {int}`
|
|
121
|
+
tuning parameter (default=2)
|
|
122
|
+
|
|
123
|
+
An improper invocation will result in an exit value of 1. A proper invocation
|
|
124
|
+
will result in an exit code of 0 in all cases except when `--check {int}` exits
|
|
125
|
+
with 2.
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
Testing has thus far been done manually. I may add a unit test suite at some
|
|
130
|
+
point, but it seems unnecessary for now.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
ISC License
|
|
135
|
+
|
|
136
|
+
Copyleft (c) 2025 Jonathan Voss (k98kurz)
|
|
137
|
+
|
|
138
|
+
Permission to use, copy, modify, and/or distribute this software
|
|
139
|
+
for any purpose with or without fee is hereby granted, provided
|
|
140
|
+
that the above copyleft notice and this permission notice appear in
|
|
141
|
+
all copies.
|
|
142
|
+
|
|
143
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
144
|
+
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
145
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
146
|
+
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
147
|
+
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
148
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
149
|
+
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
150
|
+
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
151
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .algos import tapehash1, tapehash2, tapehash3, license
|
|
2
|
+
from .work import (
|
|
3
|
+
HasNonceProtocol,
|
|
4
|
+
calculate_difficulty,
|
|
5
|
+
calculate_target,
|
|
6
|
+
check_difficulty,
|
|
7
|
+
work
|
|
8
|
+
)
|
|
9
|
+
del algos
|
|
10
|
+
|
|
11
|
+
__version__ = '0.1.0'
|
|
12
|
+
|
|
13
|
+
def version() -> str:
|
|
14
|
+
"""Returns the current library version."""
|
|
15
|
+
return __version__
|
|
16
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from hashlib import (
|
|
2
|
+
sha256, md5,
|
|
3
|
+
shake_128, shake_256,
|
|
4
|
+
sha3_256, sha3_512,
|
|
5
|
+
blake2b, blake2s
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
"""New randomized proof-of-work hashing algorithms inspired roughly by
|
|
10
|
+
XMR's RandomX but much simpler and shoddier.
|
|
11
|
+
From https://pastebin.com/xY3B3K8S
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def tapehash1(preimage: bytes, code_size: int = 20) -> bytes:
|
|
15
|
+
"""Runs the tapehash 1 algorithm on the preimage and returns a
|
|
16
|
+
32-byte hash. Computational complexity is tuneable via the
|
|
17
|
+
`code_size` parameter.
|
|
18
|
+
"""
|
|
19
|
+
if type(preimage) is not bytes:
|
|
20
|
+
raise TypeError('preimage must be bytes')
|
|
21
|
+
if type(code_size) is not int:
|
|
22
|
+
raise TypeError('code_size must be an int between 1 and 65,536')
|
|
23
|
+
if code_size <= 0 or code_size > 65_536:
|
|
24
|
+
raise ValueError('code_size must be an int between 1 and 65,536')
|
|
25
|
+
|
|
26
|
+
# generate the code and the tape
|
|
27
|
+
code = shake_256(preimage).digest(code_size)
|
|
28
|
+
tape = bytearray(blake2b(preimage).digest())
|
|
29
|
+
|
|
30
|
+
# run the program
|
|
31
|
+
for i in range(0, len(code)):
|
|
32
|
+
opcode = code[i] >> 4
|
|
33
|
+
#pointer = ((code[i] << 4) % 256) >> 4
|
|
34
|
+
pointer = code[i] & 0b00001111
|
|
35
|
+
double_pointer = int.from_bytes(code[i:i+2], 'big') % 2
|
|
36
|
+
|
|
37
|
+
tape = execute_opcode(opcode, pointer, tape)
|
|
38
|
+
tape = execute_opcode(opcode, pointer + 16, tape)
|
|
39
|
+
tape = execute_opcode(opcode, pointer + 32, tape)
|
|
40
|
+
tape = execute_opcode(opcode, pointer + 48, tape)
|
|
41
|
+
|
|
42
|
+
return sha256(tape).digest()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def tapehash2(preimage: bytes, tape_size_multiplier: int = 2) -> bytes:
|
|
46
|
+
"""Runs the tapehash2 algorithm on the preimage and returns a
|
|
47
|
+
32-byte hash. Memory complexity can be tuned via the
|
|
48
|
+
`tape_size_multiplier` parameter.
|
|
49
|
+
"""
|
|
50
|
+
if type(preimage) is not bytes:
|
|
51
|
+
raise TypeError('preimage must be bytes')
|
|
52
|
+
if type(tape_size_multiplier) is not int:
|
|
53
|
+
raise TypeError('tape_size_multiplier must be an int between 1 and 65,536')
|
|
54
|
+
if tape_size_multiplier <= 0 or tape_size_multiplier > 65_536:
|
|
55
|
+
raise ValueError('tape_size_multiplier must be an int between 1 and 65,536')
|
|
56
|
+
|
|
57
|
+
# generate the code and the tape
|
|
58
|
+
code = blake2b(preimage).digest()
|
|
59
|
+
tape = bytearray(shake_256(preimage).digest(tape_size_multiplier * 32))
|
|
60
|
+
|
|
61
|
+
# run the program
|
|
62
|
+
for i in range(0, len(code), 2):
|
|
63
|
+
opcode = code[i] >> 4
|
|
64
|
+
#pointer = ((code[i] << 4) % 256) >> 4
|
|
65
|
+
pointer = code[i] & 0b00001111
|
|
66
|
+
double_pointer = int.from_bytes(code[i:i+2], 'big') % tape_size_multiplier
|
|
67
|
+
|
|
68
|
+
tape = execute_opcode(opcode, pointer + double_pointer * 32, tape)
|
|
69
|
+
tape = execute_opcode(opcode, pointer + 16 + double_pointer * 32, tape)
|
|
70
|
+
|
|
71
|
+
return sha256(tape).digest()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def tapehash3(
|
|
75
|
+
preimage: bytes, code_size: int = 64, tape_size_multiplier: int = 2
|
|
76
|
+
) -> bytes:
|
|
77
|
+
"""Runs the tapehash3 algorithm on the preimage and returns a
|
|
78
|
+
32-byte hash. Computational complexity is tuneable via the
|
|
79
|
+
`code_size` parameter. Memory complexity is tuneable via the
|
|
80
|
+
`tape_size_multiplier` parameter.
|
|
81
|
+
"""
|
|
82
|
+
if type(preimage) is not bytes:
|
|
83
|
+
raise TypeError('preimage must be bytes')
|
|
84
|
+
if type(code_size) is not int:
|
|
85
|
+
raise TypeError('code_size must be an int between 1 and 65,536')
|
|
86
|
+
if code_size <= 0 or code_size > 65_536:
|
|
87
|
+
raise ValueError('code_size must be an int between 1 and 65,536')
|
|
88
|
+
if type(tape_size_multiplier) is not int:
|
|
89
|
+
raise TypeError('tape_size_multiplier must be an int between 1 and 65,536')
|
|
90
|
+
if tape_size_multiplier <= 0 or tape_size_multiplier > 65_536:
|
|
91
|
+
raise ValueError('tape_size_multiplier must be an int between 1 and 65,536')
|
|
92
|
+
|
|
93
|
+
# generate the code and the tape
|
|
94
|
+
code = shake_256(preimage).digest(code_size)
|
|
95
|
+
tape = bytearray(shake_256(preimage).digest(tape_size_multiplier * 32))
|
|
96
|
+
|
|
97
|
+
# run the program
|
|
98
|
+
for i in range(0, len(code), 2):
|
|
99
|
+
opcode = code[i] >> 4
|
|
100
|
+
#pointer = ((code[i] << 4) % 256) >> 4
|
|
101
|
+
pointer = code[i] & 0b00001111
|
|
102
|
+
double_pointer = int.from_bytes(code[i:i+2], 'big') % tape_size_multiplier
|
|
103
|
+
|
|
104
|
+
tape = execute_opcode(opcode, pointer + double_pointer * 32, tape)
|
|
105
|
+
tape = execute_opcode(opcode, pointer + 16 + double_pointer * 32, tape)
|
|
106
|
+
|
|
107
|
+
return sha256(tape).digest()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def rotate_tape(tape: bytearray, pointer: int) -> bytes:
|
|
112
|
+
"""Rotates the tape so that the pointer-indexed byte is first."""
|
|
113
|
+
if not isinstance(tape, bytearray):
|
|
114
|
+
raise TypeError('tape must be bytearray')
|
|
115
|
+
if not isinstance(pointer, int):
|
|
116
|
+
raise TypeError('pointer must be int')
|
|
117
|
+
if len(tape) < 1:
|
|
118
|
+
raise ValueError('tape must not be empty')
|
|
119
|
+
if pointer < 0 or pointer >= len(tape):
|
|
120
|
+
raise ValueError('pointer must be a valid index of tape')
|
|
121
|
+
|
|
122
|
+
new_tape = tape[pointer:]
|
|
123
|
+
new_tape.extend(tape[:pointer])
|
|
124
|
+
return bytes(new_tape)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def execute_opcode(opcode: int, pointer: int, tape: bytearray) -> bytearray:
|
|
128
|
+
"""Execute a single opcode."""
|
|
129
|
+
if type(opcode) is not int:
|
|
130
|
+
raise TypeError('opcode must be an int')
|
|
131
|
+
if type(pointer) is not int:
|
|
132
|
+
raise TypeError('pointer must be an int')
|
|
133
|
+
if type(tape) is not bytearray:
|
|
134
|
+
raise TypeError('tape must be bytearray')
|
|
135
|
+
if opcode < 0 or opcode > 15:
|
|
136
|
+
raise ValueError('opcode must be between 0 and 15')
|
|
137
|
+
if pointer < 0 or pointer >= len(tape):
|
|
138
|
+
raise ValueError('pointer must be a valid index of tape')
|
|
139
|
+
|
|
140
|
+
operations = {
|
|
141
|
+
0: lambda data: data, # no op
|
|
142
|
+
1: lambda data: (data + 1) % 256,
|
|
143
|
+
2: lambda data: data - 1 if data > 0 else 255,
|
|
144
|
+
3: lambda data: data >> 1,
|
|
145
|
+
4: lambda data: (data << 1) % 256,
|
|
146
|
+
5: lambda data: data ^ 255,
|
|
147
|
+
6: lambda data: (data * 2) % 256,
|
|
148
|
+
7: lambda data: (data ** 2) % 256,
|
|
149
|
+
8: lambda data: (data // 2) % 256,
|
|
150
|
+
9: lambda data: ((data << 4) % 256) | (data >> 4),
|
|
151
|
+
10: lambda data: sha256(data).digest()[data[0] % 32],
|
|
152
|
+
11: lambda data: md5(data).digest()[data[0] % 16],
|
|
153
|
+
12: lambda data: shake_128(data).digest(data[0] + 1)[data[0]],
|
|
154
|
+
13: lambda data: sha3_256(data).digest()[data[0] % 32],
|
|
155
|
+
14: lambda data: sha3_512(data).digest()[data[0] % 64],
|
|
156
|
+
15: lambda data: blake2s(data).digest()[data[0] % 32]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if opcode < 10:
|
|
160
|
+
tape[pointer] = operations[opcode](tape[pointer])
|
|
161
|
+
else:
|
|
162
|
+
tape[pointer] = operations[opcode](rotate_tape(tape, pointer))
|
|
163
|
+
return tape
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def license():
|
|
167
|
+
"""Copyright (c) 2025 Jonathan Voss (k98kurz)
|
|
168
|
+
|
|
169
|
+
Permission to use, copy, modify, and/or distribute this software
|
|
170
|
+
for any purpose with or without fee is hereby granted, provided
|
|
171
|
+
that the above copyright notice and this permission notice appear in
|
|
172
|
+
all copies.
|
|
173
|
+
|
|
174
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
175
|
+
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
176
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
177
|
+
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
178
|
+
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
179
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
180
|
+
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
181
|
+
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
182
|
+
"""
|
|
183
|
+
return license.__doc__
|
|
184
|
+
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from sys import argv, stdin, stdout
|
|
2
|
+
from .algos import tapehash1, tapehash2, tapehash3
|
|
3
|
+
from .work import work, check_difficulty, calculate_difficulty
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def help_tapehash1():
|
|
7
|
+
print('Use: command | tapehash1 OR tapehash1 --preimage {str} OR tapehash1 --file path/to/file')
|
|
8
|
+
print('\t--from:hex - parse preimage as hexadecimal')
|
|
9
|
+
print('\t--code_size {int} - set the code_size parameter (default=1024)')
|
|
10
|
+
print('\t-cs {int} - set the code_size parameter (default=1024)')
|
|
11
|
+
print('\t--to:raw - write the raw hash digest bytes to stdout')
|
|
12
|
+
print('\t--difficulty - output the difficulty of the hash digest')
|
|
13
|
+
print('\t--check {int} - outputs 1 if the hash digest meets the ' +
|
|
14
|
+
'difficulty threshold; outputs 0 and exits with error code 2 '+
|
|
15
|
+
'if it does not'
|
|
16
|
+
)
|
|
17
|
+
print('\nOnly 1 of --to:raw --difficulty --check can be used at a time')
|
|
18
|
+
|
|
19
|
+
def run_tapehash1() -> None:
|
|
20
|
+
preimage = b''
|
|
21
|
+
code_size = 20
|
|
22
|
+
|
|
23
|
+
if not stdin.isatty():
|
|
24
|
+
preimage = stdin.buffer.read()
|
|
25
|
+
|
|
26
|
+
if len(argv) > 1:
|
|
27
|
+
if '--help' in argv or '-h' in argv:
|
|
28
|
+
return help_tapehash1()
|
|
29
|
+
|
|
30
|
+
if '--preimage' in argv:
|
|
31
|
+
idx = argv.index('--preimage')
|
|
32
|
+
if len(argv) < idx + 2:
|
|
33
|
+
print("must supply data after --preimage")
|
|
34
|
+
exit(1)
|
|
35
|
+
preimage = argv[idx+1].encode('utf-8')
|
|
36
|
+
|
|
37
|
+
if '--file' in argv:
|
|
38
|
+
idx = argv.index('--file')
|
|
39
|
+
if len(argv) < idx + 2:
|
|
40
|
+
print("must supply path after --file")
|
|
41
|
+
exit(1)
|
|
42
|
+
with open(argv[idx+1], 'rb') as f:
|
|
43
|
+
preimage = f.read()
|
|
44
|
+
|
|
45
|
+
if '--from:hex' in argv:
|
|
46
|
+
preimage = bytes.fromhex(preimage.decode('utf-8'))
|
|
47
|
+
|
|
48
|
+
if '--code_size' in argv:
|
|
49
|
+
idx = argv.index('--code_size')
|
|
50
|
+
if len(argv) < idx + 2:
|
|
51
|
+
print("must supply integer after --code_size")
|
|
52
|
+
exit(1)
|
|
53
|
+
code_size = int(argv[idx+1])
|
|
54
|
+
|
|
55
|
+
if '-cs' in argv:
|
|
56
|
+
idx = argv.index('-cs')
|
|
57
|
+
if len(argv) < idx + 2:
|
|
58
|
+
print("must supply integer after -cs")
|
|
59
|
+
exit(1)
|
|
60
|
+
code_size = int(argv[idx+1])
|
|
61
|
+
|
|
62
|
+
if '--to:raw' in argv:
|
|
63
|
+
stdout.buffer.write(tapehash1(preimage, code_size))
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
if '--difficulty' in argv:
|
|
67
|
+
diff = calculate_difficulty(tapehash1(preimage, code_size))
|
|
68
|
+
print(diff)
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
if '--check' in argv:
|
|
72
|
+
idx = argv.index('--check')
|
|
73
|
+
if len(argv) < idx + 2:
|
|
74
|
+
print("must supply integer after --check")
|
|
75
|
+
exit(1)
|
|
76
|
+
target = int(argv[idx+1])
|
|
77
|
+
digest = tapehash1(preimage, code_size)
|
|
78
|
+
if check_difficulty(digest, target):
|
|
79
|
+
print(1)
|
|
80
|
+
return
|
|
81
|
+
else:
|
|
82
|
+
print(0)
|
|
83
|
+
exit(2)
|
|
84
|
+
|
|
85
|
+
print(tapehash1(preimage, code_size).hex())
|
|
86
|
+
|
|
87
|
+
def help_tapehash2():
|
|
88
|
+
print('Use: command | tapehash1 OR tapehash1 --preimage {str} OR tapehash2 --file path/to/file')
|
|
89
|
+
print('\t--from:hex - parse preimage as hexadecimal')
|
|
90
|
+
print('\t--tape_size_multiplier {int} - set the tape_size_multiplier' +
|
|
91
|
+
' parameter (default=1024)'
|
|
92
|
+
)
|
|
93
|
+
print('\t-tsm {int} - set the tape_size_multiplier parameter (default=1024)')
|
|
94
|
+
print('\t--to:raw - write the raw hash digest bytes to stdout')
|
|
95
|
+
print('\t--difficulty - output the difficulty of the hash digest')
|
|
96
|
+
print('\t--check {int} - outputs 1 if the hash digest meets the ' +
|
|
97
|
+
'difficulty threshold; outputs 0 and exits with error code 2 '+
|
|
98
|
+
'if it does not'
|
|
99
|
+
)
|
|
100
|
+
print('\nOnly 1 of --to:raw --difficulty --check can be used at a time')
|
|
101
|
+
|
|
102
|
+
def run_tapehash2() -> None:
|
|
103
|
+
preimage = b''
|
|
104
|
+
tape_size_multiplier = 2
|
|
105
|
+
|
|
106
|
+
if not stdin.isatty():
|
|
107
|
+
preimage = stdin.buffer.read()
|
|
108
|
+
|
|
109
|
+
if len(argv) > 1:
|
|
110
|
+
if '--help' in argv or '-h' in argv:
|
|
111
|
+
return help_tapehash2()
|
|
112
|
+
|
|
113
|
+
if '--preimage' in argv:
|
|
114
|
+
idx = argv.index('--preimage')
|
|
115
|
+
if len(argv) < idx + 2:
|
|
116
|
+
print("must supply data after --preimage")
|
|
117
|
+
exit(1)
|
|
118
|
+
preimage = argv[idx+1].encode('utf-8')
|
|
119
|
+
|
|
120
|
+
if '--file' in argv:
|
|
121
|
+
idx = argv.index('--file')
|
|
122
|
+
if len(argv) < idx + 2:
|
|
123
|
+
print("must supply path after --file")
|
|
124
|
+
exit(1)
|
|
125
|
+
with open(argv[idx+1], 'rb') as f:
|
|
126
|
+
preimage = f.read()
|
|
127
|
+
|
|
128
|
+
if '--from:hex' in argv:
|
|
129
|
+
preimage = bytes.fromhex(preimage.decode('utf-8'))
|
|
130
|
+
|
|
131
|
+
if '--tape_size_multiplier' in argv:
|
|
132
|
+
idx = argv.index('--tape_size_multiplier')
|
|
133
|
+
if len(argv) < idx + 2:
|
|
134
|
+
print("must supply integer after --tape_size_multiplier")
|
|
135
|
+
exit(1)
|
|
136
|
+
tape_size_multiplier = int(argv[idx+1])
|
|
137
|
+
|
|
138
|
+
if '-tsm' in argv:
|
|
139
|
+
idx = argv.index('-tsm')
|
|
140
|
+
if len(argv) < idx + 2:
|
|
141
|
+
print("must supply integer after -tsm")
|
|
142
|
+
exit(1)
|
|
143
|
+
tape_size_multiplier = int(argv[idx+1])
|
|
144
|
+
|
|
145
|
+
if '--to:raw' in argv:
|
|
146
|
+
stdout.buffer.write(tapehash2(preimage, tape_size_multiplier))
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
if '--difficulty' in argv:
|
|
150
|
+
diff = calculate_difficulty(tapehash2(preimage, tape_size_multiplier))
|
|
151
|
+
print(diff)
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
if '--check' in argv:
|
|
155
|
+
idx = argv.index('--check')
|
|
156
|
+
if len(argv) < idx + 2:
|
|
157
|
+
print("must supply integer after --check")
|
|
158
|
+
exit(1)
|
|
159
|
+
target = int(argv[idx+1])
|
|
160
|
+
digest = tapehash2(preimage, tape_size_multiplier)
|
|
161
|
+
if check_difficulty(digest, target):
|
|
162
|
+
print(1)
|
|
163
|
+
return
|
|
164
|
+
else:
|
|
165
|
+
print(0)
|
|
166
|
+
exit(2)
|
|
167
|
+
|
|
168
|
+
print(tapehash2(preimage, tape_size_multiplier).hex())
|
|
169
|
+
|
|
170
|
+
def help_tapehash3():
|
|
171
|
+
print('Use: command | tapehash1 OR tapehash1 --preimage {str} OR tapehash3 --file path/to/file')
|
|
172
|
+
print('\t--from:hex - parse preimage as hexadecimal')
|
|
173
|
+
print('\t--code_size {int} - set the code_size parameter (default=1024)')
|
|
174
|
+
print('\t-cs {int} - set the code_size parameter (default=1024)')
|
|
175
|
+
print('\t--tape_size_multiplier {int} - set the tape_size_multiplier' +
|
|
176
|
+
' parameter (default=1024)'
|
|
177
|
+
)
|
|
178
|
+
print('\t-tsm {int} - set the tape_size_multiplier parameter (default=1024)')
|
|
179
|
+
print('\t--to:raw - write the raw hash digest bytes to stdout')
|
|
180
|
+
print('\t--difficulty - output the difficulty of the hash digest')
|
|
181
|
+
print('\t--check {int} - outputs 1 if the hash digest meets the ' +
|
|
182
|
+
'difficulty threshold; outputs 0 and exits with error code 2 '+
|
|
183
|
+
'if it does not'
|
|
184
|
+
)
|
|
185
|
+
print('\nOnly 1 of --to:raw --difficulty --check can be used at a time')
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def run_tapehash3() -> None:
|
|
189
|
+
preimage = b''
|
|
190
|
+
code_size = 64
|
|
191
|
+
tape_size_multiplier = 2
|
|
192
|
+
|
|
193
|
+
if not stdin.isatty():
|
|
194
|
+
preimage = stdin.buffer.read()
|
|
195
|
+
|
|
196
|
+
if len(argv) > 1:
|
|
197
|
+
if '--help' in argv or '-h' in argv:
|
|
198
|
+
return help_tapehash2()
|
|
199
|
+
|
|
200
|
+
if '--preimage' in argv:
|
|
201
|
+
idx = argv.index('--preimage')
|
|
202
|
+
if len(argv) < idx + 2:
|
|
203
|
+
print("must supply data after --preimage")
|
|
204
|
+
exit(1)
|
|
205
|
+
preimage = argv[idx+1].encode('utf-8')
|
|
206
|
+
|
|
207
|
+
if '--file' in argv:
|
|
208
|
+
idx = argv.index('--file')
|
|
209
|
+
if len(argv) < idx + 2:
|
|
210
|
+
print("must supply path after --file")
|
|
211
|
+
exit(1)
|
|
212
|
+
with open(argv[idx+1], 'rb') as f:
|
|
213
|
+
preimage = f.read()
|
|
214
|
+
|
|
215
|
+
if '--from:hex' in argv:
|
|
216
|
+
preimage = bytes.fromhex(preimage.decode('utf-8'))
|
|
217
|
+
|
|
218
|
+
if '--code_size' in argv:
|
|
219
|
+
idx = argv.index('--code_size')
|
|
220
|
+
if len(argv) < idx + 2:
|
|
221
|
+
print("must supply integer after --code_size")
|
|
222
|
+
exit(1)
|
|
223
|
+
code_size = int(argv[idx+1])
|
|
224
|
+
|
|
225
|
+
if '-cs' in argv:
|
|
226
|
+
idx = argv.index('-cs')
|
|
227
|
+
if len(argv) < idx + 2:
|
|
228
|
+
print("must supply integer after -cs")
|
|
229
|
+
exit(1)
|
|
230
|
+
code_size = int(argv[idx+1])
|
|
231
|
+
|
|
232
|
+
if '--tape_size_multiplier' in argv:
|
|
233
|
+
idx = argv.index('--tape_size_multiplier')
|
|
234
|
+
if len(argv) < idx + 2:
|
|
235
|
+
print("must supply integer after --tape_size_multiplier")
|
|
236
|
+
exit(1)
|
|
237
|
+
tape_size_multiplier = int(argv[idx+1])
|
|
238
|
+
|
|
239
|
+
if '-tsm' in argv:
|
|
240
|
+
idx = argv.index('-tsm')
|
|
241
|
+
if len(argv) < idx + 2:
|
|
242
|
+
print("must supply integer after -tsm")
|
|
243
|
+
exit(1)
|
|
244
|
+
tape_size_multiplier = int(argv[idx+1])
|
|
245
|
+
|
|
246
|
+
if '--to:raw' in argv:
|
|
247
|
+
stdout.buffer.write(tapehash3(preimage, code_size, tape_size_multiplier))
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
if '--difficulty' in argv:
|
|
251
|
+
diff = calculate_difficulty(tapehash3(preimage, code_size, tape_size_multiplier))
|
|
252
|
+
print(diff)
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
if '--check' in argv:
|
|
256
|
+
idx = argv.index('--check')
|
|
257
|
+
if len(argv) < idx + 2:
|
|
258
|
+
print("must supply integer after --check")
|
|
259
|
+
exit(1)
|
|
260
|
+
target = int(argv[idx+1])
|
|
261
|
+
digest = tapehash1(preimage, code_size, tape_size_multiplier)
|
|
262
|
+
if check_difficulty(digest, target):
|
|
263
|
+
print(1)
|
|
264
|
+
return
|
|
265
|
+
else:
|
|
266
|
+
print(0)
|
|
267
|
+
exit(2)
|
|
268
|
+
|
|
269
|
+
print(tapehash3(preimage, code_size, tape_size_multiplier).hex())
|
|
270
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Protocol, Callable
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HasNonceProtocol(Protocol):
|
|
6
|
+
"""The HasNonceProtocol requires that an implementation has a settable
|
|
7
|
+
nonce property.
|
|
8
|
+
"""
|
|
9
|
+
@property
|
|
10
|
+
def nonce(self) -> int:
|
|
11
|
+
...
|
|
12
|
+
@nonce.setter
|
|
13
|
+
def nonce(self, val: int):
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
def calculate_difficulty(val: bytes) -> int:
|
|
17
|
+
"""Calculates the difficulty of a hash by dividing 2**256 (max int)
|
|
18
|
+
by the supplied val interpreted as a big-endian unsigned int.
|
|
19
|
+
This provides a linear metric that represents the expected
|
|
20
|
+
amount of work (hashes) that have to be computed on average to
|
|
21
|
+
reach the given hash val or better (lower).
|
|
22
|
+
"""
|
|
23
|
+
val = int.from_bytes(val, 'big')
|
|
24
|
+
if not val:
|
|
25
|
+
return 2**256
|
|
26
|
+
return 2**256 // val
|
|
27
|
+
|
|
28
|
+
def calculate_target(difficulty: int) -> int:
|
|
29
|
+
"""Calculates the target value that a hash must be below to meet
|
|
30
|
+
the difficulty threshold.
|
|
31
|
+
"""
|
|
32
|
+
return 2**256 if not difficulty else 2**256 // difficulty
|
|
33
|
+
|
|
34
|
+
def check_difficulty(val: bytes, difficulty: int) -> bool:
|
|
35
|
+
"""Returns True if the val has a difficulty score greater than or
|
|
36
|
+
equal to the supplied difficulty, otherwise False.
|
|
37
|
+
"""
|
|
38
|
+
return calculate_difficulty(val) >= difficulty
|
|
39
|
+
|
|
40
|
+
def work(
|
|
41
|
+
state: HasNonceProtocol, serialize: Callable[[HasNonceProtocol], bytes],
|
|
42
|
+
difficulty: int, hash_algo: Callable[[bytes], bytes]
|
|
43
|
+
) -> HasNonceProtocol:
|
|
44
|
+
"""Continually increments `state.nonce` until the difficulty score of
|
|
45
|
+
`hash_algo(serialize(state))` >= target, then returns the updated
|
|
46
|
+
state.
|
|
47
|
+
"""
|
|
48
|
+
target = calculate_target(difficulty)
|
|
49
|
+
while int.from_bytes(hash_algo(serialize(state)), 'big') > target:
|
|
50
|
+
state.nonce += 1
|
|
51
|
+
return state
|
|
52
|
+
|