#!/usr/bin/env python3 # Copyright (C) 2026 Whiterun LLC, # This software is licensed under the GNU Lesser General Public License (LGPL), version 3.0 or later. # # Regenerates packages/core/src/protocols/message-signing.vectors.json. # # The `sign_message` extension exists to produce signatures that OTHER software # accepts. Asserting our own construction against our own reimplementation of it # proves nothing, so the vectors these tests run against are generated by a real # Electron Cash install — its msg_magic, its signer, its address encoder. # # Usage: # contrib/generate-message-signing-vectors.py [EC_SITE_PACKAGES] > \ # packages/core/src/protocols/message-signing.vectors.json # # EC_SITE_PACKAGES defaults to the AppImage-style layout. Electron Cash bundles # its own interpreter, so run this with that interpreter, not the system one: # # APPDIR=/opt/electron-cash \ # LD_LIBRARY_PATH=/opt/electron-cash/usr/lib/:/opt/electron-cash/usr/lib/x86_64-linux-gnu \ # /opt/electron-cash/usr/bin/python3.11 -s contrib/generate-message-signing-vectors.py import binascii import json import sys import base64 DEFAULT_SITE_PACKAGES = "/opt/electron-cash/usr/lib/python3.11/site-packages" site_packages = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_SITE_PACKAGES sys.path.insert(0, site_packages) from electroncash.bitcoin import regenerate_key, msg_magic, Hash # noqa: E402 from electroncash.address import Address # noqa: E402 from electroncash.version import PACKAGE_VERSION # noqa: E402 # Fixed test keys. Key 1 is the secp256k1 generator's scalar — convenient and # stable; the second is arbitrary, to catch anything that only works for key 1. KEYS = { "one": "00" * 31 + "01", "arbitrary": "c96d9a1f7f1a3e5b2d84f0a7c31e6b90d5427ac8fe13b6420d9e8a7c5b3f1d2e", } MESSAGES = [ ("simple", "hello"), ("login", "wizardconnect login nonce=8f3a21c0d4 issued=2026-08-06T10:00:00Z"), ("empty", ""), ("multibyte", "Straße 日本語 \U0001f345"), ("multiline", "line one\nline two\n"), ("whitespace", " padded "), # The compactSize length prefix widens from one byte to three at 253. ("compactsize_252", "a" * 252), ("compactsize_253", "a" * 253), ] vectors = [] for key_name, key_hex in KEYS.items(): key = regenerate_key(binascii.unhexlify(key_hex)) for compressed in (True, False): pubkey = key.get_public_key(compressed) address = Address.from_pubkey(pubkey) for message_name, message in MESSAGES: signature = key.sign_message(message, compressed) vectors.append( { "name": f"{key_name}/{message_name}/{'compressed' if compressed else 'uncompressed'}", "privateKey": key_hex, "message": message, "compressed": compressed, "publicKey": pubkey if isinstance(pubkey, str) else binascii.hexlify(pubkey).decode(), "signature": base64.b64encode(signature).decode(), "cashaddr": address.to_full_string(Address.FMT_CASHADDR), "legacy": address.to_full_string(Address.FMT_LEGACY), "preimageHash": binascii.hexlify( Hash(msg_magic(message.encode("utf8"))) ).decode(), "header": signature[0], } ) print( json.dumps( { "_provenance": { "generatedBy": "Electron Cash", "electronCashVersion": PACKAGE_VERSION, "regenerate": "contrib/generate-message-signing-vectors.py", "note": ( "Signatures, addresses and preimage hashes here are produced by " "Electron Cash, not by this repository. They are the external " "reference the sign_message extension must match. Signature bytes " "are NOT portable across implementations (Electron Cash and libauth " "derive the ECDSA nonce differently), so a conforming implementation " "must reproduce preimageHash exactly and must VERIFY these " "signatures — it will not reproduce them byte for byte." ), }, "vectors": vectors, }, indent=2, ensure_ascii=False, ) )