151 lines
5.7 KiB
Python
151 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Classical-only Teensy 4.1 n_signer test (no OTP, no PQ).
|
|
|
|
Runs the full classical + Nostr sequence in one uninterrupted boot and
|
|
prints exactly which verb succeeded and which one crashed the device.
|
|
After a crash, re-run to read the CrashReport from the next boot.
|
|
|
|
Usage:
|
|
python3 firmware/teensy41/test_classical.py [--port /dev/ttyACM0]
|
|
"""
|
|
import serial, struct, json, time, sys, argparse, hashlib
|
|
|
|
DEFAULT_PORT = "/dev/ttyACM0"
|
|
BAUD = 115200
|
|
|
|
def send_request(ser, req):
|
|
payload = json.dumps(req).encode("utf-8")
|
|
ser.write(struct.pack(">I", len(payload)) + payload)
|
|
ser.flush()
|
|
h = b""
|
|
deadline = time.time() + 30.0
|
|
while len(h) < 4 and time.time() < deadline:
|
|
c = ser.read(4 - len(h))
|
|
if c: h += c
|
|
else: time.sleep(0.01)
|
|
if len(h) < 4: raise TimeoutError("hdr timeout")
|
|
n = struct.unpack(">I", h)[0]
|
|
if n == 0 or n > 65536: raise ValueError("bad len %d" % n)
|
|
p = b""
|
|
while len(p) < n:
|
|
c = ser.read(n - len(p))
|
|
if c: p += c
|
|
else: time.sleep(0.01)
|
|
return json.loads(p.decode())
|
|
|
|
def call(ser, method, params=None, idx=[0]):
|
|
idx[0] += 1
|
|
r = {"jsonrpc": "2.0", "id": idx[0], "method": method}
|
|
if params is not None: r["params"] = params
|
|
print(f" -> {method} {json.dumps(params) if params else '[]'}", flush=True)
|
|
resp = send_request(ser, r)
|
|
ok = "result" in resp
|
|
print(f" <- {'OK' if ok else 'ERR'} {str(resp)[:160]}", flush=True)
|
|
return resp
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--port", default=DEFAULT_PORT)
|
|
ap.add_argument("--read-crash", action="store_true", help="only drain and print boot/CrashReport text")
|
|
args = ap.parse_args()
|
|
|
|
ser = serial.Serial(args.port, BAUD, timeout=2.0)
|
|
time.sleep(5)
|
|
boot = b""
|
|
while ser.in_waiting:
|
|
boot += ser.read(ser.in_waiting)
|
|
if boot:
|
|
print("=== BOOT OUTPUT ===")
|
|
print(boot.decode("utf-8", errors="replace"))
|
|
print("=== END BOOT ===")
|
|
if args.read_crash:
|
|
ser.close(); return 0
|
|
|
|
passed = 0; failed = 0
|
|
def t(name, fn):
|
|
nonlocal passed, failed
|
|
print(f"\n[{name}]", flush=True)
|
|
try:
|
|
fn()
|
|
passed += 1
|
|
except Exception as e:
|
|
print(f" !! CRASH at {name}: {e}", flush=True)
|
|
failed += 1
|
|
raise
|
|
|
|
try:
|
|
t("get_info", lambda: call(ser, "get_info"))
|
|
# classical pubkeys
|
|
pubkeys = {}
|
|
def gpk(alg):
|
|
r = call(ser, "get_public_key", [{"algorithm": alg, "index": 0}])
|
|
pubkeys[alg] = r["result"]["public_key"]
|
|
t("gpk secp256k1", lambda: gpk("secp256k1"))
|
|
t("gpk ed25519", lambda: gpk("ed25519"))
|
|
t("gpk x25519", lambda: gpk("x25519"))
|
|
# secp256k1 schnorr sign + verify
|
|
msg = hashlib.sha256(b"test schnorr").hexdigest()
|
|
sig = [None]
|
|
def schnorr_sign():
|
|
r = call(ser, "sign", [msg, {"algorithm": "secp256k1", "index": 0, "scheme": "schnorr"}])
|
|
sig[0] = r["result"]["signature"]
|
|
t("sign schnorr", schnorr_sign)
|
|
def schnorr_verify():
|
|
call(ser, "verify", [msg, sig[0], {"algorithm": "secp256k1", "index": 0, "scheme": "schnorr"}])
|
|
t("verify schnorr", schnorr_verify)
|
|
# secp256k1 ecdsa sign + verify
|
|
msg2 = hashlib.sha256(b"test ecdsa").hexdigest()
|
|
sig2 = [None]
|
|
def ecdsa_sign():
|
|
r = call(ser, "sign", [msg2, {"algorithm": "secp256k1", "index": 0, "scheme": "ecdsa"}])
|
|
sig2[0] = r["result"]["signature"]
|
|
t("sign ecdsa", ecdsa_sign)
|
|
def ecdsa_verify():
|
|
call(ser, "verify", [msg2, sig2[0], {"algorithm": "secp256k1", "index": 0, "scheme": "ecdsa"}])
|
|
t("verify ecdsa", ecdsa_verify)
|
|
# ed25519 sign + verify
|
|
msg3 = b"test ed25519 message".hex()
|
|
sig3 = [None]
|
|
def ed_sign():
|
|
r = call(ser, "sign", [msg3, {"algorithm": "ed25519", "index": 0}])
|
|
sig3[0] = r["result"]["signature"]
|
|
t("sign ed25519", ed_sign)
|
|
def ed_verify():
|
|
call(ser, "verify", [msg3, sig3[0], {"algorithm": "ed25519", "index": 0}])
|
|
t("verify ed25519", ed_verify)
|
|
# x25519 shared secret
|
|
def x25519():
|
|
call(ser, "derive_shared_secret", [pubkeys["x25519"], {"algorithm": "x25519", "index": 0}])
|
|
t("derive_shared_secret x25519", x25519)
|
|
# derive (HMAC)
|
|
t("derive", lambda: call(ser, "derive", ["derive-test", {"algorithm": "secp256k1", "index": 1}]))
|
|
# nostr
|
|
npub = [None]
|
|
def ngpk():
|
|
r = call(ser, "nostr_get_public_key", [{"nostr_index": 0}])
|
|
npub[0] = r["result"]
|
|
t("nostr_get_public_key", ngpk)
|
|
def nse():
|
|
ev = {"kind": 1, "created_at": int(time.time()), "tags": [], "content": "hello event"}
|
|
call(ser, "nostr_sign_event", [ev, {"nostr_index": 0}])
|
|
t("nostr_sign_event", nse)
|
|
# nip04
|
|
def nip04():
|
|
r = call(ser, "nostr_nip04_encrypt", [npub[0], "hello via nip04", {"nostr_index": 0}])
|
|
call(ser, "nostr_nip04_decrypt", [npub[0], r["result"], {"nostr_index": 0}])
|
|
t("nip04 round-trip", nip04)
|
|
# nip44
|
|
def nip44():
|
|
r = call(ser, "nostr_nip44_encrypt", [npub[0], "hello via nip44", {"nostr_index": 0}])
|
|
call(ser, "nostr_nip44_decrypt", [npub[0], r["result"], {"nostr_index": 0}])
|
|
t("nip44 round-trip", nip44)
|
|
except Exception as e:
|
|
print(f"\n!! STOPPED: {e}", flush=True)
|
|
|
|
print(f"\nRESULTS: {passed} passed, {failed} failed")
|
|
ser.close()
|
|
return 0 if failed == 0 else 1
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|