Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 230 additions & 19 deletions Cargo.lock

Large diffs are not rendered by default.

33 changes: 25 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ qp-dilithium-crypto = { version = "0.6.1", features = ["serde"] }
qp-human-checkphrase = "2.2.1"
qp-rusty-crystals-dilithium = { version = "4.1.1", features = ["ml-dsa-65", "ml-dsa-87"] }
qp-rusty-crystals-hdwallet = { version = "4.1.1", features = ["ml-dsa-65"] }
# The pre-March-2026 BIP32 tree ("Bitcoin seed" master HMAC key, soft child
# support): historical Dilithium accounts and wormhole entropies in
# cli/airdrop.rs derive through it. Same crate qp-rusty-crystals-hdwallet
# < 2.1.0 used internally.
nam-tiny-hderive = { version = "=0.3.1-nam.1", default-features = false }

# HTTP client for Subsquid queries
blake3 = "1.8"
Expand Down Expand Up @@ -122,23 +127,35 @@ subxt-metadata = "0.44"
# ZK proof generation (aligned with chain)
anyhow = "1.0"

# Building blocks of qp-poseidon-core 0.9.5's hasher. The local v09 sponge in
# cli/airdrop.rs hashes the secret preimage from a borrowed slice because the
# 0.9.5 crate's hash_no_pad consumes its Vec and frees it unscrubbed.
p3-field = { version = "0.3.0", default-features = false }
p3-goldilocks = { version = "0.3.0", default-features = false }
p3-symmetric = { version = "0.3.0", default-features = false }
qp-ownership-circuit = { version = "=4.4.0", default-features = false, features = ["std"] }
qp-ownership-prover = { version = "=4.4.0", default-features = false, features = ["std"] }
qp-plonky2 = { version = "1.5.5", default-features = false, features = ["rand", "std"] }
qp-plonky2-verifier = { version = "1.5.5", default-features = false }
qp-wormhole-aggregator = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0", default-features = false, features = ["rayon", "std"] }
qp-wormhole-circuit = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0", default-features = false, features = ["std"] }
qp-wormhole-circuit-builder = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0" }
qp-wormhole-inputs = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0", default-features = false, features = ["std"] }
qp-wormhole-prover = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0", default-features = false, features = ["std"] }
qp-wormhole-verifier = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0", default-features = false, features = ["std"] }
qp-zk-circuits-common = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0", default-features = false, features = ["std"] }
qp-poseidon-core = "3.1.0"
rand_chacha = { version = "0.9", default-features = false }
# Resonance-era (v0.9.x) permutation constants differ from the current ones.
qp-poseidon-core-v09 = { package = "qp-poseidon-core", version = "=0.9.5" }
qp-wormhole-aggregator = { version = "=4.4.0", default-features = false, features = ["rayon", "std"] }
qp-wormhole-circuit = { version = "=4.4.0", default-features = false, features = ["std"] }
qp-wormhole-circuit-builder = "=4.4.0"
qp-wormhole-inputs = { version = "=4.4.0", default-features = false, features = ["std"] }
qp-wormhole-prover = { version = "=4.4.0", default-features = false, features = ["std"] }
qp-wormhole-verifier = { version = "=4.4.0", default-features = false, features = ["std"] }
qp-zk-circuits-common = { version = "=4.4.0", default-features = false, features = ["std"] }

[target.'cfg(unix)'.dependencies]
libc = "0.2"

[build-dependencies]
hex = "0.4"
qp-poseidon-core = "3.1.0"
qp-wormhole-circuit-builder = { version = "4.4.0", git = "https://github.com/Quantus-Network/qp-zk-circuits", tag = "v4.4.0" }
qp-wormhole-circuit-builder = "=4.4.0"
sha2 = "0.10"

[dev-dependencies]
Expand Down
106 changes: 106 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ A modern command line interface for interacting with the Quantus Network, featur

- **Quantum-Safe Wallets**: Built with Dilithium post-quantum cryptography
- **Cold Wallet Signing**: Air-gapped signing over QR codes with Keystone or the Quantus cold wallet app
- **Airdrop Claims**: Find and claim testnet rewards across every historical key-derivation scheme
- **SubXT Integration**: Modern Substrate client with type-safe API
- **Generic Pallet Calls**: Call ANY blockchain function using metadata-driven parsing
- **Real Chain Operations**: Send tokens, query balances, explore metadata
Expand Down Expand Up @@ -324,6 +325,111 @@ quantus wormhole check-nullifier --secret-file ./secret.hex --transfer-counts 0-

---

### Airdrop (Testnet Reward Claims)

