baobab/tools/gen_auth.sh
Maxime Van Hees 0ebda7c1aa Updates
2025-08-14 14:14:34 +02:00

124 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage:
gen_auth.sh --nonce "<nonce_string>" [--priv <private_key_hex>] [--json]
Options:
--nonce The nonce string returned by fetch_nonce (paste as-is).
--priv Optional private key hex (64 hex chars). If omitted, a new key is generated.
--json Output JSON instead of plain KEY=VALUE lines.
Outputs:
PRIVATE_HEX Private key hex (only when generated, or echoed back if provided)
PUBLIC_HEX Compressed secp256k1 public key hex (33 bytes, 66 hex chars)
NONCE The nonce string you passed in
SIGNATURE_HEX Compact ECDSA signature hex (64 bytes, 128 hex chars)
Notes:
- The signature is produced by signing sha256(nonce_ascii) and encoded as compact r||s (64 bytes),
which matches the server/client behavior ([interfaces/openrpc/client/src/auth.rs](interfaces/openrpc/client/src/auth.rs:55), [interfaces/openrpc/server/src/auth.rs](interfaces/openrpc/server/src/auth.rs:85)).
USAGE
}
NONCE=""
PRIV_HEX=""
OUT_JSON=0
while [[ $# -gt 0 ]]; do
case "$1" in
--nonce)
NONCE="${2:-}"; shift 2 ;;
--priv)
PRIV_HEX="${2:-}"; shift 2 ;;
--json)
OUT_JSON=1; shift ;;
-h|--help)
usage; exit 0 ;;
*)
echo "Unknown arg: $1" >&2; usage; exit 1 ;;
esac
done
if [[ -z "$NONCE" ]]; then
echo "Error: --nonce is required" >&2
usage
exit 1
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "Error: python3 not found. Install Python 3 (e.g., sudo pacman -S python) and retry." >&2
exit 1
fi
# Ensure 'ecdsa' module is available; install to user site if missing.
if ! python3 - <<'PY' >/dev/null 2>&1
import importlib; importlib.import_module("ecdsa")
PY
then
echo "Installing Python 'ecdsa' package in user site..." >&2
if ! python3 -m pip install --user --quiet ecdsa; then
echo "Error: failed to install 'ecdsa'. Install manually: python3 -m pip install --user ecdsa" >&2
exit 1
fi
fi
# Now run Python to generate/derive keys and sign the nonce (ASCII) with compact ECDSA.
python3 - "$NONCE" "$PRIV_HEX" "$OUT_JSON" <<'PY'
import sys, json, hashlib
from ecdsa import SigningKey, VerifyingKey, SECP256k1, util
NONCE = sys.argv[1]
PRIV_HEX = sys.argv[2]
OUT_JSON = int(sys.argv[3]) == 1
def to_compact_signature(sk: SigningKey, msg_ascii: str) -> bytes:
digest = hashlib.sha256(msg_ascii.encode()).digest()
return sk.sign_digest(digest, sigencode=util.sigencode_string) # 64 bytes r||s
def compressed_pubkey(vk: VerifyingKey) -> bytes:
try:
return vk.to_string("compressed")
except TypeError:
p = vk.pubkey.point
x = p.x()
y = vk.pubkey.point.y()
prefix = b'\x02' if (y % 2 == 0) else b'\x03'
return prefix + x.to_bytes(32, "big")
generated = False
if PRIV_HEX:
if len(PRIV_HEX) != 64:
print("ERROR: Provided --priv must be 64 hex chars", file=sys.stderr)
sys.exit(1)
sk = SigningKey.from_string(bytes.fromhex(PRIV_HEX), curve=SECP256k1)
else:
sk = SigningKey.generate(curve=SECP256k1)
generated = True
vk = sk.get_verifying_key()
pub_hex = compressed_pubkey(vk).hex()
sig_hex = to_compact_signature(sk, NONCE).hex()
priv_hex = sk.to_string().hex()
out = {
"PUBLIC_HEX": pub_hex,
"NONCE": NONCE,
"SIGNATURE_HEX": sig_hex,
}
if generated or PRIV_HEX:
out["PRIVATE_HEX"] = priv_hex
if OUT_JSON:
print(json.dumps(out, separators=(",", ":")))
else:
if "PRIVATE_HEX" in out:
print(f"PRIVATE_HEX={out['PRIVATE_HEX']}")
print(f"PUBLIC_HEX={out['PUBLIC_HEX']}")
print(f"NONCE={out['NONCE']}")
print(f"SIGNATURE_HEX={out['SIGNATURE_HEX']}")
PY
# End