104 lines
4.3 KiB
Python
104 lines
4.3 KiB
Python
|
|
#!/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,
|
||
|
|
)
|
||
|
|
)
|