The `airdrop` commands find testnet addresses you own in the published reward
snapshot and claim their rewards. You prove ownership either by signing with an
ML-DSA-87 (Dilithium) key or with a ZK wormhole ownership proof; the payout is
credited to any account you choose.

Ownership is re-derived from your wallet, so old addresses are found even if
the key-derivation scheme has changed since they were created. From a single
wallet mnemonic the scan covers:

- **Your wallet's current Dilithium key**, hashed under every address format
each testnet generation used (Resonance through Planck).
- **Every historical key-generation scheme**: non-HD keys expanded from the
BIP39 seed (both the pre-FIPS and FIPS 204 seed expansions), BIP32 keys under
the legacy `"Bitcoin seed"` master (soft `m/44'/189189'/N'/0/0` and hardened
`m/44'/189189'/N'/0'/0'` paths, plus account-only `m/44'/189189'/N'`), and the
current `"Dilithium seed"` HD tree — accounts `0..=8` by default, plus the
wallet's own stored derivation path if it was imported with a custom
`--derivation-path`.
- **HD wormhole secrets** in both the current `"Dilithium seed"` tree and the
legacy `"Bitcoin seed"` tree (including the pre-2026 master-node secret), at
paths `m/44'/189189189'/0'/round'/index'` for rounds `0..=8` and indexes
`0..=16` by default.

Wallets without a mnemonic (raw-seed imports) are matched on their current key
only; cold wallets cannot be used. ML-DSA-65 wallets cannot sign Dilithium
claims, but their mnemonic is still scanned for historical ML-DSA-87 and
wormhole addresses.

#### `quantus airdrop check`

List snapshot rows owned by your wallet and/or an explicit wormhole secret.
Read-only: nothing is signed or submitted.

```bash
quantus airdrop check --wallet my_wallet

# Also (or only) check an explicit wormhole secret
quantus airdrop check --wallet my_wallet --wormhole-secret-file ./secret.hex
quantus airdrop check --wormhole-secret-file ./secret.hex
```

Output:
```
Snapshot v1 (3f9c2a81be04) — 1234 rewarded addresses
2 snapshot match(es):
qDx... 150.00 QUAN Resonance dilithium-v08-padded (dilithium) [claimable]
qDy... 75.50 QUAN Planck wormhole-rate8-compact (wormhole) [claimable]
```

- `--wallet`: Hot wallet used to derive Dilithium and HD wormhole addresses.
- `--wormhole-secret-file`: File with a 32-byte hex wormhole secret. On Unix
the file must be a regular file owned by you with no group/other access
(`chmod 600`), like `--password-file`.
- `--wormhole-index`: Pin the HD wormhole address index instead of scanning
`0..=16`; every branch/round is still scanned.
- `--scan-accounts`: Highest Dilithium account index scanned per historical
keygen family (default: `8`). Raise it if the wallet was created with a
higher account index.
- `--scan-rounds`: Highest wormhole branch/round component scanned (default:
`8`). Raise it if you ran `wormhole multiround` with more rounds.
- `--server`: Claim server base URL.

#### `quantus airdrop claim`

Prove ownership of every match and submit the claims. Amounts come from the
snapshot; the server verifies each proof and records the payout.

```bash
# Claim everything the wallet owns, paid out to the wallet's own account
quantus airdrop claim --wallet my_wallet

# Pay out to a different account (wallet name or SS58 address)
quantus airdrop claim --wallet my_wallet --to qDz...

# Claim from an explicit wormhole secret (requires --to)
quantus airdrop claim --wormhole-secret-file ./secret.hex --to qDz...

# Inspect the exact payloads without submitting anything
quantus airdrop claim --wallet my_wallet --dry-run
```

For each match the CLI builds the appropriate proof:

- **Dilithium rewards** are claimed with an ML-DSA-87 signature over
`address ‖ payout account ‖ expiry`, using your wallet's key or, for
historical matches, a key re-derived on the spot from the mnemonic with that
era's exact scheme (all secret material is wiped from memory after signing).
- **Wormhole rewards** under the current scheme (`wormhole-rate8-compact`) are
claimed with a ZK ownership proof, so the secret itself is never sent.
Matches under older wormhole schemes are listed as `not claimable yet` and
skipped — the server does not accept them yet.

- `--to`: Payout destination (wallet name or SS58). Defaults to `--wallet`'s
own account; required when claiming with only a secret file.
- `--dry-run`: Print the signed/proved claim payloads without POSTing.
- All `check` flags (`--wormhole-secret-file`, `--wormhole-index`,
`--scan-accounts`, `--scan-rounds`, `--server`) work the same here.

The command exits non-zero if any claim fails, and prints a summary of
recorded, skipped, and failed claims.

---

### Developer Tools

#### `quantus developer build-circuits`
Expand Down
Loading
Loading