{"_id":"mdip-agent-kit","_rev":"7-7151744d41f8a90795e8a036202fa49f","name":"mdip-agent-kit","dist-tags":{"latest":"0.1.5"},"versions":{"0.0.0-stage":{"name":"mdip-agent-kit","version":"0.0.0-stage","_id":"mdip-agent-kit@0.0.0-stage","stub":true,"maintainers":[{"name":"trill216","email":"will@selfid.com"}],"dist":{"shasum":"83d317380a28f37c8a26f29730fa6a43483afe32","tarball":"https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-0.0.0-stage.tgz","fileCount":2,"integrity":"sha512-nqUqrQZ4X4bXh4+HU9jT32sgCL8AZZn4+n3Gi38qVp7N6Hz8IN95bWi1Th/4dXZznZUcat/t8RmYnJ6PVE6Jnw==","signatures":[{"sig":"MEUCIFwplJ3ZR0j/LNQgdx5gZdp+DUZ8ZkO/WoqZ2xkzBTWzAiEA+ppbRoveZgDkwXLXZeL5dbn4Q6n1S1CJ8/r42NCYh9o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":336},"_npmUser":{"name":"trill216","email":"will@selfid.com"},"description":"Temporary package placeholder for staged publishing","directories":{}},"0.1.0":{"name":"mdip-agent-kit","version":"0.1.0","keywords":["mdip","did","did:mdip","self","selfid","keychain","verifiable-credentials","ai-agent","agent-identity","hyperswarm"],"license":"MIT","_id":"mdip-agent-kit@0.1.0","maintainers":[{"name":"trill216","email":"will@selfid.com"}],"bin":{"mdip-agent":"bin/mdip-agent.js"},"dist":{"shasum":"accc7ce473df66ee7a1adc6b1933faef5727bc35","tarball":"https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-0.1.0.tgz","fileCount":10,"integrity":"sha512-bIPNSR3G66q5yJOSul2DdmL/+jYFp+Bamcy0dOg0U6x/dSZRrTpY4znrdrDMvWoqTX76jpyY1/wnOTVuq/Gi8Q==","signatures":[{"sig":"MEQCIHoUPUoV8bCom0q9NuqagOTu0oZH1fY3Nku7vT3dlfbxAiBCZ7+X6dUPvJeIRE5Mpcl7V0gKXVEmUvhQrQttwWUY2w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQC5NYDYOnL3yuZh2J5FFaEDtM/vo9tTdNVGZiq04xoscAIgUC1RdC68ckoLTX4eBKoPrvx0v0XwzRBOf58WbP6+21o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":43243},"main":"src/index.js","type":"module","engines":{"node":">=20"},"exports":{".":"./src/index.js"},"gitHead":"96576525dfb97099df206b98ab98cb99ed378e47","scripts":{"test":"bash test/e2e.sh"},"_npmUser":{"name":"trill216","email":"will@selfid.com"},"_npmVersion":"11.21.0","description":"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.","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@mdip/cipher":"2.2.0","@mdip/keymaster":"2.2.0","@mdip/gatekeeper":"2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mdip-agent-kit_0.1.0_1792701159605_0.7623812782988482","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"mdip-agent-kit","version":"0.1.1","keywords":["mdip","did","did:mdip","self","selfid","keychain","verifiable-credentials","ai-agent","agent-identity","hyperswarm"],"license":"MIT","_id":"mdip-agent-kit@0.1.1","maintainers":[{"name":"trill216","email":"will@selfid.com"}],"bin":{"mdip-agent":"bin/mdip-agent.js"},"dist":{"shasum":"5c5d85dbbe4fefa17407a302c96b19feef9240ee","tarball":"https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-0.1.1.tgz","fileCount":10,"integrity":"sha512-+xZaaeFtXJg1YrxCOze4Phkdf86RBBEm9A0+zsTGythH0CC41Fnlxxa5rWOvlwlTmdwhhs2GeIq/s5XDsPOhJA==","signatures":[{"sig":"MEYCIQDjK3v2WorVf8OK4mmFJk8JIiVZlBRcCusH7WT+6qOa3AIhAIYRykMXZntS5OPfoNYk/uKmpEd3vPOjmuYD+xowsGq6","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCICVN/ADleBBhWfiCybbeZ1k+VmeVeBrFvE4vGcuKnXKjAiEA0rDas/p1l0iNn0yK6NF4SFozy/lWqkM3LoBHzp9fLTA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":45266},"main":"src/index.js","type":"module","engines":{"node":">=22"},"exports":{".":"./src/index.js"},"gitHead":"491d9b84f7a6db13b4e7a25b65ce0716af59f536","scripts":{"test":"bash test/e2e.sh"},"_npmUser":{"name":"trill216","email":"will@selfid.com"},"_npmVersion":"11.21.0","description":"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.","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@mdip/cipher":"2.2.0","@mdip/keymaster":"2.2.0","@mdip/gatekeeper":"2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mdip-agent-kit_0.1.1_1792704426913_0.9780868767270232","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"mdip-agent-kit","version":"0.1.2","keywords":["mdip","did","did:mdip","self","selfid","keychain","verifiable-credentials","ai-agent","agent-identity","hyperswarm"],"license":"MIT","_id":"mdip-agent-kit@0.1.2","maintainers":[{"name":"trill216","email":"will@selfid.com"}],"bin":{"mdip-agent":"bin/mdip-agent.js"},"dist":{"shasum":"6f761f1bec1b5210f6d6cd86349927e389eb2737","tarball":"https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-0.1.2.tgz","fileCount":42,"integrity":"sha512-piSHqMQpBXk29uuNYQyY/MYbmfJHnc9rbbZRp1tknvsWpfkDPTU2HE1mQIhg/3doBgIrBq1oSLrI+uEgtAPGjw==","signatures":[{"sig":"MEYCIQC+kWv+LXo3oVfJis5cDCVVHcwtSGBb4CsdZUtyMuMhLwIhAKWaFh0LnqBMH/3pK690cNNCH+66bY9WlhXyDX1x/y0V","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDdRyWeJqC1uvF6Jt/34d6egFwgEKNAOBHxmCCxFtyZJwIgWbel/FNShDUCC78poBKO5yd882Iu8jkoGo8msrzlIHE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":75556},"main":"src/index.js","type":"module","engines":{"node":">=22"},"exports":{".":"./src/index.js"},"gitHead":"019294135ef260614e2aadf2a7befedeca7fd24d","scripts":{"test":"bash test/e2e.sh","test:vectors":"bash vectors/verify-openssl.sh && node vectors/verify-with-kit.js"},"_npmUser":{"name":"trill216","email":"will@selfid.com"},"_npmVersion":"11.21.1","description":"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.","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@mdip/cipher":"2.2.0","@mdip/keymaster":"2.2.0","@mdip/gatekeeper":"2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mdip-agent-kit_0.1.2_1793028180481_0.4311638854224482","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"mdip-agent-kit","version":"0.1.3","keywords":["mdip","did","did:mdip","self","selfid","keychain","verifiable-credentials","ai-agent","agent-identity","hyperswarm"],"license":"MIT","_id":"mdip-agent-kit@0.1.3","maintainers":[{"name":"trill216","email":"will@selfid.com"}],"bin":{"mdip-agent":"bin/mdip-agent.js"},"dist":{"shasum":"c400485cebc90318e94d163eb46f1d2bb62ca1c7","tarball":"https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-0.1.3.tgz","fileCount":70,"integrity":"sha512-5r4dN/IymizCARyFYYodyIXfke85BE12wIBEH0DUy3xtHVoUWMqSjbCqKT8YJ833cu0DwU5SlPTkpPC+wcCLxg==","signatures":[{"sig":"MEUCIHF17m/DTLfWQx4upC4mtkHwxS7/gb2cTSuGU+GbdnWjAiEAuAv95AaloKUNAhB5ObBOv/eivO3nvdyUETMl+FkfoAM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIDl8l675nd6nNw30ch8OXiES8Emq5din5a3ITRzi74ufAiEAnzZNc2rfnyG/AEdQGoQuU3yZd8nLD9NMVbWovEdMEEs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":111762},"main":"src/index.js","type":"module","engines":{"node":">=22"},"exports":{".":"./src/index.js"},"gitHead":"0fcd65d4575a24f563767fa4916c798aa8d97334","scripts":{"test":"bash test/e2e.sh","test:vectors":"bash vectors/verify-openssl.sh && node vectors/verify-with-kit.js"},"_npmUser":{"name":"trill216","email":"will@selfid.com"},"_npmVersion":"11.21.1","description":"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.","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@mdip/cipher":"2.2.0","@mdip/keymaster":"2.2.0","@mdip/gatekeeper":"2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mdip-agent-kit_0.1.3_1793033708093_0.6555758228775113","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"mdip-agent-kit","version":"0.1.4","keywords":["mdip","did","did:mdip","self","selfid","keychain","verifiable-credentials","ai-agent","agent-identity","hyperswarm"],"license":"MIT","_id":"mdip-agent-kit@0.1.4","maintainers":[{"name":"trill216","email":"will@selfid.com"}],"homepage":"https://selfid.com","bin":{"mdip-agent":"bin/mdip-agent.js"},"dist":{"shasum":"ded308bcd0db7253500cd21ee638c115ae09a42b","tarball":"https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-0.1.4.tgz","fileCount":70,"integrity":"sha512-bIB4SZISqY8ckXFnmQBrQr47EktBqffxNlZ57ANU7cIZwHDv3ioLy8jgdhQ3NJDwGWBpRRYSh0f//lUOPcGhDg==","signatures":[{"sig":"MEUCIGcJwZjeA0oAyv2uFTSRdGb5z46z2djkZ8kfyI6JVcQGAiEA2RsSY2GLRiSKYj3WhDojET4ZUo0izwwDfziFkUl55Gs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIH12y61D0nDn2V6QpTysHFVJyw8TDOTJxZzxKTV5ltMzAiEAqEDTDv47ivLGRbEy0J8NTFBtjOfFijAwZ+ptUNBA8LY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":113497},"main":"src/index.js","type":"module","engines":{"node":">=22"},"exports":{".":"./src/index.js"},"gitHead":"f2ffeb255a62f94eb5466a936365e6b79ad7700e","scripts":{"test":"bash test/e2e.sh","test:vectors":"bash vectors/verify-openssl.sh && node vectors/verify-with-kit.js"},"_npmUser":{"name":"trill216","email":"will@selfid.com"},"_npmVersion":"11.21.1","description":"Give any AI agent its own MDIP (did:mdip) identity on the SELF (https://selfid.com) / Keychain hyperswarm network: keymaster wallet, DID, SELF Public Profile molecule, backups, challenges and delegated-authority credentials.","directories":{},"_nodeVersion":"20.19.2","dependencies":{"@mdip/cipher":"2.2.0","@mdip/keymaster":"2.2.0","@mdip/gatekeeper":"2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mdip-agent-kit_0.1.4_1793045921948_0.2594023159191994","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"_id":"mdip-agent-kit@0.1.5","bin":{"mdip-agent":"bin/mdip-agent.js"},"dist":{"shasum":"8095b054b0efba27f427121b3369efa59b85e029","tarball":"https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-0.1.5.tgz","fileCount":78,"integrity":"sha512-argJIuYR8YAdkK9HLqWmwwMeZcTMT/9sKXQVjIs4/XcBzSK1elnKOLLyPPY6Cp1zBDIvfpxUypZ+LfCPJUf/yA==","signatures":[{"sig":"MEUCIGv1r1wLMheW94p6r06OuAjeIH3VA1ZVBeB3vCI1PjjiAiEAmFpGuGSP1pZadkJb+5dETB+tT2IAUNqdRCDNLqAE5mk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDJYTzk9aUxvq1fhiKA6+f8aGqoxZtnlt/MyLIMrsNxfwIhAOJNiIv1dlMUn3mYLV+GuzjaeeyF0MCbQvci15Wl7m/u"}],"unpackedSize":243704},"main":"src/index.js","name":"mdip-agent-kit","type":"module","engines":{"node":">=22"},"exports":{".":"./src/index.js"},"gitHead":"e35bea8d4f0df9db8ffe0220d8d55532517bee6f","license":"MIT","scripts":{"test":"bash test/e2e.sh","test:vectors":"bash vectors/verify-openssl.sh && node vectors/verify-with-kit.js && bash test/vectors-negative.sh"},"version":"0.1.5","_npmUser":{"name":"trill216","email":"will@selfid.com"},"homepage":"https://selfid.com","keywords":["mdip","did","did:mdip","self","selfid","keychain","verifiable-credentials","ai-agent","agent-identity","hyperswarm"],"_npmVersion":"11.21.1","description":"Give any AI agent its own MDIP (did:mdip) identity on the SELF (https://selfid.com) / Keychain hyperswarm network: keymaster wallet, DID, SELF Public Profile molecule, backups, challenges and delegated-authority credentials.","directories":{},"maintainers":[{"name":"trill216","email":"will@selfid.com"}],"_nodeVersion":"20.19.2","dependencies":{"@mdip/cipher":"2.2.0","@mdip/keymaster":"2.2.0","@mdip/gatekeeper":"2.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mdip-agent-kit_0.1.5_1793118312620_0.8963790221700495"}}},"time":{"created":"2026-10-22T20:30:01.887Z","modified":"2026-10-27T16:25:12.928Z","0.0.0-stage":"2026-10-22T20:30:01.887Z","0.1.0":"2026-10-22T20:32:39.690Z","0.1.1":"2026-10-22T21:27:07.005Z","0.1.2":"2026-10-26T15:23:00.681Z","0.1.3":"2026-10-26T16:55:08.171Z","0.1.4":"2026-10-26T20:18:42.047Z","0.1.5":"2026-10-27T16:25:12.745Z"},"license":"MIT","homepage":"https://selfid.com","keywords":["mdip","did","did:mdip","self","selfid","keychain","verifiable-credentials","ai-agent","agent-identity","hyperswarm"],"description":"Give any AI agent its own MDIP (did:mdip) identity on the SELF (https://selfid.com) / Keychain hyperswarm network: keymaster wallet, DID, SELF Public Profile molecule, backups, challenges and delegated-authority credentials.","maintainers":[{"name":"trill216","email":"will@selfid.com"}],"readme":"# mdip-agent-kit\n\nGive your AI agent its own **decentralized identity** (`did:mdip`) on the public\n[Keychain MDIP](https://keychain.org) hyperswarm network, the network used by the\n[SELF®](https://selfid.com) identity app. One command gets the agent:\n\n- its own **Keymaster wallet** (encrypted, local) and **agent DID**\n- SELF's official **Public Profile molecule**, self-attested and **published + revealed** on its DID manifest\n- **two backups**: a network backup recoverable from a 12-word recovery phrase, plus a local encrypted file\n- an automatic **restore test** of both backups, run without touching the live wallet\n- a small library/CLI to **sign/verify**, **issue and answer challenges**, and **accept credentials**\n  such as a purchase authorization issued from a human's SELF app\n\n> Install: `npm install mdip-agent-kit` · run: `npx mdip-agent-kit <command>`\n>\n> Status: early. Changes per version are in [`CHANGELOG.md`](CHANGELOG.md). Not affiliated with SELF or\n> Keychain. License MIT (see `LICENSE`).\n\n## Why\n\nAgents act for people. An agent DID gives every action a verifiable author. A **delegated-authority\ncredential** issued by a human's DID (\"this agent may buy things for me, up to $X, until Y\") lets\nanyone check that authority cryptographically, with no shared passwords and no API keys handed over.\nChallenge/response lets agents check each other before they interact. *Every interaction is an\ninvite*: each time you challenge another agent, it is pushed to get a DID too.\n\n## Requirements\n\n- Node.js ≥ 22\n- A **Gatekeeper** URL (the MDIP node your keymaster talks to). See [Choosing a gatekeeper](#choosing-a-gatekeeper).\n\n## Quickstart\n\n```bash\n# 1. a gatekeeper: run your own MDIP node (Docker). The script ships in the package:\nnpm install mdip-agent-kit\nbash node_modules/mdip-agent-kit/scripts/run-local-node.sh   # -> http://127.0.0.1:4224, syncs with the public network\nexport MDIP_GATEKEEPER_URL=http://127.0.0.1:4224\n\n# 2. identity + public profile + backups + restore test\nexport MDIP_HOME=~/.mdip-agent/my-agent   # one dir per agent, never shared\nexport MDIP_AGENT_NAME=my-agent\nnpx mdip-agent-kit init --profile-name \"My Agent\"\n\n# 3. confirm it reached the public network (usually < 2 min)\nnpx mdip-agent-kit whoami                  # includes explorer visibility\ncurl -s https://explorer.selfid.com/api/did/<your did>\n```\n\nOnce installed locally (`npm install mdip-agent-kit`), the binary is also available as `npx mdip-agent ...`.\n\n`init` is idempotent: re-running it reuses the wallet, DID, and profile.\n\n## CLI\n\nRun as `npx mdip-agent-kit <command>` (or `npx mdip-agent <command>` once installed locally).\n\n```\nmdip-agent init [--profile-name \"Name\"] [--no-backup]\nmdip-agent whoami | profile \"<name>\" | backup | verify-backups | explorer [did]\nmdip-agent resolve <did>\nmdip-agent sign <file|-> | verify <file|->\nmdip-agent challenge [--schema DID]... [--issuer DID]... [--callback URL]\nmdip-agent respond <challengeDid> [--valid-days N]\nmdip-agent verify-response <responseDid> --challenge <challengeDid>\nmdip-agent accept <credentialDid> | credentials | credential <did>\n```\nResponses stay valid 7 days by default (`--valid-days N` to change; always capped at the\nchallenge's own expiry). Keymaster's built-in default is only 1 hour. Responders: prefer capping at the\nchallenge's `validUntil`; verifiers accept stock-keymaster skew (response expiring up to 2 h after the\nchallenge) as long as both are still unexpired. Always pass `--challenge` to\n`verify-response` with the challenge you sent; without it a response to any earlier challenge of yours\n(same requirements) is accepted, and the CLI warns.\n\nGlobal flags: `--home DIR`, `--gatekeeper URL`, `--name ID`, `-h`/`--help`. Unknown options are errors.\nExit status: 0 ok, 1 error or failed verification, 2 a backup\ndid not restore.\n\n## Library\n\n```js\nimport { openAgent, SELF_PUBLIC_PROFILE_SCHEMA } from 'mdip-agent-kit';\n\nconst agent = await openAgent();                 // or openAgent({ home, gatekeeperUrl, name, create: true })\nagent.did;                                        // did:mdip:...\nconst signed = await agent.sign({ order: 42 });   // JSON + signature block\nawait agent.verify(signed);                       // true: SOME DID signed it, see signed.signature.signer\n\n// challenge another party (optionally require credentials)\nconst ch = await agent.createChallenge({ credentials: [{ schema: SELF_PUBLIC_PROFILE_SCHEMA }] });\nconst resp = await other.respondToChallenge(ch);  // done by the other agent\nconst r = await agent.verifyResponse(resp, { challenge: ch });  // always bind to the challenge you sent\nr.match;                                          // true; else r.checks lists why\n\n// delegated authority from a human (PURCHASE_SCHEMA / HUMAN_DID: the schema and issuer DIDs you trust)\nawait agent.acceptCredential(credentialDidFromSelfApp);\nconst grants = await agent.findCredentials({ schema: PURCHASE_SCHEMA, issuer: HUMAN_DID });\n\nawait agent.backup();                             // network + local file\nawait agent.verifyBackups();                      // { pass, results }\nagent.keymaster;                                  // full @mdip/keymaster API for everything else\n```\nSee `examples/agent-handshake.js` and `examples/purchase-authorization.js`.\n\n## Configuration\n\n| env | default | meaning |\n| --- | --- | --- |\n| `MDIP_HOME` | `~/.mdip-agent` | wallet, secrets, backups, state. **One per agent.** |\n| `MDIP_GATEKEEPER_URL` | `http://localhost:4224` | Gatekeeper REST endpoint |\n| `MDIP_AGENT_NAME` | `agent` | ID name inside the wallet (not public) |\n| `MDIP_REGISTRY` | `hyperswarm` | registry for new DIDs (what SELF reads) |\n| `MDIP_DID_PREFIX` | `did:mdip` | DID prefix used on the SELF network. The Keychain repo's sample config uses `did:test`; don't use that if you want SELF to see you |\n\nLayout of `MDIP_HOME` (dir mode 700, files 600):\n```\nwallet.json                  encrypted keymaster wallet\nsecrets/passphrase           wallet encryption passphrase (random, generated)\nsecrets/recovery-phrase.txt  12-word BIP39 recovery phrase\nbackups/wallet-YYYY-MM-DD.json  encrypted wallet export (needs the passphrase)\nstate.json                   non-secret: DID, profile credential DID, backup DID\n```\n\n## Choosing a gatekeeper\n\nThe keymaster (your wallet) lives in your process. It only needs a Gatekeeper to read and write DID\noperations. Options:\n\n1. **Your own node (recommended).** `scripts/run-local-node.sh` runs the official\n   [KeychainMDIP/kc](https://github.com/KeychainMDIP/kc) Docker Compose stack (gatekeeper +\n   hyperswarm mediator) with `did:mdip` / `hyperswarm`, bound to localhost. It peers with the public\n   network (observed peers include SELF's nodes). The first sync pulls ~150k operations\n   (as of Oct 2026). Several agents on one machine can share one gatekeeper; each still gets its\n   own `MDIP_HOME` wallet.\n2. **Another MDIP gatekeeper that you operate or that explicitly allows you to write.** There is no\n   public write gatekeeper offered by this kit; don't point agents at someone else's production\n   gatekeeper without permission.\n\nTo check publication, use SELF's public explorer API (`https://explorer.selfid.com/api/did/<did>`),\nnot a gatekeeper.\n\nYour private keys never leave `MDIP_HOME`, whichever gatekeeper you use. A gatekeeper only sees\nsigned public operations.\n\n## Verifying on the public network\n\n```bash\ncurl -s https://explorer.selfid.com/api/did/<did>       # agent DID: manifest shows the profile\nnpx mdip-agent-kit explorer <did>                           # same, summarized\n```\nA freshly created DID is normally visible within 1–2 minutes. If the node had no peers at that\nmoment, the operation goes out later via the mediator's periodic reconciliation, once the node is\nfully synced. That can take a while on a brand-new node.\n\n## Known-answer vectors (verify with stock OpenSSL, no install)\n\nFixed bytes so another agent can check this kit's signature crypto **without running any of its\ncode**. You need `bash` (3.2+ is fine), `curl`, `tar`, **OpenSSL ≥ 3.0** with secp256k1 (stock builds\nhave it) and the base tools every macOS/Linux has (`sed`, `awk`, `grep`, `od`, `tr`, `cut`, `cmp`, `dd`,\n`head`, `mktemp`). You don't need python3, jq, GNU `date` or `sha256sum`; if jq is present, it is used for\nan encoder-independent JSON parse. macOS ships LibreSSL as `openssl`, which lacks `openssl mac`, so use\nHomebrew's: `export OPENSSL=$(brew --prefix openssl@3)/bin/openssl`. Both scripts honour `$OPENSSL` and\nrefuse anything that isn't OpenSSL 3+. The one-line `openssl` commands below call plain `openssl`; on macOS\nrun them with Homebrew's on your `PATH` (`export PATH=\"$(brew --prefix openssl@3)/bin:$PATH\"`).\n\n**What MDIP actually signs** (from `@mdip/cipher` / `@mdip/keymaster` 2.2.0, used by `sign`,\n`verify` and every DID operation): `hash = sha256(JCS(obj without \"signature\"))` (RFC 8785 JSON\ncanonicalization, UTF-8). `signature.value` = secp256k1 ECDSA over that 32-byte hash (RFC 6979,\nlow-S), stored as 64-byte compact `r||s` hex. MDIP does **not** use Ed25519. For stock tools,\neach vector ships the exact canonical bytes (`*.message.bin`), the signature re-encoded as DER\n(`*.sig.der`) and the public key as SPKI PEM.\n\nThree things trip honest re-checks. **Compare by verifying, not by bytes:** OpenSSL's `dgst -sign`\nuses a random nonce, so re-signing `message.bin` with the test key gives a different (equally valid)\nsignature; only the kit's RFC 6979 signer reproduces `signature.hex` exactly. **Encodings differ:** DER\nis variable length (70 to 72 bytes; kav-1's is 70), compact `r||s` is always 64, so compact-only verifiers\nneed a transcode. **Read the exit code:** on a failure OpenSSL 3.x prints a `provider signature failure`\nerror line before `Verification failure`, so a checker that reads only the last line or only stderr can\ndisagree; exit 0 is the verdict.\n\n```bash\n# fetch the published files: a plain tarball download, nothing is installed or executed.\n# Check the tarball against the registry's sha512 integrity BEFORE unpacking (don't pipe curl into tar):\nV=0.1.5\ncurl -fsSLo kit.tgz https://registry.npmjs.org/mdip-agent-kit/-/mdip-agent-kit-$V.tgz\nI=$(curl -fsS https://registry.npmjs.org/mdip-agent-kit/$V | grep -o '\"integrity\":\"sha512-[^\"]*\"' | cut -d'\"' -f4)\nH=\"sha512-$(openssl dgst -sha512 -binary kit.tgz | openssl base64 -A)\"\nif [ -n \"$I\" ] && [ \"$I\" = \"$H\" ]; then echo \"tarball OK\"; tar xzf kit.tgz && cd package/vectors; else echo \"MISMATCH, not unpacked: registry=$I file=$H\"; fi\n#   (same value: `npm view mdip-agent-kit@$V dist.integrity`. npm versions are immutable, so this pins the\n#   bytes; it does not say who published them.)\n# each dir's SHA256SUMS lists every file in it (verify-openssl.sh checks that with openssl alone and also\n# rejects unlisted files). These sums catch corruption, not forgery: anyone who edits a file can rewrite\n# them. The registry integrity above anchors the bytes; the checkers also pin the published DIDs and the\n# kav-1 known answers (table below), so a wholesale regenerated vector set is caught too.\nfor d in kav-1 mdip-challenge-1 mdip-challenge-2; do (cd $d && shasum -a 256 -c SHA256SUMS); done   # or: sha256sum -c\n\n# A. kav-1: synthetic vector, PUBLIC TEST KEY (private part published in test-key.json on purpose)\nopenssl dgst -sha256 -r kav-1/message.bin\n#   4d4523d99bebdd70689f67a80e865349848f520d19ab9537403e56a280dcf64b *kav-1/message.bin   (== kav-1/message.sha256)\nopenssl dgst -sha256 -verify kav-1/pub.pem -signature kav-1/signature.der kav-1/message.bin\n#   Verified OK                    (exit 0)\nopenssl dgst -sha256 -verify kav-1/pub.pem -signature kav-1/signature.der kav-1/message-tampered.bin\n#   Verification failure           (exit 1, one byte changed)\n# same check on the raw 32-byte hash (what MDIP actually signs): pkeyutl verifies a precomputed digest\nopenssl dgst -sha256 -binary kav-1/message.bin > /tmp/h.bin\nopenssl pkeyutl -verify -pubin -inkey kav-1/pub.pem -in /tmp/h.bin -sigfile kav-1/signature.der\n#   Signature Verified Successfully (exit 0)\n\n# B. mdip-challenge-1: a REAL challenge -> response captured from this kit (throwaway identities)\nfor p in verifier-agent:verifier-agent challenge:verifier-agent responder-agent:responder-agent response:responder-agent; do\n  op=${p%%:*} key=${p##*:}\n  openssl dgst -sha256 -verify mdip-challenge-1/$key.pub.pem -signature mdip-challenge-1/$op.sig.der mdip-challenge-1/$op.message.bin\ndone\n#   Verified OK  x4                (exit 0 each)\n#   verify-openssl.sh also checks each DID == did:mdip:z + base58btc(01 8004 1220 || sha256(<name>.signed-op.bin))\n#   and that <name>.signed-op.bin is <name>.message.bin plus the signature in <name>.sig.der\n\n# C. mdip-challenge-2 (new in 0.1.3, thanks @homies): offline challenge <-> response BINDING.\n#    The verifier is a PUBLIC TEST identity, so anyone can decrypt the response it received.\nd=mdip-challenge-2\nopenssl pkey -in $d/verifier-agent.PUBLIC-TEST-KEY.priv.pem -pubout | cmp - $d/verifier-agent.pub.pem   # same key\nbash decrypt-openssl.sh $d/verifier-agent.PUBLIC-TEST-KEY.priv.pem $d/responder-agent.pub.pem $d/response.cipher_receiver.bin\n#   {\"response\":{\"challenge\":\"did:mdip:...\",\"credentials\":[],...,\"match\":true,\"responseNonce\":\"...\"}}\n#   the \"challenge\" value must equal dids.json \"challenge\"; verify-openssl.sh checks that, plus:\n#   - response.cipher_receiver.bin is exactly cipher_receiver inside the SIGNED response op\n#   - every DID == did:mdip:z + base58btc(01 8004 1220 || sha256(<name>.signed-op.bin))  (DID is the op hash;\n#     B and C both; leading base58 '1's are 0x00 bytes, so a padded DID string is rejected)\n#   - every JSON copy is tied to signed bytes: <name>.op.json == <name>.signed-op.bin (keys in JCS\n#     order), dids.json has exactly the four names, verify-response.json / test-verifier.json /\n#     kav-1 payload.json, signed.json and test-key.json are pinned field by field\n#   - each signature block is exactly {hash, signed, [signer,] value}, with created <= signed <= created + 60 s\n#   - each <name>.signed-op.bin is exactly <name>.message.bin plus its signature, and that signature\n#     is <name>.sig.der (so the DID-bearing bytes are the ones OpenSSL verified)\n#   - each *.pub.pem is the publicJwk inside its signed agent op; signer == controller on asset ops\n#   - challenge controller == verifier DID; response controller == encrypted.sender == responder DID\n#   - a flipped ciphertext byte fails the Poly1305 tag\n\n# D. expiry: response.validUntil <= challenge.validUntil + 2 h (stock keymaster skew); 2 h + 1 ns is rejected.\ncat expiry-cases.txt          # the table; verify-openssl.sh and verify-with-kit.js both run it\n\n# or all of the above in one go (bash + OpenSSL 3 only):\nbash verify-openssl.sh             # last line: ALL VECTORS OK, exit 0\n```\n\n| File | Value |\n|---|---|\n| `kav-1/message.bin` (240 bytes, JCS of `payload.json`) | sha256 `4d4523d99bebdd70689f67a80e865349848f520d19ab9537403e56a280dcf64b` |\n| `kav-1/signature.hex` (compact r‖s) | `2b61bc63136caf5dbbdfee80d6717d57df09555be0f6c1b0158adc70feb0363c` `2067a7017b2b52fd92fd0e980650118bf5d4e07bc88ee8527f04f6404a37b4d1` |\n| `kav-1/signature.der` | sha256 `fdc8e19ec10ced978a9dbd5ae599c35fa715d561dd1635bb5cf0360e3acd4508` |\n| `kav-1/pub.pem` | sha256 `43d15804c3379b189d1d1fbfb391d3c42abf739587297bdb9987eab382bcd131` |\n| `kav-1/test-key.json` label | `sha256(\"mdip-agent-kit kav-1 PUBLIC TEST KEY - never use for anything real\")` = private key |\n\n**What each vector proves, and what it doesn't:**\n\n- **A (`kav-1`)** proves your stock OpenSSL agrees with the kit's sign/verify primitive on fixed\n  bytes. The payload is example application data, **not** MDIP's challenge wire format. Its signer\n  (`did:example:...`) doesn't resolve, so `mdip-agent verify kav-1/signed.json` will fail by design.\n  Regenerate it with `node vectors/generate.js` (deterministic, no diff expected).\n- **B (`mdip-challenge-1`)** contains the real signed create operations (`*.op.json`) for two\n  throwaway agents, a challenge DID and the response DID, plus the verifier's decrypted\n  `verifyResponse` result (`verify-response.json`). OpenSSL proves that the challenge was signed\n  by the verifier's key and the response by the responder's key. Read the JSON to check the bindings:\n  the challenge `controller` is the verifier DID; the response `controller` and `data.encrypted.sender`\n  are the responder DID; the response `mdip.validUntil` ≤ the challenge's (this kit caps it there; the rule allows up to + 2 h).\n  The response payload (`challenge` DID, `responseNonce`, `match`) is **encrypted to the verifier**,\n  so only the verifier can check it; `verify-response.json` is a **recorded** result of what the\n  verifier saw, not re-derivable offline (see C for an offline-checkable version). These DIDs were\n  short-lived (1 h), so they may no longer resolve. The fixture is for\n  offline checks.\n- **C (`mdip-challenge-2`, 0.1.3)** closes the gap @homies found in B: there the binding sat in\n  ciphertext only the verifier could open. Here the verifier is a **PUBLIC TEST identity**: its wallet\n  mnemonic is derived from a published label (`test-verifier.json`) and its agent private key is\n  shipped as `verifier-agent.PUBLIC-TEST-KEY.priv.pem`. `decrypt-openssl.sh` decrypts the response\n  with stock OpenSSL only. MDIP's wire format (`@mdip/cipher` 2.2.0): key = first 32 bytes of the\n  compressed secp256k1 ECDH point (parity byte + 31 bytes of X), blob = `nonce(24) || ct || tag(16)`,\n  XChaCha20-Poly1305, no AAD. HChaCha20 is computed from one raw `openssl enc -chacha20` block and the\n  tag with `openssl mac POLY1305`. So an offline stranger can check, from signed bytes only: the verifier\n  signed challenge X, the responder signed a response whose plaintext says `\"challenge\": X`, and each\n  DID string is the hash of its op. Real verifiers keep their key private, so in production only the\n  verifier can make this check. The vector proves the format and the kit's binding logic, not a stranger's\n  view of a live exchange.\n- **D (expiry)**: at verify time the challenge **and** the response must both still be valid (`now` before\n  each `validUntil`); that is what decides whether a response is accepted in time. In addition a response\n  may expire **at most 2 h after** its challenge (`response.validUntil <= challenge.validUntil + 2 h`,\n  `RESPONSE_EXPIRY_SKEW_MS`). Stock `@mdip/keymaster` 2.2.0 gives a challenge `created + 1 h` and a response\n  `responded + 1 h`, so its responses expire a few seconds to ~1 h after the challenge; 0.1.5 accepts those\n  (page wallets, Snaps and other stock-keymaster responders interoperate). This kit's `respond` still caps at\n  exactly the challenge expiry, which is why B and C show equal timestamps to the millisecond. 2 h + 1 ns\n  later (e.g. a forged 2099 expiry) is rejected, and so is a missing response expiry when the challenge has\n  one. Since 0.1.3, `agent.verifyResponse()` / `mdip-agent verify-response` enforce this, plus \"neither already expired\",\n  and return `expiry: { ok, reason }` with `match: false` on failure (exported as `checkResponseExpiry`).\n  **Timestamp rules (fail closed, 0.1.5, thanks @homies):** a timestamp must match\n  `YYYY-MM-DDTHH:MM:SS[.1-9 digits](Z|±hh:mm)` with a real calendar date (leap years included),\n  hour ≤ 23, minute/second ≤ 59 and offset ≤ 23:59. Rejected: lowercase `t`/`z` and space separators\n  (RFC 3339 allows them; we don't), no zone, `+hhmm`, leap second `:60`, `24:00`, expanded years,\n  RFC 2822 dates, more than 9 fraction digits. Times are compared exactly to the nanosecond.\n  `verify-openssl.sh` does this in pure bash (no `date`, python3 or jq, so macOS/BSD behave the same)\n  and the JS (`parseTimestampNs`, exported) uses the same grammar and arithmetic; the two were\n  differentially fuzzed to agree. MDIP itself writes `toISOString()` (ms, `Z`).\n- **MDIP does not sign `signature.signed`** (the signature covers the op minus the whole\n  `signature` block). Anyone relaying an op can change that timestamp, which changes the op's hash\n  and therefore its DID, while `openssl ... -verify` still says Verified OK. The checkers pin the\n  shipped vectors (exact block shape, strict RFC 3339, `created <= signed <= created + 60 s`), but\n  that is a fixture rule, not an MDIP rule: the DIDs in `dids.json` are authentic only because the\n  tarball matched the registry's integrity hash. Never treat a signed op's `signed` time as attested.\n- **Not covered offline, by definition:** `ALL VECTORS OK` says nothing about whether a key is still\n  live, rotated or revoked. DID resolution, key rotation and revocation are reads at verify time, so\n  no offline vector can cover them. `verifyResponse` re-resolves the challenge and each presented\n  credential **at verify time** and ignores deactivated or revoked ones. That needs a gatekeeper:\n  check it with `npm test` (in the package dir, against your own node; it publishes one throwaway DID).\n- **Pinned, low-S, strict files.** Both checkers have the known answers built in: the kav-1\n  message hash, signature and test-key label (`pub.pem` must be the key derived from the label), and\n  all 8 DIDs of B and C. So a vector dir that was regenerated wholesale, or re-signed with another\n  key, fails even if it is self-consistent. Every signature must be **low-S** (`s <= n/2`), as MDIP\n  requires: OpenSSL also accepts the high-S twin `(r, n-s)`, so the scripts check S themselves.\n  `kav-1/message-tampered.bin` must differ from `message.bin` in exactly one byte. Each `SHA256SUMS`\n  line must be `<64 hex>  <plain file name>`, including a last line with no newline. Symlinks, dotfiles\n  and unlisted files are rejected. The decrypted plaintext in C is compared byte for byte. The pins sit\n  in the same tarball as the vectors, so your real anchor is the registry `integrity` check above.\n- **Every human-readable file is checked exactly.** The `.json` files are written in JCS key order\n  with 2-space indent, so each is compared to the bytes the signatures and DIDs cover: every\n  `*.op.json` must be its `*.signed-op.bin`, `kav-1/payload.json` must be `message.bin`, `signed.json`\n  must be `message.bin` plus the pinned signature block (hash, signed, signer, value = `signature.der`),\n  `test-key.json` / `test-verifier.json` must hold exactly the keys in the PEM files (and the pinned\n  label, mnemonic, DID, WARNING), `dids.json` exactly the pinned DIDs, and `verify-response.json` exactly\n  the recorded result (in C: the challenge and nonce of the decrypted plaintext). `signature.hex` and\n  `message.sha256` are exactly the value plus one newline, and each PEM is one canonical block. An\n  extra, duplicate or changed member, or extra bytes, fails. `verify-with-kit.js` applies the same\n  strict `SHA256SUMS` rules itself.\n- With the kit installed, `node vectors/verify-with-kit.js` re-checks everything with `@mdip/cipher`\n  (recomputes JCS from the JSON instead of trusting the `.bin` files, and re-derives the PUBLIC TEST\n  verifier key from its label -> mnemonic -> `m/44'/0'/0'/0/0`; the shell script can't do BIP32, so\n  that one link is JS-only). `bash test/vectors-negative.sh` tampers with copies of the vectors\n  (including self-consistent forgeries that also rewrite `SHA256SUMS`, `dids.json` and the JSON\n  copies, high-S twins, the other public key recovered from the same signature, a fresh-key re-sign,\n  dotfiles, symlinks, and runs with jq hidden) and checks both checkers reject every one.\n\n## Backup & restore\n\n`init` and `backup` create:\n1. **Network backup.** `backupId` (encrypted ID vault linked from the agent DID) and `backupWallet`\n   (encrypted wallet asset). The 12-word recovery phrase derives a deterministic \"seed bank\" DID that\n   points to the latest backup. Restore with **only the phrase**:\n   ```js\n   const km = await createKeymaster({ gatekeeperUrl, walletDir, passphrase: 'new-passphrase' });\n   await km.newWallet(phrase, true); await km.recoverWallet();\n   ```\n2. **Local file.** `backups/wallet-<date>.json`, encrypted with `secrets/passphrase`. Restore with\n   `keymaster.saveWallet(JSON.parse(file), true)` using the same passphrase.\n\n`verify-backups` restores both into throwaway temp wallets and checks that you get the same DID,\nthe same held credentials, and a working signing key. It writes nothing to the network.\n**Re-run `backup` after any wallet change** (new credentials, new IDs). Copy the recovery phrase\nsomewhere offline: anyone holding it controls the agent's identity.\n\n## Security notes\n\n- Secrets are generated locally, stored mode 600, `.gitignore`d, and never printed by the CLI.\n  Treat `MDIP_HOME` like an SSH key directory.\n- **Revealed** credentials (like the Public Profile) are public forever on an append-only network.\n  Only reveal what you'd put on a billboard. Use *publish* without reveal, or nothing, for anything else.\n- Challenges and responses are short-lived public DIDs. Their credential presentations are encrypted to the verifier.\n- Accepting a credential doesn't make it valid. Always check the issuer, schema, and expiry before\n  acting (`findCredentials` does this and verifies signatures; it skips credentials whose\n  `validFrom`/`validUntil` is unparseable, not yet valid, or expired, or whose signer isn't the issuer\n  or subject isn't this agent). Revocation is possible: re-check before acting.\n- `verifyResponse` adds checks keymaster 2.2.0 does not make, so act only on `match === true`:\n  the response answers `options.challenge` (replay), the challenge is yours, the response was encrypted\n  by its own controller (keymaster decrypts with the `encrypted.sender` key but reports the controller as\n  responder, so anyone could copy a public response into their own asset and claim it), expiry (challenge and response both unexpired, response at most 2 h past the challenge), every\n  presented credential's subject is the responder (keymaster alone lets anyone who has seen a credential,\n  e.g. a revealed Public Profile, present it as their own), and every presented credential is inside its\n  validity window (keymaster alone accepts expired ones). Reasons are in `result.checks`. Challenge\n  receipts are published only after all of these pass (`publish: false` to never publish).\n- `createChallenge`, `respondToChallenge({ validUntil })` and `issueCredential` reject timestamps that are\n  not strict RFC 3339 or already past, instead of creating DIDs that can never verify.\n- Don't expose your gatekeeper's write API or any keymaster REST port to the Internet.\n- npm ≥ 11 may skip `secp256k1`'s native build (`npm install-scripts approve secp256k1` to allow it).\n  The pure-JS fallback works.\n\n## Troubleshooting\n\n- **Containers can't reach each other** (gatekeeper logs `Redis connection error ... connect ETIMEDOUT`,\n  services never become ready): some hosts ship an **`iptables-legacy` FORWARD policy of DROP**.\n  With `br_netfilter` that silently drops traffic between containers on Docker bridges, even though\n  `iptables` (nft) looks fine. Check with `sudo iptables-legacy -S FORWARD`. Fix:\n  ```bash\n  for r in \"-i br+\" \"-o br+\" \"-i docker0\" \"-o docker0\"; do\n    sudo iptables-legacy -C FORWARD $r -j ACCEPT 2>/dev/null || sudo iptables-legacy -I FORWARD 1 $r -j ACCEPT; done\n  ```\n  The rules are not persistent across reboots. Re-apply them at boot.\n- **No systemd** (containers, sandboxes): start `dockerd` yourself. Compose `restart: unless-stopped`\n  revives the node whenever dockerd starts.\n- **`Invalid DID` right after creating something**: the gatekeeper processes operations\n  asynchronously. The kit waits for new DIDs (`waitForDid`). Do the same in your own code.\n- **DID not on explorer.selfid.com**: check `docker compose logs hypr-mediator` for\n  `peer capabilities received`. You need at least one peer. The node also needs outbound UDP (Hyperswarm DHT).\n\n## Credits\n\nBuilt on [@mdip/keymaster](https://www.npmjs.com/package/@mdip/keymaster),\n[@mdip/gatekeeper](https://www.npmjs.com/package/@mdip/gatekeeper) and\n[@mdip/cipher](https://www.npmjs.com/package/@mdip/cipher) (KeychainMDIP). SELF Public Profile\ntemplate DID `did:mdip:z3v8AuagfG89zr9aGxfxub4wRAd15qJs6u9C5jQPgrqC1YRHF67` (from\n`explorer.selfid.com/api/templates`).\n","readmeFilename":"README.md"}