Repository navigation
KasSigner v1.0.6 - On a Multisig Standard
Reproducible Build Hashes
Every released image is a production build. There are twelve, four per target:
signed and unsigned, app-only and full-flash.
Signed
App-only
Waveshare: 7fc5aade9dd88e8544ceaa5eaabd72e9f60f44f63184129d82b89fd8df18c281
Waveshare-AF: 9dde6e638d917de765ea84a05b7e24c9435385fdb993062ab397062bfa1c91e7
M5Stack: 3bd0f644b83559b36def52aeff2d4bd9581db3fa346b70907171a1b9d95b11ec
Full-flash
Waveshare: 5deaa7a085c22922349fa72eb6be80325c505bf9e2240c188f570dae54e6b43c
Waveshare-AF: 47bdc255f2f89135692b3bc23bf4a98120eefca1013df501b8cfe21d0b9ff3c9
M5Stack: d9a219af29286c863b04f5b7587c316af7855fa52071d579753ab9b365e7fe1d
Unsigned (what your own build produces, and what you compare against):
App-only
Waveshare: 5ed40eb107481a71c4cc460ff0d5c29c315ddfda31ff94bde956df2a3fc3e607
Waveshare-AF: 6e649dde5d9fd4c5559760211913927cd9be1d7e0d3144dd929f19e97a867b5b
M5Stack: 3bec88e758051934fb72c6f4ebfd7c6ccafc45c234c14ea55c8b6d2a1b356916
Full-flash
Waveshare: c32d05c2a624c06daa67691232e07ea0fac9e3ed5d07460ba1db4912b60257b8
Waveshare-AF: 1f7a8c64d74f2c66166a5ca079a0c7b23f2331b6024b70a4a8d6adc3ab45bf72
M5Stack: 650b0bd29b0e7f06d0890500b9e091f52554c1b0751c15238bdefb73700475e9
Reproduce the unsigned set:
git clone https://github.com/InKasWeRust/KasSigner.git && cd KasSigner
git checkout v1.0.6
docker build --platform linux/amd64 -f Dockerfile.base -t kassigner-toolchain:v3 .
docker build --platform linux/amd64 -t kassigner-build .
docker run --rm kassigner-buildThe toolchain tag stays v3: same package versions as 1.0.5. What changed is
where apt fetches them from. Ubuntu's live archive drops a package version the
day a security update supersedes it, which made the pinned toolchain
unbuildable on 2026-08-18; Dockerfile.base now points apt at Ubuntu's snapshot
archive as of the pin date, so the exact versions stay downloadable. Verified
2026-08-18: two full builds after docker builder prune -af produced
byte-identical results for every target.
Signed and unsigned hashes differ. The developer signature is embedded in
the firmware and changes the code segment, so your build will never match a
signed hash. Compare unsigned against unsigned.
Verify Code Segments
Each build also produces a hash over the executable code segment only, and that
is the number the device displays on its own verification screen. On a
production build it is trustworthy: verification is enforced, so a device that
reaches the UI has already proved the running code matches.
Signed Unsigned
Waveshare: 1d68a84a545d0a6d98640f4399f99fb6611a2689d4f078e51f5e95f3aeac7d9b 66c0518b642a4ebc1250dfe2dce88f84ed60b6b499ace76aac63ee0f9f513bc6
Waveshare-AF: 163edfe0a9eca5d493d75b996340c5d2d2e35077f7cfe86952b3c4e2003c148f e594c5f8caac45b6e4df88080ce87ab54000cabc8aad9af15c7d9e704016efe1
M5Stack: ceac7414902feb94bdb5cf01ad2f70815b5e6f6e63bc5102fb24ae5a1b742f2e f458134f14639776f1801408d1d946e041ef82bccaaf8f98b49f846dec5c1b2b
Read yours from the container:
docker run --rm kassigner-build cat /build/kassigner-waveshare-unsigned.codehashFlashing Pre-built Binaries
Pick the image that matches your board. Waveshare-AF is for the autofocus
camera module, which is mounted flipped and needs an orientation correction.
M5Stack is the CoreS3 Lite.
Option A: Full-flash image (recommended for new users)
No Rust toolchain needed. Download the -full.bin for your board, install
esptool, and flash at 0x0:
pip3 install esptool
# Waveshare
python3 -m esptool --port <port> --baud 460800 write_flash 0x0 kassigner-waveshare-full.bin
# Waveshare with the AF camera module
python3 -m esptool --port <port> --baud 460800 write_flash 0x0 kassigner-waveshare-af-full.bin
# M5Stack CoreS3 Lite
python3 -m esptool --port <port> --baud 460800 write_flash 0x0 kassigner-m5stack-full.binThe full-flash images include the ESP-IDF bootloader, the partition table, and
the signed firmware. Everything needed to boot from a blank chip.
Option B: App-only image (for developers)
If the device already has a bootloader and partition table from a previous
cargo run, flash just the app at 0x10000:
python3 -m esptool --port <port> --baud 460800 write_flash 0x10000 kassigner-waveshare.binReplace the port with your device's serial port (ls /dev/cu.usb* on macOS,
ls /dev/ttyACM* /dev/ttyUSB* on Linux).
Secure Boot boards
On an eFuse-provisioned board, RSA-sign the app image with your own Secure Boot
key first (espsecure sign_data, see docs/EFUSE_RUNBOOK.md) and flash the
result at 0x10000. Both boards take the same procedure.
After flashing: the USB port goes quiet, and that is correct
Released binaries are production builds. The firmware gates the USB Serial/JTAG
peripheral a second or two into boot, and prints nothing over serial. A dead
port and a silent log after flashing a release is the device working as
intended, not a failed flash.
To reflash, enter download mode first: unplug USB, hold BOOT, plug USB back
in, then release. Both boards have a BOOT button and take the same sequence.
Verified on both, including a Secure Boot provisioned M5Stack CoreS3 Lite.
What's New
A multisig and hardening release. Multisig moves to the Kaspa 45' scheme,
proven end to end on mainnet between two devices. Several security reviews of
v1.0.5 were answered: every finding was checked against the source and either
fixed, disputed with evidence, or declined with the reasoning recorded.
Multisig
- 45' multisig: cosigners derive at
m/45'/111111'/0'/<cosigner>/<chain>/<index>,
keys sorted so every implementation computes the same address, chain 1 for
change. Create on device from scanned cosigner kpubs, co-sign between devices
by QR, spend from several addresses in one transaction. Written up in
KIP: Multisig Wallet Conventions for Kaspa.
Existing 44' wallets still load and spend; new wallets are 45' only - Change verification: change claimed by a transaction is checked against
the loaded descriptor. Verified is teal, unverifiable is orange, and a claim
the descriptor contradicts is marked FORGED and refused - The descriptor is a second secret: shown as a QR and saved to SD, plain
or encrypted. A seed alone cannot find or spend multisig funds. Back up seed
and descriptor - kpub Multisig QR: a separate, labelled export of the 45' key. The 44'
watch-only kpub is byte-indistinguishable in form; the label is the
distinction - KSPT v4: derivation hints ride the transaction. A single-sig transaction
is byte-identical to before; older firmware refuses the new version cleanly
Security
- Consensus sighash tests at every boot: 27 vectors from the rusty-kaspa
2.0.1 consensus tests, all six sighash types and both transaction versions,
run before the menu on every power-on and halt the device on divergence. The
sighash self-tests turned out never to have executed in any build before - Covenant binding validation: a malformed
covenantIdcould leave the
field zeroed with the binding still active; both members are now required and
checked, and a decode error rejects the transaction - Parser hardening: declared counts checked,
{and[tracked separately
with a depth ceiling, duplicate keys guarded, both binary parsers require the
buffer fully consumed - Hardware RNG measured for the first time (SP 800-90B, offline, both
boards) and health-checked on every draw; camera and touch entropy measured
too. All of it in docs/ENTROPY.md - Every diagnostic feature is a compile error together with
production - Secure Boot state on the verification screen: five read-only eFuse bits
- Backup KDF stays PBKDF2-HMAC-SHA256 at 100,000 rounds, by decision: a
six-fold increase buys under three bits against rented GPUs; the salt and the
password are where the security is
Fixes
- PSKB round-trip capacity 5 inputs to 9; SD transaction files were capped at
1,024 bytes; the encrypted KSPT round trip could not complete; frame count
could wrap above 255. All fixed - SD reads: a short FAT chain and a circular FAT chain are now detected and
named instead of returning a truncated file as success - Multi-frame QR limit 40 to 64 frames; three rqrr grid bounds after host
fuzzing - Multisig signing hoisted from hundreds of derivations per input to one;
signing is now effectively independent of N - Covenant spends were under-paying fees; fee floor corrected
KasSee
- 45' multisig: load a descriptor and one of its addresses, watch the wallet,
build spends across addresses, relay between cosigners, broadcast - Network layer reworked: pooled WebSocket, failing resolvers remembered,
timeouts drop the socket instead of counting as success, history scans paced.
Wallet load from minutes to seconds on deep history - Amounts in BigInt everywhere; overflow checks on the release profile;
UTXO selection and consolidation at the signer's 32-input limit
Docs
- SECURITY.md, README, CHANGELOG, CONTRIBUTING and the four
docs/*.md
rewritten for 1.0.6; newdocs/ENTROPY.md; the five PDFs regenerated
Known limitations
- A 45' PSKB from an external coordinator that carries no derivation hints
cannot yet be signed; KasSee always writes them, so this affects other
coordinators only - The multisig descriptor is a second secret with the same backup requirement
as the seed. Losing it loses access to the funds
Full changelog: CHANGELOG.md
KasSee Web: kassigner.org