What this is
A Liquid wallet that signs covenant spends with a hash-based, quantum-resistant signature. Plus a small new crate inside LWK that wires the on-chain part end-to-end.
Why I built it
Blockstream had already published the SHRINCS Simplicity verifier a covenant that accepts hash-based, quantum-resistant signatures on chain. The on-chain half was done. But no wallet could actually use it: no Rust signer that produced a SHRINCS-compatible signature, no LWK glue to inject the witness, no demo of the full loop.
That gap bothered me. The whole point of having a post-quantum verifier on chain is that wallets can lock funds with it. Without a wallet, the verifier just sits there. So we built the wallet side.
How it works (in one paragraph)
You generate a Lamport keypair, derive a Liquid P2TR address that commits to a tiny wrapper covenant (the SHRINCS verifier plus two security bindings we added), fund that address with tL-BTC from the
faucet, then spend it by producing a Lamport signature over the spending transaction's sighash. The wallet builds the 4-tuple Simplicity witness [witness, program, cmr, control_block], injects it into the PSET input, signs the fee input with a normal Schnorr key, and broadcasts.
The two security bindings We added
The upstream SHRINCS verifier accepts any valid Lamport signature, full stop. That means two real problems:
- Every UTXO would share the same address (the verifier doesn't know who you are). Our wrapper adds
param::EXPECTED_PK_HASH, which binds the address to a specific public key. - A sniffed signature could be replayed against any other transaction spending the same scriptPubKey. Our wrapper adds
jet::sig_all_hash(), which makes the signed message be the spending transaction itself.
These two changes are ~10 lines of SimplicityHL. Without them, the covenant is on-chain anyone-can-spend.
How it's organised
Three pieces:
hackaton/
├── pq-liquid-wallet/ ← the wallet (this submission)
├── lwk/ ← Blockstream's LWK + one new crate We added
│ └── lwk_simplicity_pq/ (PqSigner trait + Lamport + spend helper)
└── shrincs-simplicity-verifier/ ← Blockstream's verifier (read-only)
The wallet uses a just command surface: just setup builds everything once, just demo runs the full offline pipeline in 30 seconds, just option-b prints the testnet recipe.
What works today
- Offline end-to-end: keygen → wrapper compile → Lamport sign → on-chain verifier accepts in-process. 26 tests green, byte-for-byte conformance against Blockstream's shipped
.witfixture passes. - A real funding tx is on Liquid Testnet:
43972e1970e4a4adcb036823270879a0a22c20d596ec3a4b64ef2fa5cad39a5f— 5 000 sats locked under the covenant, unspent.
What doesn't (honestly)
The on-chain spend assembles correctly (PSET built, Lamport signed, witness injected) but is rejected by Liquid's standard relay policy. The Lamport verifier's static worst-case cost is 256 iterations of SHA-256; the budget Liquid grants from the witness weight isn't enough. The standard fix (Taproot annex padding) is itself non-standard.
This isn't a bug in our code, it's a real consensus-vs-policy edge case.
Repo and full user guide: https://github.com/smeneguz/pq-liquid-wallet
Built With
- electrum
- elements
- just
- liquid
- lwk
- mcpp
- rust
- shrincs
- simplicityhl
Log in or sign up for Devpost to join the conversation.