wk-recovery: set or clear the recovery key on our own names (signs in Actions)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UozKbNTNSfntAfsaaFuLuM
This commit is contained in:
1 parent
e6107d6946
commit
d8f7ad2920
1 file changed
+50
@@ -0,0 +1,50 @@
|
|||||||
|
name: wk-recovery
|
||||||
|
# Sets (or clears, with recovery=none) the recovery key on our own well-known names.
|
||||||
|
# Signs with the name's main key inside Actions: factory = SNAIL_KNOWN, well-known = WK_NSEC. Keys never leave the runner.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
key: { description: "which main key signs: factory or well-known", required: true, default: factory }
|
||||||
|
names: { description: comma-separated names, required: true }
|
||||||
|
recovery: { description: "recovery pubkey (64 hex) or none", required: true }
|
||||||
|
jobs:
|
||||||
|
set:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: run
|
||||||
|
env:
|
||||||
|
K_FACTORY: ${{ secrets.SNAIL_KNOWN }}
|
||||||
|
K_WK: ${{ secrets.WK_NSEC }}
|
||||||
|
WHICH: ${{ inputs.key }}
|
||||||
|
NAMES: ${{ inputs.names }}
|
||||||
|
REC: ${{ inputs.recovery }}
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/rk && cd /tmp/rk
|
||||||
|
npm init -y >/dev/null && npm i --silent @noble/curves@1.9.7 >/dev/null
|
||||||
|
cat > t.mjs <<'JS'
|
||||||
|
import { schnorr } from '@noble/curves/secp256k1';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
const CH = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
||||||
|
const toHex = (s) => { s = s.trim().toLowerCase(); if (/^[0-9a-f]{64}$/.test(s)) return s;
|
||||||
|
const d = [...s.slice(s.lastIndexOf('1') + 1)].map((c) => CH.indexOf(c)).slice(0, -6); let a = 0, b = 0; const o = [];
|
||||||
|
for (const v of d) { a = (a << 5) | v; b += 5; while (b >= 8) { b -= 8; o.push((a >> b) & 255); } } return Buffer.from(o).toString('hex'); };
|
||||||
|
const raw = process.env.WHICH === 'well-known' ? process.env.K_WK : process.env.WHICH === 'factory' ? process.env.K_FACTORY : '';
|
||||||
|
if (!raw) { console.log('key must be factory or well-known'); process.exit(2); }
|
||||||
|
const rec = process.env.REC.trim().toLowerCase();
|
||||||
|
if (rec !== 'none' && !/^[0-9a-f]{64}$/.test(rec)) { console.log('recovery must be 64 hex or none'); process.exit(2); }
|
||||||
|
const sk = toHex(raw), pk = Buffer.from(schnorr.getPublicKey(sk)).toString('hex');
|
||||||
|
let fails = 0, n = 0;
|
||||||
|
for (const name of process.env.NAMES.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)) {
|
||||||
|
const ev = { pubkey: pk, created_at: Math.floor(Date.now() / 1000), kind: 27235, content: String(n++),
|
||||||
|
tags: [['u', 'https://well-known.online/recovery'], ['method', 'POST'], ['name', name], ...(rec === 'none' ? [] : [['p', rec]])] };
|
||||||
|
ev.id = createHash('sha256').update(JSON.stringify([0, ev.pubkey, ev.created_at, ev.kind, ev.tags, ev.content])).digest('hex');
|
||||||
|
ev.sig = Buffer.from(schnorr.sign(ev.id, sk)).toString('hex');
|
||||||
|
const r = await fetch('https://well-known.online/recovery', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ event: ev }) });
|
||||||
|
const j = await r.json().catch(() => ({}));
|
||||||
|
const ok = r.status === 200 && (rec === 'none' ? j.recovery === null : j.recovery === rec);
|
||||||
|
console.log(`${ok ? 'PASS' : 'FAIL'} ${name} ${r.status} ${JSON.stringify(j)}`);
|
||||||
|
if (!ok) fails++;
|
||||||
|
}
|
||||||
|
process.exit(fails ? 1 : 0);
|
||||||
|
JS
|
||||||
|
node t.mjs
|
||||||
Reference in new issue
Block a user