Skip to content

KasSigner v1.0.6 - On a Multisig Standard

Choose a tag to compare

@InKasWeRust InKasWeRust released this 18 Aug 23:52
· 20 commits to main since this release
e2461ae

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-build

The 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.codehash

Flashing 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.bin

The 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.bin

Replace 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 covenantId could 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; new docs/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