Metadata-Version: 2.4
Name: tapehash
Version: 0.1.0
Summary: Simple proof-of-work system that uses many hash algorithms.
Project-URL: Homepage, https://github.com/k98kurz/tapehash
Project-URL: Repository, https://github.com/k98kurz/tapehash
Project-URL: Bug Tracker, https://github.com/k98kurz/tapehash/issues
Author-email: k98kurz <k98kurz@gmail.com>
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: ISC License (ISCL)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# 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

```bash
pip install tapehash
```

An overview follows, but full documentation can be found
[here](https://github.com/k98kurz/tapehash/blob/master/dox.md), generated
automatically by [autodox](https://pypi.org/project/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 defaul 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) -> HasNonceProtocol:`
- `calculate_difficulty(val: bytes) -> int:`
- `calculate_target(difficulty: int) -> int:`
- `check_difficulty(val: 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:

```python
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](https://pypi.org/project/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 `--help` or `-h` flag 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 | tapehashX` is equivalent to `tapehashX --file file.txt`
- All 3 accept a `--from:hex` flag that will parse the preimage as hexadecimal
- All 3 accept these optional output flags (only 1 at a time):
    - `--to:raw` will output the digest as raw bytes
    - `--difficulty` will 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.

## Testing

Testing has thus far been done manually. I may add a unit test suite at some
point, but it seems unnecessary for now.

## License

ISC License

Copyleft (c) 2025 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 copyleft 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.

