hashsigs-rs: SHRINCS hash-based signatures in Rust, with a Solana verifier
Hash-based post quantum signatures in Rust
At a glance
- What is it?
- 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.
- Who is it for?
- 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.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 6 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
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:
[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.
Editorial 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.
Frequently asked questions
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.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/quipnetwork-hashsigs-rs)
Community notes