Addresses & signatures
A Snowblossom address is the 20-byte hash of an address spec: a list of public keys with their signature algorithms and the number of signatures required. A plain address is simply a 1-of-1 spec. Any mix of eight algorithms - including three post-quantum schemes - can be combined in one address, and the keys stay hidden until the coins are spent.
AddressSpec and its hash
message AddressSpec { int32 required_signers = 1; repeated SigSpec sig_specs = 2; }
message SigSpec { int32 signature_type = 1; bytes public_key = 2; }
AddressUtil.getHashForSpec hashes a fixed big-endian serialisation (not the protobuf encoding, which is not canonical):
int32 required_signers
int32 number of sig_specs
for each sig_spec: int32 signature_type · int32 length(public_key) · public_key bytes
address = Skein-256-160( the above ) // 20 bytes
Key order matters: the same keys in a different order give a different address. The same secp256k1 key as a compressed 33-byte point (type 1) or as an X.509 blob (type 2) gives different addresses as well. required_signers = 0 is accepted by validation (anyone can spend); required_signers > N is unspendable.
Address strings: Duck32
For people, the 20 bytes are rendered as <label>:<data> with lib/src/Duck32.java - “kinda like bech32, but with code I can actually understand”:
checksum = Skein-256-256(label ‖ data20)[0..5)- five bytes; the label (snow,snowtest,snowreg, …) is part of the hash, so an address only checks out on its own network.whole = data20 ‖ checksum5(25 bytes = 200 bits) is treated as one unsigned integer and written in base 32.- Each base-32 digit is mapped through the bech32 character set
qpzry9x8gf2tvdw0s3jn54khce6mua7l(lowercase only).
That yields 40 characters (rarely fewer, because leading zero digits are dropped). The label may be omitted on input - 62xxv0j0wjwuw5satv5nq89pw7eawpmyu6wyalgd and snow:62xxv0j0wjwuw5satv5nq89pw7eawpmyu6wyalgd are the same address - but including it is encouraged. The same 20 bytes on testnet read snowtest:62xxv0j0wjwuw5satv5nq89pw7eawpmyekfaxa08: identical data, different checksum. Uppercase input is rejected. Node identities use the label node (node:eaws55…) with the same scheme.
- Label
- -
- Spec hash (20 bytes)
- -
- Checksum bytes
- -
Decodes the base-32 data only; the checksum is shown but not verified (that needs Skein).
Signature algorithms
SignatureUtil recognises eight signature_type values. Keys other than type 1 are X.509 SubjectPublicKeyInfo DER; private keys are stored as PKCS#8. Everything runs through BouncyCastle (1.80) - never the JDK providers, which is why the 2022 “psychic signatures” Java bug did not affect Snowblossom (SR-1).
| # | Type | Algorithm | Public key | Signature | Quantum |
|---|---|---|---|---|---|
| 1 | ECDSA_COMPRESSED | ECDSA on secp256k1 only; BC “ECDSA” = SHA-1 digest + ECDSA, DER (r,s). The default for seed and standard wallets - same curve as Bitcoin. | 33 B compressed point (02/03 ‖ X) | ≈ 71 B | no |
| 2 | ECDSA | ECDSA with an X.509 key; curves secp256k1, secp384r1, secp521r1, sect571k1, sect571r1. | 88–96 B | 71–151 B | no |
| 3 | DSA | DSA (SHA-1); wallet generates 3072-bit. | ≈ 1,227 B | ≈ 70 B | no |
| 4 | RSA | RSA PKCS#1 v1.5 over the raw hash (“NONEwithRSA”); any size, wallet uses 8192-bit. | ≈ 1,058 B @ 8192 | modulus size: 1,024 B @ 8192 | no (but needs ~2× bits in qubits) |
| 5 | DSTU4145 | DSTU 4145-2002, the Ukrainian EC standard over GF(2m) with GOST R 34.11-94 digest; curves 163 … 431 bit, wallet uses 431. | 62–95 B | 44–110 B | no |
| 6 | SPHINCSPLUS | SPHINCS+ haraka-128s, stateless hash-based signatures. Gated by SIP-6 (mainnet block 358,700). | 52 B | 7,856 B | yes |
| 7 | DILITHIUM | CRYSTALS-Dilithium 5 (round-3 parameters, not ML-DSA/FIPS 204). Gated by SIP-6. | 2,616 B | ≈ 4,627 B | yes |
| 8 | FALCON | Falcon-512, lattice (NTRU) based, compact. | 915 B | ≈ 655 B | yes |
Type 1 keys are expanded to X.509 by prefixing a fixed secp256k1 header before verification. Unknown types fail validation. Types 6 and 7 were added in 2.2 together with a BouncyCastle upgrade and activated by SIP-6; Falcon is not height-gated in the code.
Wallet key modes
The key_mode setting of the client (or the wallet type in IceLeaf) decides what an address is made of. All modes produce ordinary-looking snow: addresses; the difference only shows when spending.
| key_mode | IceLeaf name | Keys | Spec | Recoverable from seed | Spend size |
|---|---|---|---|---|---|
seed default | HD Seed | secp256k1 from a BIP-39 12-word seed via BIP-44 m/44'/2338'/0'/0/i (2339 on testnet) | 1-of-1 type 1 | yes | ≈ 270 B |
standard | Old Standard | random secp256k1 key per address | 1-of-1 type 1 | no - back up the wallet directory | ≈ 270 B |
qhard | QHard | secp256k1 + RSA-8192 + DSTU 4145-431 | 3-of-3 | no | ≈ 4.4 KB |
pqc1 | PQC1 | secp256k1 + DSTU 4145-431 + Dilithium 5 + SPHINCS+ | 4-of-4 | no | ≈ 15 KB |
pqc_lite | PQC Lite | Falcon-512 | 1-of-1 | no | ≈ 1.6 KB |
key_mode in the config governs what the key pool generates - but since 2.2 any wallet can also mint addresses of any mode on demand: newaddress --key-mode pqc_lite (or --quantum) creates a Falcon address inside an ordinary seed wallet, --count N makes several, and keymodes explains the options. A deliberately minted address is reserved (marked used) so it never appears as the change output of an unrelated send; --pool leaves it in the fresh pool instead. addresses --quantum, balance --quantum and outputs --quantum show only the quantum-resistant part of a wallet - an address counts when spending it requires a post-quantum signature (pqc_lite, and pqc1's 4-of-4). The multisig helper (AddressUtil.getMultiSig) now validates 1 ≤ M ≤ N and builds real M-of-N specs (an inverted guard used to reject them and admit unspendable N+1-of-N ones); arbitrary shared M-of-N addresses between people are built with pubkey and makemultisig (how-to), and the explorer shows specs as e.g. “3of3 ECDSA RSA DSTU4145” when revealed.
Quantum resistance
Snowblossom distinguishes quantum resistant (no known quantum speed-up) from quantum safe (proven). The design has three layers of protection:
- Keys are hidden until spent. An address is a hash; the public keys appear on chain only in the claims of a spending transaction. An attacker with a discrete-log-breaking quantum computer can only target keys that are already public - i.e. during the window between broadcast and confirmation, where first-seen-first-added in the mempool gives the original transaction the advantage. Reusing addresses weakens this, which is why the wallet hands out fresh addresses and change goes to new ones.
- Algorithm diversity (QHard, 2018). A 3-of-3 over secp256k1, RSA-8192 and DSTU 4145 must be broken entirely to spend. RSA-8192 needs on the order of 16,000 logical qubits with Shor's algorithm versus ~1,600 for a 256-bit curve, buying time rather than immunity.
- Post-quantum algorithms (SIP-6, 2025). SPHINCS+ (hash-based - safe even if lattice assumptions fail), Dilithium and Falcon (lattice-based, NIST finalists). A
pqc1address requires all four of its keys, so it stays secure as long as any one algorithm holds.pqc_litetrades that belt-and-braces approach for small Falcon signatures.
Costs: PQC signatures are large, so fees (per byte) are higher, and these wallets are not seed-derived - back up the wallet directory whenever new addresses are generated (see wallet files). Migrating is simply sending coins from an old wallet to an address of a new pqc1 wallet.
Keys in wallet files
message WalletKeyPair {
int32 signature_type = 1; bytes public_key = 2; bytes private_key = 3; // PKCS#8 DER
bytes seed_id = 4; string hd_path = 5; int32 hd_change = 6; int32 hd_index = 7; // HD keys only
}
seed_id is the BIP-32 identifier of the account key m/44'/coin'/0', which also identifies an xpub, so a watch-only wallet can be built from the xpub alone (watch-only wallets).
Other address facts
- Burn addresses:
HoleGen <text>turns a string into a valid address whose preimage nobody knows (Duck32.mangleString). Coins sent there are gone. - Vanity addresses:
VanityGenbrute-forces standard keys; only the 32 Duck32 characters can appear, so “snow:s” is possible but “snow:b” is not. - Site keys: seeds can derive deterministic per-site keys under purpose
981231'for identity/login use (SeedUtil.getSiteKey); not used by the CLI. - Signed messages: the same AddressSpec + signature structure (
SignedMessage) authenticates node TLS certificates and trust-network peer info; single-key specs only.