freenet-vanity-id

February 12, 2026 · View on GitHub

Generate vanity Freenet contract IDs by grinding a nonce appended to contract parameters.

A Freenet contract ID is base58(BLAKE3(BLAKE3(wasm_code) || parameters || nonce)). This tool finds a nonce such that the resulting ID starts with a desired prefix.

Tools

freenet-vanity-id

Search for a nonce that produces a contract ID with the desired prefix.

freenet-vanity-id \
  -c <contract.wasm> \
  -p <parameters.bin> \
  -x <prefix> \
  [-t <threads>] \
  [--continue] \
  [-o <output.vanity>]
FlagDescription
-c, --codePath to the contract WASM file
-p, --paramsPath to the parameters file (e.g. 32-byte Ed25519 pubkey)
-x, --prefixDesired base58 prefix (case-sensitive)
-t, --threadsNumber of threads (default: all cores)
--continueKeep searching after a match (print all matches, Ctrl+C to stop)
-o, --outputOutput path for nonce-extended parameters (default: <params>.vanity)

Example:

freenet-vanity-id \
  -c target/wasm32-unknown-unknown/release/web_container_contract.wasm \
  -p target/deploy/webapp-params.bin \
  -x Finder --continue
Found!
  Contract ID: Finder5Z1LDMRfzs3GKBWiD2XkzBNpBgEsb3obefbHAW
  Nonce:       2305843016209829291 (0x20000001a1008dab)
  Nonce bytes: [171, 141, 0, 161, 1, 0, 0, 32]
  Wrote "target/deploy/webapp-params.vanity"

With --continue, matches are printed as they're found and the .vanity file is overwritten with each new result. Ctrl+C when you see one you like.

freenet-apply-nonce

Apply a nonce from a previous search to produce the .vanity parameters file.

freenet-apply-nonce \
  -c <contract.wasm> \
  -p <parameters.bin> \
  -n <nonce> \
  [-o <output.vanity>]
FlagDescription
-c, --codePath to the contract WASM file
-p, --paramsPath to the original parameters file (without nonce)
-n, --nonceNonce value — decimal, 0x hex, or [byte,array]
-o, --outputOutput path (default: <params>.vanity)

The nonce accepts any format from the finder output:

freenet-apply-nonce -c contract.wasm -p params.bin -n 2305843016209829291
freenet-apply-nonce -c contract.wasm -p params.bin -n 0x20000001a1008dab
freenet-apply-nonce -c contract.wasm -p params.bin -n '[171, 141, 0, 161, 1, 0, 0, 32]'

Or pipe the finder output directly (omit -n):

freenet-vanity-id -c contract.wasm -p params.bin -x Finder \
  | freenet-apply-nonce -c contract.wasm -p params.bin

Deploying a webapp with a vanity contract ID

The output .vanity file contains the original parameters with the 8-byte nonce appended. The contract must accept parameters longer than the original (ignoring trailing bytes). The web_container_contract already supports this.

Step-by-step workflow

1. Extract your webapp's verifying key (parameters)

The webapp's contract parameters are the 32-byte Ed25519 verifying key derived from your signing keys. If you already have a webapp.parameters file, use it directly. Otherwise, generate one:

web-container-tool sign \
  --input dummy.tar.xz \
  --output /dev/null \
  --parameters webapp.parameters \
  --key-file webapp-keys.toml \
  --version 1

This writes the 32-byte verifying key to webapp.parameters.

2. Grind for a vanity nonce

freenet-vanity-id \
  -c web_container_contract.wasm \
  -p webapp.parameters \
  -x YourPrefix --continue

Use --continue to see multiple matches and pick the best-looking one. Ctrl+C when satisfied. The .vanity file is overwritten with each match.

3. Save the nonce separately

The nonce is the last 8 bytes of the .vanity file. Extract it for use in deploy scripts:

tail -c 8 webapp.parameters.vanity > webapp-vanity-nonce.bin

This separation matters because web-container-tool sign regenerates webapp.parameters to exactly 32 bytes on every invocation, overwriting any appended nonce. Your deploy scripts must append the nonce after each sign step.

4. Integrate into your deploy script

The critical ordering in your build/deploy pipeline:

# 1. Sign the webapp (writes 32-byte params)
web-container-tool sign \
  --input webapp.tar.xz \
  --output webapp.metadata \
  --parameters webapp.parameters \
  --key-file webapp-keys.toml \
  --version "$version"

# 2. Append the vanity nonce (32 → 40 bytes)
cat webapp-vanity-nonce.bin >> webapp.parameters

# 3. Compute the contract ID from the nonce-extended params
CONTRACT_ID=$(fdev get-contract-id \
  --code web_container_contract.wasm \
  --parameters webapp.parameters)

# 4. Build UI with the correct base_path (if applicable)
# ... use $CONTRACT_ID in your asset paths ...

# 5. Publish with the 40-byte parameters
fdev network publish \
  --code web_container_contract.wasm \
  --parameters webapp.parameters \
  contract \
  --webapp-archive webapp.tar.xz \
  --webapp-metadata webapp.metadata

