mdip-agent-kit
Version:
Give any AI agent its own MDIP (did:mdip) identity on the SELF / Keychain hyperswarm network: keymaster wallet, DID, SELF Public Profile molecule, backups, challenges and delegated-authority credentials.
70 lines (67 loc) • 4.46 kB
JavaScript
// Captures a REAL MDIP challenge -> response round trip as a fixture (vectors/mdip-challenge-1/).
// Creates two clearly-named throwaway identities in temp homes (no secrets are written to the repo),
// runs createChallenge / createResponse / verifyResponse through this kit, then exports the signed
// create operations from the gatekeeper and writes stock-tool encodings of each signature.
// Usage: MDIP_GATEKEEPER_URL=http://localhost:4224 node vectors/make-mdip-fixture.js
// NOTE: like `npm test`, this publishes throwaway DIDs to the configured registry.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import crypto from 'node:crypto';
import { fileURLToPath } from 'node:url';
import CipherNode from '@mdip/cipher/node';
import { openAgent, waitForDid } from '../src/index.js';
const out = path.join(path.dirname(fileURLToPath(import.meta.url)), 'mdip-challenge-1');
fs.rmSync(out, { recursive: true, force: true });
fs.mkdirSync(out, { recursive: true });
const cipher = new CipherNode();
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'kav-fixture-'));
const w = (f, data) => fs.writeFileSync(path.join(out, f), data);
const der = hex => {
const int = b => { let i = 0; while (i < b.length - 1 && b[i] === 0) i++; b = b.subarray(i); if (b[0] & 0x80) b = Buffer.concat([Buffer.from([0]), b]); return Buffer.concat([Buffer.from([0x02, b.length]), b]); };
const body = Buffer.concat([int(Buffer.from(hex.slice(0, 64), 'hex')), int(Buffer.from(hex.slice(64), 'hex'))]);
return Buffer.concat([Buffer.from([0x30, body.length]), body]);
};
try {
const verifier = await openAgent({ home: path.join(tmp, 'v'), name: 'mdip-agent-kit-kav-verifier', create: true });
const responder = await openAgent({ home: path.join(tmp, 'r'), name: 'mdip-agent-kit-kav-responder', create: true });
const challengeDid = await verifier.createChallenge({}); // no credentials requested
await waitForDid(verifier.keymaster, challengeDid);
const responseDid = await responder.respondToChallenge(challengeDid);
await waitForDid(verifier.keymaster, responseDid);
const result = await verifier.verifyResponse(responseDid);
if (!result.match) throw new Error('verifyResponse did not match');
const gk = verifier.keymaster.gatekeeper;
const names = { 'verifier-agent': verifier.did, 'responder-agent': responder.did, challenge: challengeDid, response: responseDid };
const exported = await gk.exportDIDs(Object.values(names));
const ops = {}; const dids = {}; const pubs = {};
Object.keys(names).forEach((k, i) => { ops[k] = exported[i][0].operation; dids[k] = names[k]; });
for (const k of ['verifier-agent', 'responder-agent']) {
pubs[names[k]] = crypto.createPublicKey({ key: { ...ops[k].publicJwk }, format: 'jwk' }).export({ type: 'spki', format: 'pem' });
w(`${k}.pub.pem`, pubs[names[k]]);
}
const sums = [];
for (const [k, op] of Object.entries(ops)) {
const copy = JSON.parse(JSON.stringify(op)); delete copy.signature;
const msg = cipher.canonicalizeJSON(copy);
const hash = cipher.hashMessage(msg);
if (hash !== op.signature.hash) throw new Error(`${k}: hash mismatch`);
// Agent create ops are self-signed (key in op.publicJwk); asset ops are signed by their controller agent.
const signerKey = op.mdip.type === 'agent' ? op.publicJwk : ops[op.controller === verifier.did ? 'verifier-agent' : 'responder-agent'].publicJwk;
if (!cipher.verifySig(hash, op.signature.value, signerKey)) throw new Error(`${k}: sig fails`);
w(`${k}.op.json`, JSON.stringify(op, null, 2) + '\n');
w(`${k}.message.bin`, msg);
w(`${k}.sig.der`, der(op.signature.value));
sums.push(`${k}.message.bin`, `${k}.sig.der`);
}
w('dids.json', JSON.stringify(dids, null, 2) + '\n');
// The decrypted response as seen by the verifier (contents are not secret; throwaway identities).
w('verify-response.json', JSON.stringify(result, null, 2) + '\n');
w('SHA256SUMS', sums.concat(['verifier-agent.pub.pem', 'responder-agent.pub.pem'])
.map(f => crypto.createHash('sha256').update(fs.readFileSync(path.join(out, f))).digest('hex') + ' ' + f).join('\n') + '\n');
console.log(JSON.stringify({ out, dids, match: result.match }, null, 2));
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
process.exit(0);