2025-12-14 22:23:33 -08:00
#!/usr/bin/env python3
"""
Example: Sign arbitrary data with a MeshCore device over BLE.
The device performs signing on its private key via the CMD_SIGN_* flow:
- sign_start(): initializes a signing session and returns max buffer size (8KB on firmware)
- sign_data(): streams one or more data chunks
- sign_finish(): returns the signature
"""
import argparse
import asyncio
from pathlib import Path
import sys
from textwrap import wrap
# Ensure local src/ is on path when running from repo root
repo_root = Path ( __file__ ) . resolve () . parents [ 1 ]
src_path = repo_root / "src"
if src_path . exists ():
sys . path . insert ( 0 , str ( src_path ))
from meshcore import MeshCore , EventType
async def main ():
parser = argparse . ArgumentParser (
description = "Sign data using a MeshCore device over BLE"
)
parser . add_argument (
"-a" ,
"--addr" ,
help = "BLE address of the device (optional, will scan if not provided)" ,
)
parser . add_argument (
"-p" ,
"--pin" ,
help = "PIN for BLE pairing (optional)" ,
)
parser . add_argument (
"-d" ,
"--data" ,
default = "Hello from meshcore_py!" ,
help = "ASCII data to sign (will be UTF-8 encoded)" ,
)
parser . add_argument (
"--chunk-size" ,
type = int ,
2025-12-15 21:29:12 -08:00
default = 120 ,
help = "Chunk size to stream to the device (bytes). Default 120 for BLE (frames under 128 bytes work better). For serial/TCP, larger values (e.g., 512) work fine." ,
2025-12-14 22:23:33 -08:00
)
2025-12-15 19:54:44 -08:00
parser . add_argument (
"--timeout" ,
type = float ,
default = None ,
help = "Timeout for sign_finish operation in seconds (default: 15s minimum, longer for large data like JWT tokens)" ,
)
2025-12-14 22:23:33 -08:00
parser . add_argument (
"--debug" ,
action = "store_true" ,
help = "Enable debug logging" ,
)
args = parser . parse_args ()
meshcore = None
try :
print ( "Connecting to MeshCore device..." )
meshcore = await MeshCore . create_ble ( address = args . addr , pin = args . pin , debug = args . debug )
print ( "✅ Connected." )
data_bytes = args . data . encode ( "utf-8" )
2025-12-15 21:29:12 -08:00
print ( f "Data to sign: { len ( data_bytes ) } bytes" )
if args . debug :
print ( f "Data hex (first 100 bytes): { data_bytes [: 100 ] . hex () } " )
2025-12-15 19:54:44 -08:00
sig_evt = await meshcore . commands . sign ( data_bytes , chunk_size = max ( 1 , args . chunk_size ), timeout = args . timeout )
2025-12-14 22:23:33 -08:00
if sig_evt . type == EventType . ERROR :
raise RuntimeError ( f "sign failed: { sig_evt . payload } " )
signature = sig_evt . payload . get ( "signature" , b "" )
print ( f "Signature ( { len ( signature ) } bytes):" )
# Pretty-print hex in 32-byte lines
hex_sig = signature . hex ()
for line in wrap ( hex_sig , 64 ):
print ( line )
2025-12-15 21:29:12 -08:00
# Verify signature with device's public key
try :
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature
# Get device's public key from self_info
self_info = meshcore . self_info
if not self_info or "public_key" not in self_info :
print ( " \n ⚠️ Could not get device public key for verification" )
else :
pubkey_hex = self_info [ "public_key" ]
pubkey_bytes = bytes . fromhex ( pubkey_hex )
try :
public_key = Ed25519PublicKey . from_public_bytes ( pubkey_bytes )
public_key . verify ( signature , data_bytes )
print ( " \n ✅ Signature verification: SUCCESS (signature is valid)" )
except InvalidSignature :
print ( " \n ❌ Signature verification: FAILED (signature is invalid)" )
if args . debug :
print ( f " Public key: { pubkey_hex } " )
print ( f " Data length: { len ( data_bytes ) } bytes" )
print ( f " Signature length: { len ( signature ) } bytes" )
print ( f " Data (first 50 bytes): { data_bytes [: 50 ] . hex () } " )
except Exception as e :
print ( f " \n ⚠️ Signature verification error: { e } " )
except ImportError :
print ( " \n ⚠️ cryptography library not available - skipping signature verification" )
print ( " Install with: pip install cryptography" )
2025-12-14 22:23:33 -08:00
print ( " \n Signing flow completed!" )
except ConnectionError as e :
print ( f "❌ Failed to connect: { e } " )
return 1
except Exception as e :
print ( f "❌ Error: { e } " )
return 1
finally :
if meshcore :
await meshcore . disconnect ()
print ( "Disconnected." )
if __name__ == "__main__" :
try :
sys . exit ( asyncio . run ( main ()))
except KeyboardInterrupt :
print ( " \n Interrupted by user" )
sys . exit ( 1 )