Simple proof-of-work system that uses many hash algorithms.
Project description
tapehash
Tapehash is a slapdash proof-of-work system that uses a lot of different hash algorithms and an opcode execution system for ASIC-resistance. This is meant to be a good-enough solution for proof-of-concept systems.
Installation
pip install tapehash
An overview follows, but full documentation can be found here, generated automatically by autodox.
Functions
This package contains three tuneable hashing functions:
tapehash1(preimage: bytes, code_size: int = 20) -> bytes:tapehash2(preimage: bytes, tape_size_multiplier: int = 2) -> bytes:tapehash3(preimage: bytes, code_size: int = 64, tape_size_multiplier: int = 2) -> bytes:
All three have default parameters tuned to require about 0.25-0.3 ms per hash (roughly 250-300x as long as sha256 on the reference hardware).
It also includes the following proof-of-work functions:
work(state: HasNonceProtocol, serialize: Callable, difficulty: int, hash_algo: Callable, max_attempts: int = 10**10) -> HasNonceProtocol:calculate_difficulty(digest: bytes) -> int:calculate_target(difficulty: int) -> int:check_difficulty(digest: bytes, difficulty: int) -> bool:
Difficulty is calculated by dividing 2**256 by the big-endian int value of a
hash, which produces the expected average number of tries to find that hash or
better (smaller). It is a linear difficulty metric. A target for a given
difficulty level can then be calculated by dividing 2**256 by the difficulty.
The work function first calculates this target, then increments the nonce
property of state until the hash_algo(serialize(state)) is less than the
calculated target (unsigned int comparison).
Use
To use for proof-of-work/hashcash, create a class that follows the
HasNonceProtocol contract (must have a settable nonce property) and a
serialization function, then invoke work to find a nonce that meets the
difficulty threshold. To check a received value, serialize it, hash it, and then
call check_difficulty.
For example, it could be used to filter out spam as in the original hashcash proposal:
from dataclasses import dataclass, field
from tapehash import tapehash3, work, check_difficulty, calculate_difficulty
from packify import pack, unpack
_default_diff_threshold = 128 # average number of attempts to find a nonce
@dataclass
class Message:
message: str = field()
nonce: int = field(default=0)
def pack(self) -> bytes:
return pack((self.message, self.nonce))
@classmethod
def unpack(cls, data: bytes) -> 'Message':
return cls(*unpack(data))
def find_nonce(self, difficulty: int = _default_diff_threshold):
work(self, lambda m: m.pack(), difficulty, tapehash3)
def hash(self) -> bytes:
return tapehash3(self.pack())
def check(self, difficulty: int = _default_diff_threshold) -> bool:
return check_difficulty(self.hash(), difficulty)
def difficulty(self) -> int:
return calculate_difficulty(self.hash())
@classmethod
def make(cls, msg: str, difficulty: int = _default_diff_threshold) -> 'Message':
m = cls(msg)
m.find_nonce(difficulty)
return m
def filter(
messages: list[Message], difficulty: int = _default_diff_threshold
) -> list[Message]:
"""Filter out spam messages that do not reach the difficulty threshold."""
return [m for m in messages if m.check(difficulty)]
Then, a message that has the required difficulty can be generated by calling
Message.make, and a batch of received messages can be verified and filtered
using the filter function.
This example uses my serialization package packify for convenience and brevity. Any serialization would do, and a real system would benefit from an optimized serialization function rather than a generalized one.
CLI
Installing this package provides three CLI commands:
- tapehash1
- tapehash2
- tapehash3
These three commands function in the same way, with only the algorithm tuning parameters being somewhat different:
- All 3 accept a
--helpor-hflag to print help text - All 3 accept a preimage from stdin, through a
--preimage {string}parameter, or through a--file {path}parameter- NB:
cat file.txt | tapehashXis equivalent totapehashX --file file.txt
- NB:
- All 3 accept a
--from:hexflag that will parse the preimage as hexadecimal - All 3 accept these optional output flags (only 1 at a time):
--to:rawwill output the digest as raw bytes--difficultywill output the calculated difficulty score of the hash digest--check {int}will output 1 if the digest meets the difficulty threshold {int} or 0 (and an exit code of 2) if it did not- Default behavior is to print the hexadecimal digest
- tapehash1 and tapehash3 accept
--code_size {int}or-cs {int}tuning parameter (default=20 for tapehash1 and 64 for tapehash3) - tapehash2 and tapehash3 accept
--tape_size_multiplier {int}or-tsm {int}tuning parameter (default=2)
An improper invocation will result in an exit value of 1. A proper invocation
will result in an exit code of 0 in all cases except when --check {int} exits
with 2.
Example usage:
# Hash a string
tapehash1 --preimage "hello world"
# Hash from file
tapehash2 --file message.dat
# Hash hex data and check difficulty
tapehash3 --from:hex --check 100
# Use tuning parameters
tapehash1 --preimage "test" --code_size 32
tapehash2 --preimage "test" -tsm 4
Testing
First, clone the repo, set up the virtualenv, and install requirements.
git clone ...
python -m venv venv/
source venv/bin/activate
pip install -r requirements.txt
For windows, replace source venv/bin/activate with source venv/Scripts/activate.
Then run the test suite with the following:
find tests -name test_*.py -print -exec {} \;
# or
python -m unittest discover -s tests -v
There are currently 20 tests testing the three tapehash algorithms, the proof-of-work functionality, and the CLI.
Contributing
Check out the Pycelium discord server. If you experience a problem, please discuss it on the Discord server. All suggestions for improvement are also welcome, and the best place for that is also Discord. If you experience a bug and do not use Discord, open an issue or discussion on Github.
ISC License
Copyright (c) 2026 Jonathan Voss (k98kurz)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tapehash-0.1.1.tar.gz.
File metadata
- Download URL: tapehash-0.1.1.tar.gz
- Upload date:
- Size: 11.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c716bf11b50b4a8a086b9cf801ab2a584932ce709b816a1ae2f6b3d09d1a3ce
|
|
| MD5 |
e5c9b6d3f6eb0bdd7929fb71832d8a0a
|
|
| BLAKE2b-256 |
c962be113456d52b6caeb785c65e18e6bcef1ce9afc40601104acaf6e010bf8a
|
File details
Details for the file tapehash-0.1.1-py3-none-any.whl.
File metadata
- Download URL: tapehash-0.1.1-py3-none-any.whl
- Upload date:
- Size: 8.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a6cf29a810427a28a11462b471f3650eb95fe960f9258dd95833da54a310d8d
|
|
| MD5 |
90e446302515340ed442e374073d3535
|
|
| BLAKE2b-256 |
414bab8190b7027fef3f8b14a10571ad941aba62280bff542b2bbd12a1227d9f
|