Snowblossom Docs
Docs › Protocol › Addresses & signatures

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”:

  1. 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.
  2. whole = data20 ‖ checksum5 (25 bytes = 200 bits) is treated as one unsigned integer and written in base 32.
  3. 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).

#TypeAlgorithmPublic keySignatureQuantum
1ECDSA_COMPRESSEDECDSA 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 Bno
2ECDSAECDSA with an X.509 key; curves secp256k1, secp384r1, secp521r1, sect571k1, sect571r1.88–96 B71–151 Bno
3DSADSA (SHA-1); wallet generates 3072-bit.≈ 1,227 B≈ 70 Bno
4RSARSA PKCS#1 v1.5 over the raw hash (“NONEwithRSA”); any size, wallet uses 8192-bit.≈ 1,058 B @ 8192modulus size: 1,024 B @ 8192no (but needs ~2× bits in qubits)
5DSTU4145DSTU 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 B44–110 Bno
6SPHINCSPLUSSPHINCS+ haraka-128s, stateless hash-based signatures. Gated by SIP-6 (mainnet block 358,700).52 B7,856 Byes
7DILITHIUMCRYSTALS-Dilithium 5 (round-3 parameters, not ML-DSA/FIPS 204). Gated by SIP-6.2,616 B≈ 4,627 Byes
8FALCONFalcon-512, lattice (NTRU) based, compact.915 B≈ 655 Byes

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.

SHA-1 inside ECDSA (SA-1)The ECDSA and DSA engines hash the 32-byte transaction id with SHA-1 before signing. This was reviewed in Security Advisory 1: to exploit it an attacker would need a second-preimage on SHA-1 and the colliding value would itself have to be the Skein hash of a valid transaction - so it is a cosmetic flaw, not a vulnerability. It will be fixed by a signature-mode flag when a protocol change is next scheduled.

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_modeIceLeaf nameKeysSpecRecoverable from seedSpend size
seed defaultHD Seedsecp256k1 from a BIP-39 12-word seed via BIP-44 m/44'/2338'/0'/0/i (2339 on testnet)1-of-1 type 1yes≈ 270 B
standardOld Standardrandom secp256k1 key per address1-of-1 type 1no - back up the wallet directory≈ 270 B
qhardQHardsecp256k1 + RSA-8192 + DSTU 4145-4313-of-3no≈ 4.4 KB
pqc1PQC1secp256k1 + DSTU 4145-431 + Dilithium 5 + SPHINCS+4-of-4no≈ 15 KB
pqc_litePQC LiteFalcon-5121-of-1no≈ 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:

  1. 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.
  2. 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.
  3. Post-quantum algorithms (SIP-6, 2025). SPHINCS+ (hash-based - safe even if lattice assumptions fail), Dilithium and Falcon (lattice-based, NIST finalists). A pqc1 address requires all four of its keys, so it stays secure as long as any one algorithm holds. pqc_lite trades 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: VanityGen brute-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.