v0.1.2 - Teensy 4.1 secp256k1 stack reductions + shared context: reduced WINDOW_A 5->4 (Strauss ecmult tables 8->4 entries, fixes schnorr verify stack overflow) and ECMULT_CONST_GROUP_SIZE 5->4 (ECDH tables 16->8, fixes nip04 crash); added secp256k1_get_shared_context() and reused it in dispatch.cpp ECDSA/schnorr helpers to eliminate per-request secp256k1_context_create/destroy heap fragmentation; reordered test_signer.py to test classical+nostr verbs before PQ verbs and added nostr_sign_event/nip04/nip44 round-trip tests
This commit is contained in:
@@ -468,25 +468,29 @@ __attribute__((section(".flashmem"))) static int derive_alg_key(fw_alg_t alg, ui
|
||||
* secp256k1 ECDSA sign/verify (scheme:"ecdsa")
|
||||
* ==================================================================== */
|
||||
|
||||
/* The three secp256k1 helpers below reuse the persistent shared context
|
||||
* (secp256k1_get_shared_context) instead of malloc/free-ing a fresh
|
||||
* secp256k1_context per call. The Teensy 4.1 has only ~140 KB of free heap
|
||||
* and a small DTCM stack; repeated context create/destroy fragments the
|
||||
* heap and eventually crashes the device (observed during verify after a
|
||||
* few sign/get_public_key calls). The shared context is created once and
|
||||
* randomized at first use; it is never destroyed during normal operation. */
|
||||
|
||||
__attribute__((section(".flashmem"))) static int secp256k1_ecdsa_sign32(const uint8_t privkey[32],
|
||||
const uint8_t msg32[32],
|
||||
uint8_t sig_out[64]) {
|
||||
const uint8_t msg32[32],
|
||||
uint8_t sig_out[64]) {
|
||||
secp256k1_context *ctx;
|
||||
secp256k1_ecdsa_signature sig;
|
||||
int rc = -1;
|
||||
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
|
||||
ctx = secp256k1_get_shared_context();
|
||||
if (ctx == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (!secp256k1_ecdsa_sign(ctx, &sig, msg32, privkey, NULL, NULL)) {
|
||||
goto done;
|
||||
return -1;
|
||||
}
|
||||
secp256k1_ecdsa_signature_serialize_compact(ctx, sig_out, &sig);
|
||||
rc = 0;
|
||||
done:
|
||||
secp256k1_context_destroy(ctx);
|
||||
return rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__attribute__((section(".flashmem"))) static int secp256k1_ecdsa_verify32(const uint8_t msg32[32],
|
||||
@@ -495,22 +499,18 @@ __attribute__((section(".flashmem"))) static int secp256k1_ecdsa_verify32(const
|
||||
secp256k1_context *ctx;
|
||||
secp256k1_ecdsa_signature sig;
|
||||
secp256k1_pubkey pub;
|
||||
int rc = -1;
|
||||
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
|
||||
ctx = secp256k1_get_shared_context();
|
||||
if (ctx == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, sig64)) {
|
||||
goto done;
|
||||
return -1;
|
||||
}
|
||||
if (!secp256k1_ec_pubkey_parse(ctx, &pub, pub33, 33)) {
|
||||
goto done;
|
||||
return -1;
|
||||
}
|
||||
rc = secp256k1_ecdsa_verify(ctx, &sig, msg32, &pub) ? 0 : -1;
|
||||
done:
|
||||
secp256k1_context_destroy(ctx);
|
||||
return rc;
|
||||
return secp256k1_ecdsa_verify(ctx, &sig, msg32, &pub) ? 0 : -1;
|
||||
}
|
||||
|
||||
__attribute__((section(".flashmem"))) static int secp256k1_priv_to_compressed_pub(const uint8_t priv[32],
|
||||
@@ -518,23 +518,19 @@ __attribute__((section(".flashmem"))) static int secp256k1_priv_to_compressed_pu
|
||||
secp256k1_context *ctx;
|
||||
secp256k1_pubkey pub;
|
||||
size_t olen = 33;
|
||||
int rc = -1;
|
||||
|
||||
ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
|
||||
ctx = secp256k1_get_shared_context();
|
||||
if (ctx == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (!secp256k1_ec_pubkey_create(ctx, &pub, priv)) {
|
||||
goto done;
|
||||
return -1;
|
||||
}
|
||||
if (!secp256k1_ec_pubkey_serialize(ctx, pub33, &olen, &pub,
|
||||
SECP256K1_EC_COMPRESSED)) {
|
||||
goto done;
|
||||
return -1;
|
||||
}
|
||||
rc = 0;
|
||||
done:
|
||||
secp256k1_context_destroy(ctx);
|
||||
return rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
@@ -1338,9 +1334,12 @@ __attribute__((section(".flashmem"))) static void handle_request(cJSON *req, con
|
||||
secure_memzero(pub33, sizeof(pub33));
|
||||
secure_memzero(msg32, sizeof(msg32));
|
||||
} else {
|
||||
/* schnorr verify against x-only pubkey */
|
||||
/* schnorr verify against x-only pubkey.
|
||||
* Reuse the shared context instead of
|
||||
* malloc/free per call (heap-fragmentation
|
||||
* crash fix). */
|
||||
secp256k1_context *ctx =
|
||||
secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
|
||||
secp256k1_get_shared_context();
|
||||
secp256k1_xonly_pubkey xonly;
|
||||
uint8_t pub32[32];
|
||||
if (ctx != NULL &&
|
||||
@@ -1352,7 +1351,8 @@ __attribute__((section(".flashmem"))) static void handle_request(cJSON *req, con
|
||||
&xonly)) {
|
||||
valid = 1;
|
||||
}
|
||||
if (ctx) secp256k1_context_destroy(ctx);
|
||||
/* Do NOT destroy ctx — it is the shared
|
||||
* global context. */
|
||||
secure_memzero(pub32, sizeof(pub32));
|
||||
}
|
||||
} else if (alg == FW_ALG_ED25519) {
|
||||
|
||||
@@ -126,6 +126,13 @@ void secp256k1_context_teardown(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Public accessor for the persistent shared secp256k1 context. Used by
|
||||
* dispatch.cpp's ECDSA/schnorr verify helpers so they don't malloc a fresh
|
||||
* context per request (which fragments the small Teensy 4.1 heap). */
|
||||
secp256k1_context *secp256k1_get_shared_context(void) {
|
||||
return create_context();
|
||||
}
|
||||
|
||||
__attribute__((section(".flashmem"))) static int compute_compressed_pubkey(const secp256k1_context *ctx,
|
||||
const uint8_t priv[32],
|
||||
uint8_t pub33[33]) {
|
||||
|
||||
@@ -37,6 +37,14 @@ int derive_secp256k1_keys_index(const uint8_t *seed, size_t seed_len,
|
||||
int schnorr_sign32(const uint8_t privkey[32], const uint8_t msg32[32],
|
||||
uint8_t sig64[64]);
|
||||
|
||||
/* Return the persistent, shared secp256k1 context (SIGN|VERIFY), creating
|
||||
* it lazily on first call. Reused by all secp256k1 operations (key derivation,
|
||||
* schnorr sign/verify, ECDSA sign/verify) to avoid per-request
|
||||
* secp256k1_context_create/destroy, which fragments the small Teensy 4.1
|
||||
* heap and eventually crashes. Returns NULL on allocation failure. */
|
||||
struct secp256k1_context_struct;
|
||||
struct secp256k1_context_struct *secp256k1_get_shared_context(void);
|
||||
|
||||
/* --- ed25519 (SSH signatures) --- */
|
||||
|
||||
/* Derive an ed25519 keypair from the mnemonic seed using SLIP-0010
|
||||
|
||||
@@ -28,8 +28,12 @@
|
||||
# define WINDOW_A 2
|
||||
# endif
|
||||
#else
|
||||
/* optimal for 128-bit and 256-bit exponents. */
|
||||
# define WINDOW_A 5
|
||||
/* optimal for 128-bit and 256-bit exponents. Allow the build to override
|
||||
* this (e.g. the Teensy 4.1 signer sets WINDOW_A=4 to halve the Strauss
|
||||
* ecmult stack tables; see secp256k1_arduino_config.h). */
|
||||
# ifndef WINDOW_A
|
||||
# define WINDOW_A 5
|
||||
# endif
|
||||
/** Larger values for ECMULT_WINDOW_SIZE result in possibly better
|
||||
* performance at the cost of an exponentially larger precomputed
|
||||
* table. The exact table size is
|
||||
|
||||
@@ -60,6 +60,18 @@
|
||||
#define ECMULT_CONST_GROUP_SIZE 4
|
||||
#endif
|
||||
|
||||
/* WINDOW_A: the Strauss ecmult table size for the na*A term (used by
|
||||
* secp256k1_ecmult, which backs schnorr verify and ECDSA verify). The
|
||||
* default (5) allocates secp256k1_fe aux[8] + secp256k1_ge pre_a[8] on
|
||||
* the stack inside secp256k1_ecmult (~1 KB). On the Teensy 4.1 the DTCM
|
||||
* stack is only ~9.6 KB free (RAM1 remainder after ITCM), and the
|
||||
* schnorr-verify call chain (handle_request -> secp256k1_schnorrsig_verify
|
||||
* -> secp256k1_ecmult) overflows it. WINDOW_A=4 halves the tables (4
|
||||
* entries each, ~512 B) at a small throughput cost. Must be in [2..24]. */
|
||||
#ifndef WINDOW_A
|
||||
#define WINDOW_A 4
|
||||
#endif
|
||||
|
||||
/* Modules required by nostr_core_lib usage */
|
||||
#ifndef ENABLE_MODULE_ECDH
|
||||
#define ENABLE_MODULE_ECDH 1
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
|
||||
Tests all verbs: get_info, get_public_key (all 6 algorithms), sign (secp256k1,
|
||||
ed25519, ml-dsa-65, slh-dsa-128s), verify, encrypt/decrypt (OTP pad),
|
||||
encapsulate/decapsulate (ML-KEM-768), derive_shared_secret (X25519), derive.
|
||||
encapsulate/decapsulate (ML-KEM-768), derive_shared_secret (X25519), derive,
|
||||
nostr_get_public_key, nostr_sign_event, nostr_nip04_encrypt/decrypt,
|
||||
nostr_nip44_encrypt/decrypt.
|
||||
|
||||
Ordering note: the PQ keygens (ml-dsa-65, slh-dsa-128s, ml-kem-768) are
|
||||
heap-heavy on the Teensy 4.1 (~140 KB free heap). Running all three back to
|
||||
back can exhaust/fragment the heap and crash the device. To keep the suite
|
||||
useful, the classical + nostr verbs (the common case) are tested FIRST on a
|
||||
fresh heap, then the PQ verbs are tested LAST. If a PQ verb crashes the
|
||||
device, the earlier results still stand.
|
||||
|
||||
Sends requests in the canonical n_signer wire format (see api.md §4.3):
|
||||
params is a JSON ARRAY of positional arguments, with the options object
|
||||
@@ -22,10 +31,12 @@ import sys
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
import base64
|
||||
|
||||
DEFAULT_PORT = "/dev/ttyACM0"
|
||||
BAUD = 115200
|
||||
|
||||
|
||||
def send_request(ser, req: dict) -> dict:
|
||||
"""Send a JSON-RPC request with 4-byte big-endian length prefix, read response."""
|
||||
payload = json.dumps(req).encode("utf-8")
|
||||
@@ -33,17 +44,14 @@ def send_request(ser, req: dict) -> dict:
|
||||
ser.write(header + payload)
|
||||
ser.flush()
|
||||
|
||||
# Read response header (4 bytes)
|
||||
resp_header = b""
|
||||
while len(resp_header) < 4:
|
||||
deadline = time.time() + 30.0
|
||||
while len(resp_header) < 4 and time.time() < deadline:
|
||||
chunk = ser.read(4 - len(resp_header))
|
||||
if chunk:
|
||||
resp_header += chunk
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
if len(resp_header) == 0 and time.time() % 5 < 0.1:
|
||||
continue
|
||||
|
||||
if len(resp_header) < 4:
|
||||
raise TimeoutError("Timeout reading response header")
|
||||
|
||||
@@ -51,7 +59,6 @@ def send_request(ser, req: dict) -> dict:
|
||||
if resp_len == 0 or resp_len > 65536:
|
||||
raise ValueError(f"Invalid response length: {resp_len}")
|
||||
|
||||
# Read response payload
|
||||
resp_payload = b""
|
||||
while len(resp_payload) < resp_len:
|
||||
chunk = ser.read(resp_len - len(resp_payload))
|
||||
@@ -59,14 +66,10 @@ def send_request(ser, req: dict) -> dict:
|
||||
resp_payload += chunk
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
|
||||
return json.loads(resp_payload.decode("utf-8"))
|
||||
|
||||
def test_verb(ser, name, params=None, id_counter=[0]):
|
||||
"""Send a request and return the result. Prints pass/fail.
|
||||
|
||||
params must be a JSON array (the canonical n_signer wire format) or None.
|
||||
"""
|
||||
def test_verb(ser, name, params=None, id_counter=[0]):
|
||||
id_counter[0] += 1
|
||||
req = {"jsonrpc": "2.0", "id": id_counter[0], "method": name}
|
||||
if params is not None:
|
||||
@@ -86,7 +89,6 @@ def test_verb(ser, name, params=None, id_counter=[0]):
|
||||
return resp
|
||||
elif "result" in resp:
|
||||
result = resp["result"]
|
||||
# Truncate long values for display
|
||||
display = json.dumps(result, indent=2)
|
||||
if len(display) > 500:
|
||||
display = display[:500] + "... (truncated)"
|
||||
@@ -96,6 +98,7 @@ def test_verb(ser, name, params=None, id_counter=[0]):
|
||||
print(f" ❌ FAIL: no result or error in response: {resp}")
|
||||
return resp
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Test Teensy 4.1 n_signer")
|
||||
parser.add_argument("--port", default=DEFAULT_PORT, help="Serial port (default: /dev/ttyACM0)")
|
||||
@@ -103,7 +106,7 @@ def main():
|
||||
|
||||
print(f"Connecting to {args.port}...")
|
||||
ser = serial.Serial(args.port, BAUD, timeout=2.0)
|
||||
time.sleep(2) # Wait for the device to be ready (auto-generate boot)
|
||||
time.sleep(5) # wait for auto-generate boot + key derivation
|
||||
|
||||
# Drain any boot messages
|
||||
while ser.in_waiting:
|
||||
@@ -113,17 +116,16 @@ def main():
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
pubkeys = {}
|
||||
|
||||
# 1. get_info
|
||||
r = test_verb(ser, "get_info")
|
||||
if r and "result" in r: passed += 1
|
||||
else: failed += 1
|
||||
|
||||
# 2. get_public_key for each algorithm
|
||||
# Canonical format: params = [ { "algorithm": "<alg>", "index": 0 } ]
|
||||
algorithms = ["secp256k1", "ed25519", "x25519", "ml-dsa-65", "slh-dsa-128s", "ml-kem-768"]
|
||||
pubkeys = {}
|
||||
for alg in algorithms:
|
||||
# 2. get_public_key for classical algorithms (lightweight, tested first)
|
||||
classical_algs = ["secp256k1", "ed25519", "x25519"]
|
||||
for alg in classical_algs:
|
||||
r = test_verb(ser, "get_public_key", [{"algorithm": alg, "index": 0}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
@@ -132,10 +134,8 @@ def main():
|
||||
failed += 1
|
||||
|
||||
# 3. sign + verify (secp256k1 schnorr)
|
||||
# Canonical format: params = [ "<msg_hex>", { "algorithm": "secp256k1", "index": 0, "scheme": "schnorr" } ]
|
||||
msg32 = hashlib.sha256(b"test message for signing").digest()
|
||||
msg_hex = msg32.hex()
|
||||
|
||||
r = test_verb(ser, "sign", [msg_hex, {"algorithm": "secp256k1", "index": 0, "scheme": "schnorr"}])
|
||||
sig_secp = None
|
||||
if r and "result" in r:
|
||||
@@ -145,7 +145,6 @@ def main():
|
||||
failed += 1
|
||||
|
||||
if sig_secp and "secp256k1" in pubkeys:
|
||||
# verify: params = [ "<msg_hex>", "<sig_hex>", { "algorithm": "secp256k1", "index": 0, "scheme": "schnorr" } ]
|
||||
r = test_verb(ser, "verify", [msg_hex, sig_secp, {"algorithm": "secp256k1", "index": 0, "scheme": "schnorr"}])
|
||||
if r and "result" in r and r["result"].get("valid") in (True, "true"):
|
||||
passed += 1
|
||||
@@ -157,10 +156,8 @@ def main():
|
||||
failed += 1
|
||||
|
||||
# 4. sign (ed25519)
|
||||
# Canonical format: params = [ "<msg_hex>", { "algorithm": "ed25519", "index": 0 } ]
|
||||
msg_raw = b"test ed25519 message"
|
||||
msg_raw_hex = msg_raw.hex()
|
||||
|
||||
r = test_verb(ser, "sign", [msg_raw_hex, {"algorithm": "ed25519", "index": 0}])
|
||||
sig_ed = None
|
||||
if r and "result" in r:
|
||||
@@ -180,28 +177,9 @@ def main():
|
||||
print(" ⏭️ SKIP: no signature or pubkey")
|
||||
failed += 1
|
||||
|
||||
# 5. sign (ml-dsa-65)
|
||||
r = test_verb(ser, "sign", [msg_raw_hex, {"algorithm": "ml-dsa-65", "index": 0}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# 6. sign (slh-dsa-128s) — this is slow (~1-2s on Teensy)
|
||||
print("\n--- sign (slh-dsa-128s) — may take 1-2 seconds ---")
|
||||
r = test_verb(ser, "sign", [msg_raw_hex, {"algorithm": "slh-dsa-128s", "index": 0}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# 7. encrypt + decrypt (OTP pad)
|
||||
# Canonical format: params = [ "<payload_b64>", { "algorithm": "otp" } ]
|
||||
# The OTP encrypt/decrypt verbs take a base64 payload and XOR it against
|
||||
# the bound OTP pad, returning the base64 result + pad offset.
|
||||
# 5. encrypt + decrypt (OTP pad)
|
||||
plaintext = b"Hello, OTP pad encryption test!"
|
||||
pt_b64 = __import__("base64").b64encode(plaintext).decode()
|
||||
|
||||
pt_b64 = base64.b64encode(plaintext).decode()
|
||||
r = test_verb(ser, "encrypt", [pt_b64, {"algorithm": "otp"}])
|
||||
ct_b64 = None
|
||||
if r and "result" in r:
|
||||
@@ -213,7 +191,7 @@ def main():
|
||||
if ct_b64:
|
||||
r = test_verb(ser, "decrypt", [ct_b64, {"algorithm": "otp"}])
|
||||
if r and "result" in r:
|
||||
pt_result = __import__("base64").b64decode(r["result"].get("result", ""))
|
||||
pt_result = base64.b64decode(r["result"].get("result", ""))
|
||||
if pt_result == plaintext:
|
||||
print(f" ✅ decrypt matches original plaintext")
|
||||
passed += 1
|
||||
@@ -227,10 +205,102 @@ def main():
|
||||
print(" ⏭️ SKIP: no ciphertext")
|
||||
failed += 1
|
||||
|
||||
# 8. encapsulate + decapsulate (ML-KEM-768)
|
||||
# Canonical format:
|
||||
# encapsulate: params = [ "<peer_pub_hex>", { "algorithm": "ml-kem-768", "index": 0 } ]
|
||||
# decapsulate: params = [ "<ct_hex>", { "algorithm": "ml-kem-768", "index": 0 } ]
|
||||
# 6. derive_shared_secret (X25519)
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
|
||||
peer_priv = X25519PrivateKey.generate()
|
||||
peer_pub = peer_priv.public_key()
|
||||
peer_pub_hex = peer_pub.public_bytes_raw().hex()
|
||||
r = test_verb(ser, "derive_shared_secret", [peer_pub_hex, {"algorithm": "x25519", "index": 0}])
|
||||
if r and "result" in r: passed += 1
|
||||
else: failed += 1
|
||||
except ImportError:
|
||||
print("\n--- derive_shared_secret (x25519) ---")
|
||||
print(" ⏭️ SKIP: 'cryptography' module not installed")
|
||||
failed += 1
|
||||
|
||||
# 7. derive (secp256k1 at index 1)
|
||||
r = test_verb(ser, "derive", ["derive-test-data", {"algorithm": "secp256k1", "index": 1}])
|
||||
if r and "result" in r: passed += 1
|
||||
else: failed += 1
|
||||
|
||||
# 8. nostr_get_public_key
|
||||
r = test_verb(ser, "nostr_get_public_key", [{"nostr_index": 0}])
|
||||
nostr_pub = None
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
nostr_pub = r["result"]
|
||||
if isinstance(nostr_pub, dict):
|
||||
nostr_pub = nostr_pub.get("public_key", "")
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# 9. nostr_sign_event
|
||||
if nostr_pub:
|
||||
event = {"kind": 1, "created_at": int(time.time()), "tags": [], "content": "hello from test_signer"}
|
||||
r = test_verb(ser, "nostr_sign_event", [event, {"nostr_index": 0}])
|
||||
if r and "result" in r: passed += 1
|
||||
else: failed += 1
|
||||
|
||||
# 10. nostr_nip04_encrypt + decrypt (the bug we fixed)
|
||||
if nostr_pub:
|
||||
nip04_pt = "hello via nip04"
|
||||
r = test_verb(ser, "nostr_nip04_encrypt", [nostr_pub, nip04_pt, {"nostr_index": 0}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
cipher = r["result"]
|
||||
r = test_verb(ser, "nostr_nip04_decrypt", [nostr_pub, cipher, {"nostr_index": 0}])
|
||||
if r and "result" in r and r["result"] == nip04_pt:
|
||||
print(f" ✅ nip04 round-trip plaintext recovered")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" ❌ nip04 round-trip mismatch")
|
||||
failed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# 11. nostr_nip44_encrypt + decrypt (the is_nip44 dispatch bug we fixed)
|
||||
if nostr_pub:
|
||||
nip44_pt = "hello via nip44"
|
||||
r = test_verb(ser, "nostr_nip44_encrypt", [nostr_pub, nip44_pt, {"nostr_index": 0}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
cipher44 = r["result"]
|
||||
r = test_verb(ser, "nostr_nip44_decrypt", [nostr_pub, cipher44, {"nostr_index": 0}])
|
||||
if r and "result" in r and r["result"] == nip44_pt:
|
||||
print(f" ✅ nip44 round-trip plaintext recovered")
|
||||
passed += 1
|
||||
else:
|
||||
print(f" ❌ nip44 round-trip mismatch")
|
||||
failed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# ---- PQ verbs (tested LAST: heap-heavy, may crash the device) ----
|
||||
print("\n=== PQ verbs (heap-heavy; tested last) ===")
|
||||
|
||||
# 12. get_public_key for PQ algorithms
|
||||
pq_algs = ["ml-dsa-65", "slh-dsa-128s", "ml-kem-768"]
|
||||
for alg in pq_algs:
|
||||
r = test_verb(ser, "get_public_key", [{"algorithm": alg, "index": 0}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
pubkeys[alg] = r["result"].get("public_key", "")
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# 13. sign (ml-dsa-65)
|
||||
r = test_verb(ser, "sign", [msg_raw_hex, {"algorithm": "ml-dsa-65", "index": 0}])
|
||||
if r and "result" in r: passed += 1
|
||||
else: failed += 1
|
||||
|
||||
# 14. sign (slh-dsa-128s) — slow (~1-2s on Teensy)
|
||||
print("\n--- sign (slh-dsa-128s) — may take 1-2 seconds ---")
|
||||
r = test_verb(ser, "sign", [msg_raw_hex, {"algorithm": "slh-dsa-128s", "index": 0}])
|
||||
if r and "result" in r: passed += 1
|
||||
else: failed += 1
|
||||
|
||||
# 15. encapsulate + decapsulate (ML-KEM-768)
|
||||
if "ml-kem-768" in pubkeys:
|
||||
r = test_verb(ser, "encapsulate", [pubkeys["ml-kem-768"], {"algorithm": "ml-kem-768", "index": 0}])
|
||||
ct_kem = None
|
||||
@@ -263,35 +333,6 @@ def main():
|
||||
print(" ⏭️ SKIP: no ml-kem-768 pubkey")
|
||||
failed += 1
|
||||
|
||||
# 9. derive_shared_secret (X25519) — need a peer pubkey
|
||||
# Canonical format: params = [ "<peer_pub_hex>", { "algorithm": "x25519", "index": 0 } ]
|
||||
# The peer pubkey is 32 raw bytes -> 64 hex chars.
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
|
||||
peer_priv = X25519PrivateKey.generate()
|
||||
peer_pub = peer_priv.public_key()
|
||||
peer_pub_bytes = peer_pub.public_bytes_raw()
|
||||
peer_pub_hex = peer_pub_bytes.hex()
|
||||
|
||||
r = test_verb(ser, "derive_shared_secret", [peer_pub_hex, {"algorithm": "x25519", "index": 0}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
except ImportError:
|
||||
print("\n--- derive_shared_secret (x25519) ---")
|
||||
print(" ⏭️ SKIP: 'cryptography' module not installed")
|
||||
failed += 1
|
||||
|
||||
# 10. derive (secp256k1 at index 1)
|
||||
# Canonical format: params = [ "<data>", { "algorithm": "secp256k1", "index": 1 } ]
|
||||
# index is REQUIRED for derive (no default).
|
||||
r = test_verb(ser, "derive", ["derive-test-data", {"algorithm": "secp256k1", "index": 1}])
|
||||
if r and "result" in r:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RESULTS: {passed} passed, {failed} failed, {passed+failed} total")
|
||||
@@ -300,5 +341,6 @@ def main():
|
||||
ser.close()
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
+2
-2
@@ -762,8 +762,8 @@ int socket_name_random(char *out, size_t out_len);
|
||||
/* Version information (auto-updated by build/version tooling) */
|
||||
#define NSIGNER_VERSION_MAJOR 0
|
||||
#define NSIGNER_VERSION_MINOR 1
|
||||
#define NSIGNER_VERSION_PATCH 1
|
||||
#define NSIGNER_VERSION "v0.1.1"
|
||||
#define NSIGNER_VERSION_PATCH 2
|
||||
#define NSIGNER_VERSION "v0.1.2"
|
||||
|
||||
|
||||
/* NSIGNER_HEADERLESS_DECLS_END */
|
||||
|
||||
Reference in New Issue
Block a user