# hashsigs-rs: SHRINCS hash-based signatures in Rust, with a Solana verifier

> hashsigs-rs is a Rust workspace for post-quantum hash signatures: WOTS+ (legacy), SPHINCS+C and the two-path SHRINCS construction. It is the reference signer for a Solidity verifier, and it ships a verify-only Solana program and a wasm wrapper.

**QuipNetwork/hashsigs-rs** — Hash-based post quantum signatures in Rust

- Repository: https://github.com/QuipNetwork/hashsigs-rs
- Stars: 11,213 · Forks: 45
- Language: Rust
- License: AGPL-3.0
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/quipnetwork-hashsigs-rs

## The problem hashsigs-rs solves, and who it is built for

Hash-based signatures are the post-quantum option that does not rest on a new hardness assumption. The cost is size and, for the stateful variants, bookkeeping. hashsigs-rs takes a specific position on that trade-off: it ships a two-path construction called SHRINCS, where a cheap stateful path handles normal operations and an expensive stateless path is held back for recovery and key rotation.

The intended reader is not a general application developer. The README describes the crate as "the reference signer: it generates the golden vectors that anchor the Solidity verifier in hashsigs-solidity". That sentence defines the audience. If you are writing Rust that must produce signatures a Solidity or Solana verifier will accept, and you need the vectors to match, this is the implementation the project points at. If you want a library to sign a JWT, this is the wrong shape entirely.

The repository also carries a verify-only Solana program under solana/, an account-wrapper example at solana/examples/shrincs-account/, a TypeScript npm wrapper at ts/, and a Python package scaffold under py/. The Rust crate is the centre; everything else is packaging around it.

## How SHRINCS splits one key bundle across two verification paths

A 32-byte publicKeyCommitment binds two roots plus a profile identity. The stateful root is a UXMSS tree: an unbalanced Merkle tree of WOTS-C one-time leaves. The stateless root is a SPHINCS+C hypertree over FORS-C few-time signatures. The commitment is tagged shrincs-public-key/<profile> and is the only value that needs on-chain storage.

That last point drives the whole design. Callers resupply the full 164-byte public-key bundle on every verify, and the verifier recomputes the commitment and checks it. Verification is pure keccak-256, or SHA-256 under the sha2 profile, and the README states it grinds nothing. All the grinding happens at signing time, which is the SPHINCS+C idea: move work from verifier to signer.

The stateful path consumes one leaf per signature, up to maxSignatures, which the README caps at 4,096. The leaf index is implicit: it equals the auth-path length. That is a neat trick and also a hard constraint, because it means the signer must never reuse a leaf. Signer state (nonces, used-leaf tracking, budgets) belongs to the integrating account, not the verifier. The library will not protect you from signing twice with the same leaf; your account layer has to.

Dependencies point only downward between the components. shrincs composes the two halves at the API boundary, sphincs_plus_c is oblivious to shrincs, and wots_c is a shared chain walk that both paths bind with their own domain tags.

## Profiles, sizes and the q20 caveat

Six compile-time profiles ship: three parameter sets, each in a keccak and a sha2 variant. The cryptographic constants match the Solidity verifier, and src/profiles/ is described as the Rust source of truth. Two families exist. The 256s family uses 32-byte hash entropy, a hypertree of height 64 across 8 layers, 22 FORS-C trees at height 14, 64 WOTS-C chains with w = 16 and target sum 480, and a stateless signature budget of 2^20. The 128s family truncates hash entropy to 16 bytes in full 32-byte wire slots, uses a height-18 single-layer hypertree, 6 FORS-C trees at height 24, 32 WOTS-C chains with target sum 240, and a budget of 2^18 for q18 or 2^20 for q20.

One line in the README deserves to be read twice: the larger q20 budget "still needs security-analysis backing before production use". That is the project marking its own unfinished work. Treat 128s-q20 as available but unvetted.

The sha2 suite switches scheme hashes only. EVM-domain hashes (profile identity, commitments, canonical action hashes) stay keccak under every profile. So choosing a sha2 profile changes hashing work, not commitment framing, and 128s-q18-sha2 shares every parameter row with 128s-q18.

Key sizes are constant across all six SHRINCS profiles, which the README says is by design. Signature sizes and on-chain verify costs are not.

## Building the Rust crate and signing once

The crate requires Rust 1.95 and is edition 2021. It is published as hashsigs-rs with the library name hashsigs_rs, and docs.rs is listed as the documentation home. Add it as a dependency and pull in the workspace:

```toml
[dependencies]
hashsigs-rs = "0.2.1-rc9"
```

The version string in Cargo.toml is 0.2.1-rc9, so this is a release candidate rather than a settled 1.0. Note that the lib target declares crate-type = ["rlib"] only. A host no_std build cannot link a cdylib, and wasm packaging requests cdylib separately through cargo rustc --crate-type cdylib in bin/build-wasm.sh. If you were expecting to build a shared library straight from cargo build, that is why it does not appear.

The workspace ships runnable examples rather than a quickstart in the README. Four exist: examples/bench_keygen_sign.rs, examples/bench_table.rs, examples/bench_wots.rs and examples/measure_mem.rs. The README does not give a command line for them, so read the example sources under examples/ before running one. Expect a keygen-and-sign run rather than a printed signature you can paste into a verifier. For the verification side, the Solana program lives under solana/ with its account-wrapper example at solana/examples/shrincs-account/, and the npm wrapper @quip.network/hashsigs-wasm covers the browser and Node path. The README gives no end-to-end walkthrough that starts at key generation and ends at a verified on-chain transaction, so plan to read the example sources.

## Where hashsigs-rs is the wrong tool