Common pitfalls

  • Sign overwrites the nonce. web-container-tool sign --parameters always writes exactly 32 bytes. You must append the nonce after every sign call, not just once.
  • Don't double-append. If webapp.parameters is already 40 bytes from a previous run, appending again produces 48 bytes and a wrong contract ID. Always sign first (resets to 32 bytes), then append.
  • Keep the nonce file safe. If you lose webapp-vanity-nonce.bin, you lose the vanity ID. Store it alongside your signing keys.
  • Changing the contract ID invalidates the old one. If you previously deployed without a vanity nonce, the old contract (with the 32-byte params) is a different contract entirely. You can publish a redirect page to the old contract using the same signing keys — see below.

Redirecting the old contract

If you had a live contract before adding the vanity nonce, publish a redirect to avoid broken links:

# Create a redirect page
mkdir -p /tmp/redirect && cat > /tmp/redirect/index.html << EOF
<!DOCTYPE html>
<html>
<head><meta http-equiv="refresh" content="0;url=/v1/contract/web/$NEW_CONTRACT_ID/"></head>
<body><p>Moved. <a href="/v1/contract/web/$NEW_CONTRACT_ID/">Click here</a>.</p></body>
</html>
EOF
(cd /tmp/redirect && tar -cJf /tmp/redirect.tar.xz *)

# Sign with the OLD parameters (32 bytes, no nonce)
web-container-tool sign \
  --input /tmp/redirect.tar.xz \
  --output /tmp/redirect.metadata \
  --parameters old-params.bin \
  --key-file webapp-keys.toml \
  --version "$version"

# Publish — updates the old contract's state
fdev network publish \
  --code web_container_contract.wasm \
  --parameters old-params.bin \
  contract \
  --webapp-archive /tmp/redirect.tar.xz \
  --webapp-metadata /tmp/redirect.metadata

Building

RUSTFLAGS="-C target-cpu=native" cargo build --release

The target-cpu=native flag is important — it enables compile-time SIMD detection for AVX2/SSSE3 N-way parallel compression.

Expected search times

Search time depends on prefix length and the first character of the prefix. Times below are estimates at ~146M hashes/sec (8-thread i7-8550U with AVX2). Scale linearly with hash rate.

Prefixes starting with 1-H (44-char base58 encoding, ~94% of hashes):

PrefixSearch spaceBitsExpected time
1 char58~5.9instant
2 char3,364~11.7instant
3 char195,112~17.6instant
4 char11.3M~23.4< 1 second
5 char656M~29.3~4 seconds
6 char38.1B~35.1~4 minutes
7 char2.2T~41.0~4 hours
8 char128T~46.9~10 days

Prefixes starting with J-z (43-char base58 encoding, ~5.8% of hashes):

PrefixSearch spaceBitsExpected time
1 char~1,000~10.0instant
2 char~58,000~15.8instant
3 char~3.4M~21.7instant
4 char~195M~27.5~1 second
5 char~11.3B~33.4~1 minute
6 char~656B~39.3~1.2 hours
7 char~38T~45.1~3 days
8 char~2.2P~51.0~174 days

The difference: a 32-byte hash encodes to either 43 or 44 base58 characters. Characters J-z (values 17-57) can only appear as the first character of a 43-char encoding, which covers only ~5.8% of the hash space. This makes those prefixes ~17x harder than the naive 58^n estimate.

These are average times — actual results follow a geometric distribution. A 6-character prefix starting with 1-H is the practical sweet spot for a single machine. For J-z prefixes, 5 characters is more realistic.

How it works

The tool computes BLAKE3(BLAKE3(wasm) || params || nonce) for sequential nonce values across multiple threads, checking if the result's base58 encoding starts with the desired prefix.

Optimizations

1. N-way parallel BLAKE3 compression (AVX2 / SSSE3)

The hot loop processes 8 nonces simultaneously (AVX2) or 4 (SSSE3) using a transposed state layout where each SIMD lane holds one word from a different hash. This is the same technique used by BLAKE3's reference hash4/hash8 implementations.

Additionally, block 1 has only 2 non-zero message words out of 16 (the 8-byte nonce). The other 14 are zero. The sparse message optimization skips these zero-word additions — saving ~14 of 16 adds per round across 7 rounds.

Runtime dispatch selects AVX2 8-way → SSSE3 4-way → scalar 1-way based on CPU features.

2. Midstate caching

The 72-byte hash input (32-byte code hash + 32-byte params + 8-byte nonce) spans two BLAKE3 64-byte blocks. Block 0 (code_hash || params) is constant across all trials. The tool caches the chaining value from block 0 and recomputes only block 1 (the nonce) per trial — one compression instead of two.

This avoids the blake3 crate's Hasher struct entirely, which is 1920 bytes and dominates per-hash cost for small inputs.

3. u64 range pre-check

Instead of base58-encoding every hash (expensive), the tool precomputes a [u8; 32] byte range that corresponds to the desired prefix. The first 8 bytes of the hash are compared as a single u64 — one comparison instead of a 32-byte lexicographic compare. Only candidates passing this fast check proceed to full base58 verification.

4. Parallel nonce search

Threads search disjoint nonce ranges with minimal synchronization. A shared AtomicBool is checked every ~8K iterations (amortized cost ~0) to signal early termination when any thread finds a match.

Base58 alphabet

Freenet uses the Bitcoin base58 alphabet:

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

Note the missing characters: 0, O, I, l (zero, uppercase O, uppercase I, lowercase L).

License

AGPL-3.0 — see LICENSE.md