Building mochimo_wots: A Dart Implementation of Mochimo WOTS Cryptography
mochimo_wots is my Dart/Flutter library for the Mochimo network’s Winternitz One-Time Signature (WOTS) protocol — hash chains, 2208-byte addresses, signing/verification, tagged identities, datagram framing, and CRC checks — published as a reusable package.
I built it as Prakash Niraula, System Engineer at AI Kensetsu Co., Ltd in Sakai, Osaka. The goal was not a demo UI — it was a correct, testable protocol implementation other Flutter developers can depend on.
Why WOTS is hard to implement well
WOTS is a hash-based one-time signature scheme. Security comes from one-way hash chains, not elliptic curves. That changes the engineering:
- One-time use — a WOTS keypair should sign once; reuse leaks secret material.
- Large keys — Mochimo uses
2144bytes of signature/public-key material, not a 64-byte curve sig. - Exact layouts — addresses are
2208bytes; datagrams are8920bytes. Endian mistakes break peers silently. - Protocol fidelity — chain lengths, checksum digits, tag placement, and CRC must match the network.
This is systems + cryptography work: buffers, little-endian packing, PRFs, and interoperability tests.
What the package includes
Current release: v1.0.3 (Dart SDK ≥ 3.0, Flutter ≥ 3.0).
- WOTS core — seed expansion, base-w encoding, checksum, chains, sign, verify
- Wallet API — deterministic create, sign/verify, secure
clear() - Tags — 12-byte tags + Base58 helpers
- Datagram codec — serialize/parse network frames + capabilities
- Utilities —
MochimoHasher,ByteBuffer,ByteUtils,CRC16 - Tests & examples — unit suites for each major module
Protocol constants
wots parameters
WOTSW = 16
WOTSLOGW = 4
PARAMSN = 32
WOTSLEN1 = 64
WOTSLEN2 = 3
WOTSLEN = 67
WOTSSIGBYTES = 2144 // 67 × 32
Address = 2208 // 2144 + 32 + 32
Tag = 12
Datagram = 8920
If these drift, local round-trips can still “pass” while network peers reject you.
Package layout
lib/
lib/
mochimo_wots.dart
core/
hasher/mochimo_hasher.dart
model/byte_buffer.dart
protocol/
wots.dart
wots_hash.dart
wots_address.dart
wots_wallet.dart
tags.dart
datagram.dart
utils/
byte_utils.dart
crc16.dart
tag_utils.dart
Keep protocol math pure and testable; expose WOTSWallet as the safer app-facing API.
Install
pubspec.yaml
dependencies:
mochimo_wots: ^1.0.3
terminal
dart pub get
# or
flutter pub get
Address layout (2208 bytes)
- 0–2143 — WOTS public key chains (67 × 32)
- 2144–2175 — public seed (32)
- 2176–2207 — address/rnd seed (32), with optional 12-byte tag region
Tags give a shorter identity; full WOTS material is still required for verification.
Create a wallet (correct path)
Use WOTSWallet.create(...). It expands the secret, builds the 2208-byte address, and attaches a tag.
dart — create wallet
import 'dart:typed_data';
import 'package:mochimo_wots/mochimo_wots.dart';
import 'package:mochimo_wots/core/utils/byte_utils.dart';
void main() {
// Production: CSPRNG / secure keystore — never hardcode secrets.
final secret = Uint8List(32);
for (var i = 0; i < 32; i++) {
secret[i] = i + 1;
}
final tag = Uint8List(12)..fillRange(0, 12, 0x34);
final wallet = WOTSWallet.create('Main Wallet', secret, tag);
print(wallet.getAddrTagHex());
print(wallet.getAddrTagBase58());
final wots = wallet.getWots();
if (wots != null) {
print('len=${wots.length}'); // 2208
print(ByteUtils.bytesToHex(wots.sublist(0, 32)));
}
wallet.clear();
}
Tip: Default component derivation hashes seed || "seed", seed || "publ", and seed || "addr". Keep those labels stable forever or addresses will diverge.
Sign and verify
Signing walks each chain to a message-dependent height. Verification finishes each chain to the public tip and compares the reconstructed public key.
dart — sign / verify
import 'dart:convert';
import 'dart:typed_data';
import 'package:mochimo_wots/mochimo_wots.dart';
void main() {
final secret = Uint8List(32)..fillRange(0, 32, 0x56);
final tag = Uint8List(12)..fillRange(0, 12, 0x34);
final wallet = WOTSWallet.create('Signer', secret, tag);
final message = Uint8List.fromList(utf8.encode('Hello, Mochimo!'));
final signature = wallet.sign(message); // 2144 bytes
print(wallet.verify(message, signature)); // true
final bad = Uint8List.fromList(message)..[0] ^= 0x01;
print(wallet.verify(bad, signature)); // false
wallet.clear();
}
Security: The API can sign more than once, but production spend paths must treat WOTS keys as one-time and rotate after use.
Low-level WOTS control
dart — generateAddress
import 'dart:typed_data';
import 'package:mochimo_wots/core/protocol/wots.dart';
import 'package:mochimo_wots/core/protocol/wots_wallet.dart';
import 'package:mochimo_wots/core/utils/byte_utils.dart';
void main() {
final secret = Uint8List(32)..fillRange(0, 32, 0x12);
final tag = Uint8List(12)..fillRange(0, 12, 0xAB);
final address = WOTS.generateAddress(
tag,
secret,
WOTSWallet.componentsGenerator,
);
print(ByteUtils.bytesToHex(address.sublist(0, 64)));
print(WOTS.isValid(secret, address));
}
Core primitives: expandSeed, baseW_, wotsChecksum, genChain, wotsSign, wotsPkFromSig.
ByteBuffer and endianness
Most chain bugs are binary I/O bugs. The package includes an explicit-endian ByteBuffer because datagram fields are little-endian in many places, while some crypto counters are big-endian.
dart — byte buffer
import 'package:mochimo_wots/mochimo_wots.dart';
import 'package:mochimo_wots/core/utils/byte_utils.dart';
void main() {
final buffer = ByteBuffer.allocate(16);
buffer.order(ByteOrder.BIG_ENDIAN).putInt(0x12345678);
buffer.order(ByteOrder.LITTLE_ENDIAN).putInt(0x9ABCDEF0);
buffer.rewind();
print(ByteUtils.bytesToHex(buffer.array()));
}
Datagram framing
Datagram encodes an 8920-byte frame: version/flags, network + session ids, operation codes, block metadata, three 2208-byte addresses, amounts, 2144-byte signature, CRC16, trailer.
dart — datagram
import 'package:mochimo_wots/mochimo_wots.dart';
void main() {
final dg = Datagram()
..setId1(1)
..setId2(2)
..setOperation(Operation.balanceRequest)
..setCurrentBlockHeight(BigInt.from(1000))
..setAddToPeerList(true);
final bytes = dg.serialize();
print(bytes.length); // 8920
final parsed = Datagram.of(bytes);
print(parsed.getOperation());
print(parsed.getCRC());
}
Hashing helpers
dart — MochimoHasher
import 'dart:typed_data';
import 'package:mochimo_wots/core/hasher/mochimo_hasher.dart';
import 'package:mochimo_wots/core/utils/byte_utils.dart';
void main() {
final data = Uint8List.fromList('mochimo'.codeUnits);
print(ByteUtils.bytesToHex(MochimoHasher.hash(data)));
print(ByteUtils.bytesToHex(MochimoHasher.hashWith('sha3-512', data)));
print(ByteUtils.bytesToHex(MochimoHasher.hashWith('ripemd160', data)));
}
Dependencies stay small: PointyCastle, base58check, meta, Flutter SDK.
Skills this project required
- Cryptographic literacy — WOTS chains, checksum digits, one-time key discipline
- Binary protocol design — fixed widths, endianness, CRC framing
- Dart systems work —
Uint8List, copies vs views, secret wiping - API design — low-level
WOTS+ higher-levelWOTSWallet - Test discipline — hasher, tags, wallet, address, datagram, WOTS suites
- Open-source packaging — pub.dev metadata, examples, MIT, versioned API
Pitfalls worth documenting
- Prefer
WOTSWallet.create— a bare constructor may not synthesize a full 2208-byte address. - Tags are 12 bytes — older 20-byte assumptions will throw.
- Component labels matter — default is
seed/publ/addr; custom KDFs must stay consistent. - Never ship demo secrets — fill bytes like
0x12/0x56are tests only. - Validate on the wire — local verify can pass while datagram CRC/endian still fails peers.
Quick checklist
- Add
mochimo_wots: ^1.0.3 - Generate a secure 32-byte secret
WOTSWallet.create('name', secret, tag12)- Persist public address/tag; protect the secret
- Sign → verify → then broadcast
- Use
Datagramif you speak the peer protocol - Call
wallet.clear()when disposing sensitive state
Most of my public work is full-stack and operational — SaaS like Web Converter Tools, CRM systems, Flutter apps, Laravel/Next.js, and infrastructure. mochimo_wots is the cryptographic counterpart: protocol-level Dart, published and documented.