StatVault · Witness handshake
Sign our Merkle root in five minutes.
StatVault publishes a tamper-evident root over the entire evidence ledger every six hours. Any GitHub account can co-sign it with an ed25519 key from a free Actions runner. One external signer flips the trust model from self-anchored (we vouch for us) to federated (you watch us). No payment, no review, no gate.
Current federation
1
unique signer on file · 1 veteran · 0 trusted · 0 novice
Three steps
- Run
npx -y github:VaultSparkStudios/statvault scripts/onboard-witness.mjsinside any repo you control (or use the YAML below). The script generates an ed25519 keypair, stores the public side, and writes the workflow file. - Add the generated private key as a repo secret named
WITNESS_PRIVATE_KEY. Optionally set a human-readableWITNESS_LABELrepo variable. - Push and dispatch the workflow once. Within minutes your pubkey lands on /trust/merkle. You can stop at any time — receipts already co-signed stay verifiable forever.
Paste-ready workflow
Drop this at .github/workflows/statvault-witness.yml if you skip the script.
name: statvault-witness
on:
schedule:
- cron: "17 */6 * * *"
workflow_dispatch: {}
jobs:
witness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- name: Sign + post current Merkle root
env:
WITNESS_PRIVATE_KEY: ${{ secrets.WITNESS_PRIVATE_KEY }}
WITNESS_LABEL: ${{ vars.WITNESS_LABEL || github.repository_owner }}
run: |
curl -fsSL https://statvault.org/api/v1/trust/merkle > /tmp/root.json
node -e "
const { createSign, createPrivateKey } = require('node:crypto');
const fs = require('node:fs');
const root = JSON.parse(fs.readFileSync('/tmp/root.json', 'utf8')).root;
const key = createPrivateKey({ key: process.env.WITNESS_PRIVATE_KEY, format: 'pem' });
const sig = createSign('SHA512').update(root).sign(key).toString('base64');
const body = { merkleRoot: root, signature: sig, label: process.env.WITNESS_LABEL };
fetch('https://statvault.org/api/v1/trust/witness', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
}).then(r => r.text()).then(console.log);
"
The signature is over the raw Merkle root string. No personal data leaves your runner. You can revoke at any time by deleting the secret — past co-signatures remain valid because they're anchored to the root they signed.