124 lines
3.6 KiB
Bash
Executable File
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 |