A Java port of IOTA's Curl-P-27 hash function and Proof-of-Work miner.
Curl is the ternary cryptographic primitive used by the legacy IOTA protocol. It is a sponge function, from the same construction family as SHA-3 / Keccak, but it operates on balanced ternary rather than binary. This repository provides a dependency-free implementation of both the hash and the bit-sliced Proof-of-Work search.
Status: reference / educational implementation. It mirrors the original C
reference behaviour and is checked against known hash and mining vectors (see
src/test). It is not intended as a hardened or
audited production crypto library.
- Concepts
- Features
- Requirements
- Build and test
- Usage
- How it works
- Transaction layout
- Project structure
- References
- License
Three terms are enough to follow the rest of this document:
| Term | Meaning |
|---|---|
| trit | A ternary digit with value -1, 0, or +1 (balanced ternary). |
| tryte | A group of 3 trits, encoding one of 27 values. Written as a character from the alphabet 9ABCDEFGHIJKLMNOPQRSTUVWXYZ, where 9 = 0 and A…Z = 1…26. |
MWM (minWeightMagnitude) |
The Proof-of-Work difficulty: how many trailing trits of the hash must be 0. Each additional unit raises the expected work exponentially. The IOTA reference default is 13. |
Fixed sizes used throughout the code:
- A Curl hash is
81trytes, or243trits (IOTACURL_HASH_SZ). - The Curl state is
729trits, three times the hash width (IOTACURL_3STATE). - A full IOTA transaction is
2673trytes (TX_LENGTH).
- Curl-P-27 hash: absorb / transform / squeeze sponge over balanced ternary.
- Proof-of-Work miner: searches for a nonce whose hash meets the requested MWM.
- Bit-sliced transform: packs 32 nonce candidates per
longand tests them in a single transform pass via SIMD-within-a-register logic. - Single- and multi-threaded mining. The multi-threaded search is deterministic: it returns the smallest valid nonce, identical to the single-threaded result, regardless of thread count.
- Boundary validation: inputs are checked against the tryte alphabet and the
expected length, failing fast with
IllegalArgumentExceptioninstead of producing garbage or anArrayIndexOutOfBoundsException. - No runtime dependencies: JUnit is used only in the test scope.
- Java 8 (the project targets
1.8). - Maven 3.x to build and run the test suite.
No third-party runtime dependencies are required.
# Compile
mvn clean compile
# Run the test suite (hash and mining characterization vectors)
mvn test
# Produce the artifact in target/
mvn packageIotaCurlHash.iotaCurlHash(...) is a static, thread-safe entry point. Each call
builds its own internal state, so concurrent callers do not interfere.
import com.iota.curl.IotaCurlHash;
// `tx` is a tryte string ([9A-Z]); `len` is how many trytes to absorb.
String tx = "..."; // e.g. a 2673-tryte transaction
String hash = IotaCurlHash.iotaCurlHash(tx, tx.length());
System.out.println("hash: " + hash); // 81 trytesNote: an instance of IotaCurlHash carries mutable state and is not
thread-safe. Always go through the static iotaCurlHash(...) method for
concurrent use.
import com.iota.curl.IotaCurlMiner;
final IotaCurlMiner miner = new IotaCurlMiner();
// `in` must be exactly IotaCurlMiner.TX_LENGTH (2673) trytes of [9A-Z].
final int minWeightMagnitude = 13;
// Multi-threaded: deterministic smallest-nonce search.
String mined = miner.iotaCurlProofOfWork(in, minWeightMagnitude);
// Single-threaded equivalent (same result, one thread):
String minedSingle = miner.doCurlPowSingleThread(in, minWeightMagnitude);
System.out.println("mined tx: " + mined);iotaCurlProofOfWork wraps the multi-threaded search and converts the checked
concurrency exceptions into an unchecked IllegalStateException (preserving the
cause and restoring the interrupt flag), so callers do not have to handle
ExecutionException or InterruptedException directly.
The Miner class is a runnable
demo / CLI.
# Compile first
mvn clean compile
# Run: java -cp target/classes com.iota.curl.miner.Miner TX_TRYTES [minWeightMagnitude]
java -cp target/classes com.iota.curl.miner.Miner "<2673-tryte transaction>" 13Arguments:
| Argument | Required | Description |
|---|---|---|
TX_TRYTES |
yes | The raw transaction, exactly 2673 trytes, characters [9A-Z]. |
minWeightMagnitude |
no | PoW difficulty; defaults to 13. Must be > 0. |
Running with no arguments prints usage. The tool validates length and alphabet,
mines the transaction, then prints the approvalNonce, the full mined
transaction, and the hash of the result.
Hashing proceeds in three phases:
- Absorb: the input trytes are converted to trits and mixed into the state
243trits at a time; after each block the state is run through the Curl transform. - Transform:
27rounds (hence Curl-P-27). Each round rewrites every trit as a Curl S-box of two trits selected by a fixed index permutation that splits the state at its midpoint (PIVOT = 364). The S-box is a lookup intoTRUTH_TABLE; the boolean derivation of the same table lives inIotaCurlUtils#binaryTruth, kept as a cross-check rather than on the hot path. - Squeeze: the first
243trits of the final state are read out as the81-tryte digest.
The miner runs the same permutation as the scalar hash, but stores trits two
bits at a time inside longs. Because a long holds 32 × 2 bits, 32 candidate
nonces are evaluated per transform using the lc() and ld() bit-logic helpers
and the LMASK1/2/3 masks. The search:
- precomputes a
midStatefrom the immutable transaction header once (powInit), - then sweeps blocks of 32 nonces, absorbing the trunk and branch hashes and
checking whether the last
MWMtrits of the result are all0.
Worker threads share an atomic block cursor and an atomic best nonce. A worker
stops requesting new blocks once the next offset is ≥ best, guaranteeing every
block that could still hold a smaller nonce is searched. The multi-threaded
result is therefore bit-for-bit identical to the single-threaded smallest-nonce
search; see MinerTest.
A 2673-tryte transaction is processed as a fixed-size header followed by three
81-tryte hash fields. The miner only varies the approvalNonce:
| Field | Tryte offset | Length (trytes) |
|---|---|---|
| Header (signature, address, value, tag, timestamp, …) | 0 |
2430 |
approvalNonce |
2430 |
81 |
trunkTransaction |
2511 |
81 |
branchTransaction |
2592 |
81 |
src/
├── main/java/com/iota/curl/
│ ├── IotaCurlHash.java # Curl-P-27 sponge hash (absorb/transform/squeeze)
│ ├── IotaCurlMiner.java # Bit-sliced Proof-of-Work search (single & multi-threaded)
│ ├── IotaCurlUtils.java # Trit/tryte conversion tables, validation, ternary arithmetic
│ └── miner/Miner.java # Command-line entry point / demo
└── test/java/com/iota/curl/
├── HashTest.java # Hash and conversion vectors, boundary validation
└── MinerTest.java # Mining vectors, determinism and parity invariants
- IOTA — https://www.iota.org
- Sponge functions — https://en.wikipedia.org/wiki/Sponge_function
- Balanced ternary — https://en.wikipedia.org/wiki/Balanced_ternary
No license is currently declared for this repository. Until one is added, all rights are reserved by the author; please contact the maintainer before reusing the code.
Originally ported by Gianluigi Davassi.