The stateful path is a leaf budget, not a counter you can ignore. At most 4,096 signatures, and each one burns a leaf permanently. Lose the used-leaf tracking and you have a catastrophic failure mode that no verifier will catch, because verification only recomputes a commitment. The README is explicit that signer state belongs to the integrating account. That is a deliberate separation, and it means the library cannot be dropped into a stateless service that scales horizontally without a shared state store.

The Python package is not ready. pyproject.toml describes py/ as a scaffold and the README says the binding API is under construction. Worse for anyone scripting a build: a bare maturin build is documented as producing a package whose hashsigs._ext is empty, because maturin builds one Cargo package and one Cargo package builds at most one cdylib. The in-tree backend hashsigs_build exists to build one extension per profile. Going through pip install . or python -m build is described as the only way to get a complete wheel.

There is also a documented FIPS 205 deviation: the signer does not serialize upper-layer hypertree coordinates, and the verifier re-derives them from the FORS digest plus a fixed recurrence in src/sphincs_plus_c/hypertree.rs. If your requirement is SLH-DSA conformance, that deviation is a compatibility boundary, not a detail.

Finally, the README does not document rollback, key migration or a recovery procedure for a lost state file. The stateless path is described as reserved for recovery and key rotation, but the operational steps are not in the README.

## WOTS+ is legacy, and what to use instead

The crate bundles wotsplus, a standalone WOTS+ implementation with checksum chains, described as the v1 wallet scheme. The README is blunt: "Legacy: do not use WOTS+ for new integrations. Use SHRINCS." It stays only to keep v1 wallets verifiable.

That is a real alternative inside the same repository, and the difference is structural rather than cosmetic. WOTS+ keeps the checksum chains and needs a Merkle tree above it to be more than one-time. SPHINCS+C drops the checksum chains entirely: the signer grinds a counter until the message digits sum to a fixed target, and the verifier checks the target-sum equation instead. Each signature adds a 4-byte grind counter and a per-signature randomizer. SHRINCS then wraps that stateless design alongside the UXMSS stateful tree, so a single commitment covers both.

If you want a comparison outside this repository, the honest one is FIPS 205 SLH-DSA, the NIST standard for stateless hash-based signatures. The README lists it as a lineage source and notes address-word conventions are adopted, with the one documented deviation. SPHINCS+C is not FIPS 205; it is a compression of SPHINCS+ that trades signer-side grinding for smaller signatures and cheaper verification. If your constraint is standards conformance, use a SLH-DSA implementation. If your constraint is on-chain verification cost, SPHINCS+C is the reason this project exists.

## Licence, release checks and upgrade cost

The crate is AGPL-3.0-or-later, and pyproject.toml states the same for the Python distribution. AGPL is a copyleft licence with a network clause, which matters here because the primary deployment target is a verifier that other people interact with over a network. Whether your integration triggers the network clause is a question for your own counsel; the repository does not answer it, and nothing in the README discusses the implications for a Solana program that consumes the verifier.

Upgrade cost is not trivial. The version is 0.2.1-rc9 and there are no retrieved releases, so the workspace is pre-1.0. The Makefile documents a release gate called check-release, described as an aggregate of prerequisites rather than a wrapper script, invoked by CI with make -k so that a broken crate manifest does not hide a broken npm package. It runs check-versions, which delegates to bin/sync-versions.sh check, and check-packages, which runs bin/tests/test-packages.sh. Every manifest carries the root Cargo.toml version, and the Python version comes from py/Cargo.toml so the workspace has one source of truth.

That version sync is the thing to watch when you upgrade. A profile added on the Rust side without a matching package in every ecosystem will fail check-packages, and the npm tree under ts/ is excluded from the published crate, so the Rust and TypeScript release paths are separate artefacts that must stay aligned manually.

## Conclusion

Adopt hashsigs-rs if you need a Rust reference signer whose golden vectors anchor an on-chain verifier, and you can accept that signer state lives in your integrating account. Do not adopt it if you want a stable Python API or a drop-in SLH-DSA implementation: the Python binding API is documented as under construction, and the FIPS 205 deviation on upper-layer hypertree coordinates means you cannot assume byte-for-byte compatibility with the standard. Before building, verify which profile you need by reading src/profiles/ as the source of truth, and confirm the AGPL-3.0-or-later terms against your own distribution plans.

## FAQ

### What is hashsigs-rs, and which signature schemes does it implement?

It is a Rust workspace for hash-based post-quantum signatures containing WOTS+ (documented as legacy), SPHINCS+C, and SHRINCS, a hybrid that combines a stateful UXMSS path with a stateless SPHINCS+C path under one 32-byte public key commitment.

### How many signatures can a SHRINCS stateful key produce?

Each stateful signature consumes one leaf, up to maxSignatures, which the README caps at 4,096. The leaf index is implicit and equals the auth-path length, so the used-leaf tracking has to live in the integrating account.

### How do I install the hashsigs-rs Python package?

The package is named hashsigs and requires Python 3.9 or later. The README describes the binding API as under construction, and pyproject.toml states that pip install . or python -m build is the only way to get a complete wheel, because a bare maturin build produces a package whose hashsigs._ext is empty.

### Can I use hashsigs-rs to verify signatures on Solana?

The repository includes solana/, described as a verify-only Solana program, plus an account-wrapper example at solana/examples/shrincs-account/. Verification recomputes the 32-byte commitment from the full 164-byte public-key bundle that the caller resupplies.

## Sources

- [Issues](https://github.com/QuipNetwork/hashsigs-rs/issues)
- [License: AGPL-3.0](https://github.com/QuipNetwork/hashsigs-rs/blob/main/LICENSE)
- [QuipNetwork/hashsigs-rs on GitHub](https://github.com/QuipNetwork/hashsigs-rs)
- [README](https://github.com/QuipNetwork/hashsigs-rs/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/quipnetwork-hashsigs-rs
