chainlesschain 0.166.18 → 0.166.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-CQSBl3hY.js → AIOps-BjMnsUci.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-IQt-vRLt.js → ActionButton-DdYFP03A.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DkHC8O7k.js → Analytics-Bb8TuuK_.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CSti2P2T.js → AppLayout-Kn3TIOtk.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C57hxn3v.js → Artifacts-4IxpqI_b.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DByLntcf.js → Audit-BOV2caqU.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D1Bd2EOa.js → BackgroundAgents-B86k-ay4.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-VstHzHng.js → Backup-C5_ie1e3.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CLhdexIf.js → BaseInput-xpwg6QCW.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BefC_JZS.js → Chat-CVZOcZau.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DbXMvgmn.js → ChatBubbleRenderer-D8fPYhUu.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CEY982vx.js → Checkbox-MKTq3zdP.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dcm_DTvJ.js → Codegen-JUk3wzep.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C8SSu4KA.js → Col-JPvTA0kC.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BVV9vwRR.js → Community-Tqgl87NQ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B7c5Vo18.js → Compact-BuAFYBmN.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Tq9jSZ5a.js → Compliance-CXS9WIPZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BJUjT09Q.js → Cowork-Ds2bFHrr.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CjV9PEaS.js → Cron-C_dV_OeW.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Dl7ReL8F.js → Crosschain-BsO6tIRo.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CG8PjWmM.js → DID-rNOFqBac.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CDBM6wmF.js → Dashboard-C2fcKKVY.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-8_55bXTD.js → Dropdown-Y7Ndr0X_.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D3qOU1Cm.js → EmailListRenderer-DcP_uPmL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bv5oWMZF.js → FamilyGuardDashboard-Cwn3gDzk.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-hpZ1xkKd.js → Federation-ClQuOsqb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BN7GT191.js → FormItemContext-DfwtmlcK.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-PCRhfA7M.js → GenericCardRenderer-zNZhtbC5.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CIlP2vy7.js → Git-BZmdW-G5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-81lgm7YL.js → Governance-CqBk4lEn.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Cs24xMOV.js → Inference-Dbmt3ZXk.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CrJLVCZo.js → KnowledgeGraph-hMTH6qf3.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DLyO8RKT.js → Logs-CkiYgiW2.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DBOqijrP.js → MarkdownRenderer-BL1FElXg.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-Dz7AbQZc.js → Marketplace-CbB6wfin.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BM0aFiWv.js → McpTools-BSwiFtte.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C9SJzENX.js → Memory-B03PeHrz.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BO2HnNRk.js → MobileBridge-CRGrnd-X.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CK_jMz0l.js → MobileProjects-CE_5v2kO.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CfhrJsdO.js → Mtc-Dp11QVx1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BE2ELxgI.js → MtcAudit-CYu-3JBu.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CgR_E3Jd.js → Multisig-CGyJ0Xl3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Cw9445_K.js → NLProgramming-CoWi_ov1.js} +1 -1
- package/src/assets/web-panel/assets/Notes-CgU9DDo9.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-DxHVh4uT.js → NotificationSettings-DX4I2b4W.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CwHc15Zs.js +1 -0
- package/src/assets/web-panel/assets/{Organization-pPXJtWo4.js → Organization-oopLO_tk.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-YamdTJwA.js → Overflow-BFVvjMyf.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BvqP53x7.js → P2P-CBy5t-Tk.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Blz1Xn2n.js → PdhVaultBrowser--YWJNsu6.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CgFRQqFJ.js → Permissions-C2dZ8scb.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CFeo3-6O.js → PersonalDataHub-Bs3l7Hlx.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BkaRojEK.js → Pipeline-DEBWbZuD.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BNZEx2Qp.js → Privacy-CjHBb6NU.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-HUBOgEEK.js → ProjectInit-D2WipUz5.js} +2 -2
- package/src/assets/web-panel/assets/ProjectSettings-B6WfnrGB.js +2 -0
- package/src/assets/web-panel/assets/{Projects-Dc517BXp.js → Projects-FF_thRi6.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DTLf8bgj.js → Providers-CCv5M_Dv.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BBNjPszk.js → QrScannerModal-CGthxU54.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DCsHvk3G.js → QuickAsk-CnPmXPFb.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Dpr5YSop.js → Recommend-Df_tGEa9.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DmKnvqGg.js → RemoteSession-Z58N0Loe.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CSRfuurX.js → Reputation-CZTBQ2Vt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CDj3pQ_w.js → Row-CdK3YpCQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DI0a_kNq.js → RssFeed-CFMVT5sb.js} +3 -3
- package/src/assets/web-panel/assets/{Search-jAy7B2nU.js → Search-DrF8RIvD.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CkHTqYhc.js → Security-Ddk2f2DM.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CP-0Nzga.js → Services-CCuK-VOo.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BkF5-p3n.js → Skeleton-DxCpHzeM.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BDu-utDE.js → Skills-t2oTDgcv.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CQvq_2Qb.js → Sla-Df63M9_R.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-T7Ap512g.js → SpeechSettings-l7ox6KuX.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Bt-PLvot.js → SyncSettings-COd_tgPp.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-yLEcCogg.js → Tasks-CzDCcrik.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-ByN3fmbr.js → Templates-D-vfxTGA.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DBI7hITH.js → Tenant-BlpT_Gxc.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B77_DlGX.js → Terminal-D5rq52Hg.js} +2 -2
- package/src/assets/web-panel/assets/TimelineRenderer-DdoMndkK.js +1 -0
- package/src/assets/web-panel/assets/{Tokens-BV2N6cR8.js → Tokens-BhgBsI6g.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CjXWGH3p.js → Trigger-DwwzoikZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-uqXIq3WC.js → Trust-HkTpzXFb.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D-6qu4vY.js → UkeySign-D9nk6Ehe.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CxGmx_2e.js → VideoEditing-BKk0rfoy.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DSq7WlAg.js → Wallet-IVWIU_Lq.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CDBiD57O.js → WebAuthn-CFTCpfsG.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Z8f63TSb.js → WorkflowEditor-Dnf4Obif.js} +1 -1
- package/src/assets/web-panel/assets/{chat-B-UZFBjy.js → chat-D2snERdj.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CFk7K-7Q.js → colors-DKEjK1N1.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-X0IVMvgi.js → compact-item-BEDLwQdt.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DWg4j2n6.js → createContext-DA9iW1a0.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DyhgpXPf.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CkVCwNNF.js → hasIn-5l2xEcY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BipwEbTA.js → index-5pW4t1iD.js} +1 -1
- package/src/assets/web-panel/assets/{index-5OcZHdzc.js → index-7P1b8HZo.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cvwu_rA_.js → index-B00GeekA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbIAWbma.js → index-B4QYBliR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNKFsBlz.js → index-B7_W_rYd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoskgPt9.js → index-BO9dQjpt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvOa-IFV.js → index-BZ5lY2zx.js} +1 -1
- package/src/assets/web-panel/assets/{index-_-9k3Tna.js → index-B_eCoZgW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbihMThv.js → index-BgfImyZI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLSBks8h.js → index-Bq5P3piS.js} +1 -1
- package/src/assets/web-panel/assets/{index-LU54bChK.js → index-Bqc8tdIF.js} +1 -1
- package/src/assets/web-panel/assets/{index-Btqk6zWb.js → index-C7sKbM5s.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8uEL8Uw.js → index-CGlotREA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLlP1E4X.js → index-CRPrHE7h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeWKPQGs.js → index-CX1x4AFn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db_5xhrU.js → index-CbTPvp2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDfSD07S.js → index-CjWltbi_.js} +1 -1
- package/src/assets/web-panel/assets/index-Ck1jhFE0.js +1 -0
- package/src/assets/web-panel/assets/{index-BPBia4b-.js → index-CkMKuqUs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ZAOJRW.js → index-CmruhzFi.js} +1 -1
- package/src/assets/web-panel/assets/{index-P83LDnCz.js → index-Csfb-7hw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcDggpWR.js → index-CxEtQYdi.js} +1 -1
- package/src/assets/web-panel/assets/{index-OjnAwUJo.js → index-D7z4zB_a.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-EJNxgR.js → index-DLoalqRX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3-B3FWD.js → index-DU2TXJu-.js} +1 -1
- package/src/assets/web-panel/assets/{index-D92g4l3n.js → index-DXet1xol.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ca0mij1n.js → index-Dbd7eEex.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCqpl_E_.js → index-Dbkghqn_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyWlOuak.js → index-Dg2a9owQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUBkvFPH.js → index-DpqtbXdE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhVHoqGL.js → index-DqwPFbOb.js} +3 -3
- package/src/assets/web-panel/assets/{index-BG-GaZri.js → index-DvV03Ayk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMCEXBRh.js → index-NJieh3T1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B273IY-E.js → index-Td8ZwyoC.js} +1 -1
- package/src/assets/web-panel/assets/index-c8jJEHVs.js +1 -0
- package/src/assets/web-panel/assets/{index-BzXjYUmd.js → index-e-e40id6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCLwoATF.js → index-fccZmC_h.js} +1 -1
- package/src/assets/web-panel/assets/{index-CN8HZlLH.js → index-fgYk0QXO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BB4KHHcv.js → index-yYEQXYaU.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CFNgY8lJ.js → initDefaultProps-CPrcB1TC.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D-5RrDzn.js → motion-BQ7mWRh_.js} +1 -1
- package/src/assets/web-panel/assets/{move-DN-L0Bd-.js → move-CzOEq99p.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-6mgLSeCK.js → mtc-parser-BlQI5jrw.js} +1 -1
- package/src/assets/web-panel/assets/{omit-ClWW8eTP.js → omit-DlW4gxmv.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Vystq3R8.js → pickAttrs-BBYms5nP.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-D0H9hK4W.js → placementArrow-az2MPJ_7.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B3iNRU-a.js → responsiveObserve-D1-ibEol.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CiMXe6am.js → slide-DHGqa4GA.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CGDZJ1uT.js → statusUtils-CYGDo6lD.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D2zA6NkY.js → styleChecker-Cl2EM9K6.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C1sYU6QE.js → useFlexGapSupport-DdYhzuv1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BzUHSjg2.js → useFs-DWumPuBW.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CxB5yZCq.js → usePersonalDataHub-DxAO-Aqi.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BfUfMwtc.js → vnode-CplsQf_N.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CFYmj1TO.js → zoom-Cw5HXNSG.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +19 -1
- package/src/commands/evolution-knowledge.js +54 -0
- package/src/commands/evolution-workbench.js +83 -0
- package/src/commands/evolution.js +6 -1
- package/src/commands/serve.js +16 -0
- package/src/commands/skill.js +158 -16
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/session-protocol.js +2 -0
- package/src/gateways/ws/ws-agent-handler.js +124 -69
- package/src/gateways/ws/ws-server.js +19 -0
- package/src/generated/cc-agent-protocol.schema.json +7 -1
- package/src/harness/jsonl-session-store.js +25 -1
- package/src/index.js +1 -1
- package/src/lib/app-server/cli-agent-kernel-adapter.js +8 -0
- package/src/lib/app-server/graph-runtime.js +40 -1
- package/src/lib/app-server/server.js +181 -3
- package/src/lib/app-server/websocket-transport.js +2 -0
- package/src/lib/evolution/agent-evolution-runtime-composition-brand.js +17 -0
- package/src/lib/evolution/agent-evolution-runtime-composition.js +165 -1
- package/src/lib/evolution/controlled-skill-production-pilot.js +912 -0
- package/src/lib/evolution/evolution-artifact-ports.js +21 -0
- package/src/lib/evolution/evolution-raw-crypto-shred.js +185 -0
- package/src/lib/evolution/evolution-workbench-batch-executor.js +195 -0
- package/src/lib/evolution/evolution-workbench-cli-host.js +169 -0
- package/src/lib/evolution/evolution-workbench-metrics-ledger-adapter.js +648 -0
- package/src/lib/evolution/evolution-workbench-metrics.js +943 -0
- package/src/lib/evolution/evolution-workbench-projection.js +734 -0
- package/src/lib/evolution/evolution-workbench-version-control.js +272 -0
- package/src/lib/evolution/governed-knowledge-conflict-merge.js +352 -0
- package/src/lib/evolution/governed-knowledge-dependency-authority.js +273 -0
- package/src/lib/evolution/governed-knowledge-dependency-ledger-executor.js +646 -0
- package/src/lib/evolution/governed-knowledge-merge-ledger-executor.js +644 -0
- package/src/lib/evolution/governed-knowledge-merge-publisher-authority.js +282 -0
- package/src/lib/evolution/governed-knowledge-node-crypto-authority.js +348 -0
- package/src/lib/evolution/governed-knowledge-rbac-approval-authority.js +621 -0
- package/src/lib/evolution/governed-knowledge-review-host.js +249 -0
- package/src/lib/evolution/governed-knowledge-sync-ledger-adapter.js +643 -0
- package/src/lib/evolution/governed-knowledge-sync-merge-publisher.js +259 -0
- package/src/lib/evolution/governed-knowledge-sync.js +485 -0
- package/src/lib/evolution/governed-knowledge-trust-ledger.js +707 -0
- package/src/lib/evolution/governed-skill-marketplace.js +430 -0
- package/src/lib/evolution/governed-wiki-pruning.js +435 -0
- package/src/lib/evolution/skill-outcome-index-authority.js +268 -0
- package/src/lib/evolution/skill-outcome-source-catalog-authority.js +237 -0
- package/src/lib/public-ide-capability-manifest.js +4 -0
- package/src/lib/skill-outcome-authority.js +43 -0
- package/src/lib/skill-outcome-transcript-authority.js +252 -0
- package/src/lib/skill-retrieval-router-benchmark.js +135 -0
- package/src/lib/skill-retrieval-router.js +235 -0
- package/src/lib/skill-vector-authority.js +325 -0
- package/src/lib/skill-vector-process-authority.js +386 -0
- package/src/repl/agent-repl.js +7 -1
- package/src/runtime/agent-core.js +72 -6
- package/src/runtime/agent-runtime.js +42 -0
- package/src/runtime/headless-runner.js +32 -0
- package/src/runtime/headless-stream.js +36 -1
- package/src/runtime/runtime-factory.js +62 -4
- package/src/assets/web-panel/assets/Notes-DZimkTrb.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-Iy-pnX56.js +0 -1
- package/src/assets/web-panel/assets/ProjectSettings-CFMmhROS.js +0 -2
- package/src/assets/web-panel/assets/TimelineRenderer-CsLTB_QO.js +0 -1
- package/src/assets/web-panel/assets/devWarning-B2IW1J_J.js +0 -1
- package/src/assets/web-panel/assets/index-BCp2ir-1.js +0 -1
- package/src/assets/web-panel/assets/index-Bw4iuzRD.js +0 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { types as utilTypes } from "node:util";
|
|
3
|
+
|
|
4
|
+
import { verifyGovernedKnowledgeMergePlan } from "./governed-knowledge-conflict-merge.js";
|
|
5
|
+
|
|
6
|
+
export const GOVERNED_KNOWLEDGE_MERGE_PUBLISH_REQUEST_SCHEMA =
|
|
7
|
+
"chainlesschain.governed-knowledge-merge-publish-request/v1";
|
|
8
|
+
export const GOVERNED_KNOWLEDGE_MERGE_PUBLISH_RESULT_SCHEMA =
|
|
9
|
+
"chainlesschain.governed-knowledge-merge-publish-result/v1";
|
|
10
|
+
|
|
11
|
+
const AUTHORITIES = new WeakSet();
|
|
12
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
13
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}$/u;
|
|
14
|
+
const MAX_RESULT_AGE_MS = 24 * 60 * 60 * 1000;
|
|
15
|
+
const MAX_FUTURE_SKEW_MS = 5 * 60 * 1000;
|
|
16
|
+
const DESCRIPTOR_KEYS = new Set([
|
|
17
|
+
"authorityId",
|
|
18
|
+
"handlerArtifactDigest",
|
|
19
|
+
"revision",
|
|
20
|
+
]);
|
|
21
|
+
const RESULT_KEYS = new Set([
|
|
22
|
+
"attestation",
|
|
23
|
+
"deviceId",
|
|
24
|
+
"durable",
|
|
25
|
+
"envelopeDigest",
|
|
26
|
+
"idempotent",
|
|
27
|
+
"knowledgeId",
|
|
28
|
+
"mergedContentDigest",
|
|
29
|
+
"operationId",
|
|
30
|
+
"planDigest",
|
|
31
|
+
"providerAuthorityId",
|
|
32
|
+
"providerHandlerArtifactDigest",
|
|
33
|
+
"providerRevision",
|
|
34
|
+
"publishedAt",
|
|
35
|
+
"requestDigest",
|
|
36
|
+
"resultDigest",
|
|
37
|
+
"schema",
|
|
38
|
+
"tenantId",
|
|
39
|
+
]);
|
|
40
|
+
const ATTESTATION_KEYS = new Set(["algorithm", "keyId", "value"]);
|
|
41
|
+
|
|
42
|
+
function canonical(value) {
|
|
43
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
44
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
45
|
+
return `{${Object.keys(value)
|
|
46
|
+
.sort()
|
|
47
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
48
|
+
.join(",")}}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hash(domain, value) {
|
|
52
|
+
return `sha256:${createHash("sha256")
|
|
53
|
+
.update(domain)
|
|
54
|
+
.update("\0")
|
|
55
|
+
.update(canonical(value))
|
|
56
|
+
.digest("hex")}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function clone(value) {
|
|
60
|
+
return structuredClone(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function freeze(value) {
|
|
64
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
65
|
+
Object.freeze(value);
|
|
66
|
+
for (const child of Object.values(value)) freeze(child);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function exact(value, keys, label) {
|
|
72
|
+
if (
|
|
73
|
+
!value ||
|
|
74
|
+
typeof value !== "object" ||
|
|
75
|
+
Array.isArray(value) ||
|
|
76
|
+
utilTypes.isProxy(value) ||
|
|
77
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
78
|
+
) {
|
|
79
|
+
throw new TypeError(`${label} must be a plain object`);
|
|
80
|
+
}
|
|
81
|
+
const actual = Reflect.ownKeys(value);
|
|
82
|
+
if (
|
|
83
|
+
actual.length !== keys.size ||
|
|
84
|
+
actual.some((key) => typeof key !== "string" || !keys.has(key))
|
|
85
|
+
) {
|
|
86
|
+
throw new TypeError(`${label} has an invalid shape`);
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function identifier(value, label) {
|
|
92
|
+
if (typeof value !== "string" || !ID.test(value)) {
|
|
93
|
+
throw new TypeError(`${label} is invalid`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function capture(owner, method, label) {
|
|
99
|
+
if (
|
|
100
|
+
!owner ||
|
|
101
|
+
typeof owner !== "object" ||
|
|
102
|
+
utilTypes.isProxy(owner) ||
|
|
103
|
+
typeof owner[method] !== "function"
|
|
104
|
+
) {
|
|
105
|
+
throw new TypeError(`${label}.${method}() is required`);
|
|
106
|
+
}
|
|
107
|
+
return (...args) => Reflect.apply(owner[method], owner, args);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function descriptor(input, label) {
|
|
111
|
+
exact(input, DESCRIPTOR_KEYS, `${label} descriptor`);
|
|
112
|
+
if (
|
|
113
|
+
!Number.isSafeInteger(input.revision) ||
|
|
114
|
+
input.revision < 1 ||
|
|
115
|
+
!DIGEST.test(input.handlerArtifactDigest ?? "")
|
|
116
|
+
) {
|
|
117
|
+
throw new TypeError(`${label} descriptor is invalid`);
|
|
118
|
+
}
|
|
119
|
+
return freeze({
|
|
120
|
+
authorityId: identifier(input.authorityId, `${label} authorityId`),
|
|
121
|
+
revision: input.revision,
|
|
122
|
+
handlerArtifactDigest: input.handlerArtifactDigest,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function requestFor(plan) {
|
|
127
|
+
const core = {
|
|
128
|
+
schema: GOVERNED_KNOWLEDGE_MERGE_PUBLISH_REQUEST_SCHEMA,
|
|
129
|
+
tenantId: plan.tenantId,
|
|
130
|
+
deviceId: plan.deviceId,
|
|
131
|
+
operationId: `knowledge-merge:${plan.planDigest.slice(7)}`,
|
|
132
|
+
planDigest: plan.planDigest,
|
|
133
|
+
knowledgeId: plan.knowledgeId,
|
|
134
|
+
mergedContentDigest: plan.mergedKnowledge.contentDigest,
|
|
135
|
+
mergedKnowledge: plan.mergedKnowledge,
|
|
136
|
+
};
|
|
137
|
+
return freeze({
|
|
138
|
+
...core,
|
|
139
|
+
requestDigest: hash(GOVERNED_KNOWLEDGE_MERGE_PUBLISH_REQUEST_SCHEMA, core),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function validateResult(input, request, providerDescriptor, now) {
|
|
144
|
+
const result = exact(input, RESULT_KEYS, "merge publish result");
|
|
145
|
+
const attestation = exact(
|
|
146
|
+
result.attestation,
|
|
147
|
+
ATTESTATION_KEYS,
|
|
148
|
+
"merge publish attestation",
|
|
149
|
+
);
|
|
150
|
+
const core = clone(result);
|
|
151
|
+
delete core.resultDigest;
|
|
152
|
+
delete core.attestation;
|
|
153
|
+
const publishedAt = Date.parse(result.publishedAt);
|
|
154
|
+
if (
|
|
155
|
+
result.schema !== GOVERNED_KNOWLEDGE_MERGE_PUBLISH_RESULT_SCHEMA ||
|
|
156
|
+
result.tenantId !== request.tenantId ||
|
|
157
|
+
result.deviceId !== request.deviceId ||
|
|
158
|
+
result.operationId !== request.operationId ||
|
|
159
|
+
result.requestDigest !== request.requestDigest ||
|
|
160
|
+
result.planDigest !== request.planDigest ||
|
|
161
|
+
result.knowledgeId !== request.knowledgeId ||
|
|
162
|
+
result.mergedContentDigest !== request.mergedContentDigest ||
|
|
163
|
+
result.providerAuthorityId !== providerDescriptor.authorityId ||
|
|
164
|
+
result.providerRevision !== providerDescriptor.revision ||
|
|
165
|
+
result.providerHandlerArtifactDigest !==
|
|
166
|
+
providerDescriptor.handlerArtifactDigest ||
|
|
167
|
+
result.durable !== true ||
|
|
168
|
+
result.idempotent !== true ||
|
|
169
|
+
!DIGEST.test(result.envelopeDigest ?? "") ||
|
|
170
|
+
!DIGEST.test(result.resultDigest ?? "") ||
|
|
171
|
+
result.resultDigest !==
|
|
172
|
+
hash(GOVERNED_KNOWLEDGE_MERGE_PUBLISH_RESULT_SCHEMA, core) ||
|
|
173
|
+
!Number.isFinite(publishedAt) ||
|
|
174
|
+
publishedAt < now - MAX_RESULT_AGE_MS ||
|
|
175
|
+
publishedAt > now + MAX_FUTURE_SKEW_MS
|
|
176
|
+
) {
|
|
177
|
+
throw new Error("merge publisher returned an invalid durable result");
|
|
178
|
+
}
|
|
179
|
+
for (const [key, value] of Object.entries(attestation)) {
|
|
180
|
+
if (
|
|
181
|
+
typeof value !== "string" ||
|
|
182
|
+
value.trim() !== value ||
|
|
183
|
+
value.length < 1 ||
|
|
184
|
+
value.length > (key === "value" ? 4096 : 256)
|
|
185
|
+
) {
|
|
186
|
+
throw new TypeError("merge publish attestation is invalid");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return freeze(clone(result));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function digestGovernedKnowledgeMergePublishResult(value) {
|
|
193
|
+
const core = clone(value);
|
|
194
|
+
delete core.resultDigest;
|
|
195
|
+
delete core.attestation;
|
|
196
|
+
delete core.verificationReceiptDigest;
|
|
197
|
+
return hash(GOVERNED_KNOWLEDGE_MERGE_PUBLISH_RESULT_SCHEMA, core);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function createGovernedKnowledgeMergePublisherAuthority({
|
|
201
|
+
tenantId: tenantIdInput,
|
|
202
|
+
deviceId: deviceIdInput,
|
|
203
|
+
providerDescriptor: providerDescriptorInput,
|
|
204
|
+
verifierDescriptor: verifierDescriptorInput,
|
|
205
|
+
provider,
|
|
206
|
+
verifier,
|
|
207
|
+
now = Date.now,
|
|
208
|
+
} = {}) {
|
|
209
|
+
const tenantId = identifier(tenantIdInput, "tenantId");
|
|
210
|
+
const deviceId = identifier(deviceIdInput, "deviceId");
|
|
211
|
+
const providerDescriptor = descriptor(providerDescriptorInput, "provider");
|
|
212
|
+
const verifierDescriptor = descriptor(verifierDescriptorInput, "verifier");
|
|
213
|
+
if (
|
|
214
|
+
providerDescriptor.authorityId === verifierDescriptor.authorityId ||
|
|
215
|
+
providerDescriptor.handlerArtifactDigest ===
|
|
216
|
+
verifierDescriptor.handlerArtifactDigest
|
|
217
|
+
) {
|
|
218
|
+
throw new TypeError("merge provider and verifier must be independent");
|
|
219
|
+
}
|
|
220
|
+
if (typeof now !== "function" || utilTypes.isProxy(now)) {
|
|
221
|
+
throw new TypeError("now must be a non-proxy function");
|
|
222
|
+
}
|
|
223
|
+
const publish = capture(provider, "publish", "provider");
|
|
224
|
+
const verify = capture(verifier, "verify", "verifier");
|
|
225
|
+
const authority = Object.freeze({
|
|
226
|
+
tenantId,
|
|
227
|
+
deviceId,
|
|
228
|
+
async publish(planInput) {
|
|
229
|
+
const plan = verifyGovernedKnowledgeMergePlan(planInput);
|
|
230
|
+
if (plan.tenantId !== tenantId || plan.deviceId !== deviceId) {
|
|
231
|
+
throw new Error("merge publish plan crossed its authority boundary");
|
|
232
|
+
}
|
|
233
|
+
const request = requestFor(plan);
|
|
234
|
+
const currentTime = Number(now());
|
|
235
|
+
if (!Number.isFinite(currentTime)) {
|
|
236
|
+
throw new TypeError("merge publisher authority clock is invalid");
|
|
237
|
+
}
|
|
238
|
+
const result = validateResult(
|
|
239
|
+
await publish(request),
|
|
240
|
+
request,
|
|
241
|
+
providerDescriptor,
|
|
242
|
+
currentTime,
|
|
243
|
+
);
|
|
244
|
+
const verified = await verify({
|
|
245
|
+
request,
|
|
246
|
+
result,
|
|
247
|
+
providerDescriptor,
|
|
248
|
+
verifierDescriptor,
|
|
249
|
+
});
|
|
250
|
+
if (
|
|
251
|
+
verified?.authenticated !== true ||
|
|
252
|
+
verified.durable !== true ||
|
|
253
|
+
verified.tenantId !== tenantId ||
|
|
254
|
+
verified.deviceId !== deviceId ||
|
|
255
|
+
verified.operationId !== request.operationId ||
|
|
256
|
+
verified.requestDigest !== request.requestDigest ||
|
|
257
|
+
verified.planDigest !== plan.planDigest ||
|
|
258
|
+
verified.resultDigest !== result.resultDigest ||
|
|
259
|
+
verified.envelopeDigest !== result.envelopeDigest ||
|
|
260
|
+
verified.providerAuthorityId !== providerDescriptor.authorityId ||
|
|
261
|
+
verified.providerRevision !== providerDescriptor.revision ||
|
|
262
|
+
verified.verifierAuthorityId !== verifierDescriptor.authorityId ||
|
|
263
|
+
verified.verifierRevision !== verifierDescriptor.revision ||
|
|
264
|
+
!DIGEST.test(verified.verificationReceiptDigest ?? "")
|
|
265
|
+
) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"merge publisher result was not independently verified",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return freeze({
|
|
271
|
+
...clone(result),
|
|
272
|
+
verificationReceiptDigest: verified.verificationReceiptDigest,
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
AUTHORITIES.add(authority);
|
|
277
|
+
return authority;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function isGovernedKnowledgeMergePublisherAuthority(value) {
|
|
281
|
+
return AUTHORITIES.has(value);
|
|
282
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCipheriv,
|
|
3
|
+
createDecipheriv,
|
|
4
|
+
createHash,
|
|
5
|
+
createPrivateKey,
|
|
6
|
+
createPublicKey,
|
|
7
|
+
randomBytes,
|
|
8
|
+
sign,
|
|
9
|
+
verify,
|
|
10
|
+
} from "node:crypto";
|
|
11
|
+
import { types as utilTypes } from "node:util";
|
|
12
|
+
|
|
13
|
+
export const GOVERNED_KNOWLEDGE_CRYPTO_AUTHORITY_SCHEMA =
|
|
14
|
+
"chainlesschain.governed-knowledge-node-crypto-authority/v1";
|
|
15
|
+
|
|
16
|
+
const AUTHORITIES = new WeakSet();
|
|
17
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,255}$/u;
|
|
18
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
19
|
+
const SIGNATURE_KEYS = new Set(["algorithm", "keyId", "value"]);
|
|
20
|
+
const CIPHER_VERSION = 1;
|
|
21
|
+
const NONCE_BYTES = 12;
|
|
22
|
+
const TAG_BYTES = 16;
|
|
23
|
+
const MAX_PLAINTEXT_BYTES = 12 * 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
function identifier(value, label) {
|
|
26
|
+
if (typeof value !== "string" || !ID.test(value)) {
|
|
27
|
+
throw new TypeError(`${label} is invalid`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function plainRecord(value, label) {
|
|
33
|
+
if (
|
|
34
|
+
!value ||
|
|
35
|
+
typeof value !== "object" ||
|
|
36
|
+
Array.isArray(value) ||
|
|
37
|
+
utilTypes.isProxy(value) ||
|
|
38
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
39
|
+
) {
|
|
40
|
+
throw new TypeError(`${label} must be a plain object`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function canonical(value) {
|
|
46
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
47
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
48
|
+
return `{${Object.keys(value)
|
|
49
|
+
.sort()
|
|
50
|
+
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
|
|
51
|
+
.join(",")}}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sha256(value) {
|
|
55
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function keyObject(value, kind, label) {
|
|
59
|
+
let key;
|
|
60
|
+
try {
|
|
61
|
+
if (
|
|
62
|
+
value &&
|
|
63
|
+
typeof value === "object" &&
|
|
64
|
+
!utilTypes.isProxy(value) &&
|
|
65
|
+
value.type === kind
|
|
66
|
+
) {
|
|
67
|
+
key = value;
|
|
68
|
+
} else {
|
|
69
|
+
key =
|
|
70
|
+
kind === "private" ? createPrivateKey(value) : createPublicKey(value);
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
throw new TypeError(`${label} is not a valid ${kind} key`);
|
|
74
|
+
}
|
|
75
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
76
|
+
throw new TypeError(`${label} must be Ed25519`);
|
|
77
|
+
}
|
|
78
|
+
return key;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function publicKeyId(key) {
|
|
82
|
+
const spki = key.export({ format: "der", type: "spki" });
|
|
83
|
+
return `key:ed25519:${createHash("sha256").update(spki).digest("hex")}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function scopeBinding(value) {
|
|
87
|
+
const source = plainRecord(value, "knowledge metadata");
|
|
88
|
+
const tenantId = identifier(source.tenantId, "knowledge tenantId");
|
|
89
|
+
const knowledgeId = identifier(source.knowledgeId, "knowledgeId");
|
|
90
|
+
const scope = identifier(source.scope, "knowledge scope");
|
|
91
|
+
const scopeId = identifier(source.scopeId, "knowledge scopeId");
|
|
92
|
+
const action = identifier(source.action, "knowledge action");
|
|
93
|
+
if (!["project", "team", "org"].includes(scope)) {
|
|
94
|
+
throw new Error("shared knowledge encryption scope is invalid");
|
|
95
|
+
}
|
|
96
|
+
if (!["upsert", "tombstone", "revoke"].includes(action)) {
|
|
97
|
+
throw new Error("shared knowledge action is invalid");
|
|
98
|
+
}
|
|
99
|
+
if (!DIGEST.test(source.contentDigest ?? "")) {
|
|
100
|
+
throw new TypeError("knowledge contentDigest is invalid");
|
|
101
|
+
}
|
|
102
|
+
plainRecord(source.vectorClock, "knowledge vectorClock");
|
|
103
|
+
return Object.freeze({
|
|
104
|
+
schema: GOVERNED_KNOWLEDGE_CRYPTO_AUTHORITY_SCHEMA,
|
|
105
|
+
tenantId,
|
|
106
|
+
knowledgeId,
|
|
107
|
+
scope,
|
|
108
|
+
scopeId,
|
|
109
|
+
action,
|
|
110
|
+
contentDigest: source.contentDigest,
|
|
111
|
+
vectorClock: structuredClone(source.vectorClock),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function aadFor(value) {
|
|
116
|
+
return Buffer.from(canonical(scopeBinding(value)), "utf8");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeScopeKeys(scopeKeys, tenantId) {
|
|
120
|
+
if (
|
|
121
|
+
!Array.isArray(scopeKeys) ||
|
|
122
|
+
scopeKeys.length < 1 ||
|
|
123
|
+
scopeKeys.length > 256
|
|
124
|
+
) {
|
|
125
|
+
throw new TypeError("scopeKeys must contain 1..256 keys");
|
|
126
|
+
}
|
|
127
|
+
const byScope = new Map();
|
|
128
|
+
const byReference = new Map();
|
|
129
|
+
for (const input of scopeKeys) {
|
|
130
|
+
const record = plainRecord(input, "scope key");
|
|
131
|
+
const keyTenantId = identifier(record.tenantId, "scope key tenantId");
|
|
132
|
+
const scope = identifier(record.scope, "scope key scope");
|
|
133
|
+
const scopeId = identifier(record.scopeId, "scope key scopeId");
|
|
134
|
+
const keyRef = identifier(record.keyRef, "scope key keyRef");
|
|
135
|
+
if (
|
|
136
|
+
keyTenantId !== tenantId ||
|
|
137
|
+
!["project", "team", "org"].includes(scope)
|
|
138
|
+
) {
|
|
139
|
+
throw new Error("scope key crossed its tenant or scope boundary");
|
|
140
|
+
}
|
|
141
|
+
if (!Buffer.isBuffer(record.key) || record.key.length !== 32) {
|
|
142
|
+
throw new TypeError("scope key must be a 32-byte AES key");
|
|
143
|
+
}
|
|
144
|
+
const binding = `${scope}\0${scopeId}`;
|
|
145
|
+
if (byReference.has(keyRef)) throw new Error("scope keyRef is duplicated");
|
|
146
|
+
byReference.set(keyRef, {
|
|
147
|
+
tenantId,
|
|
148
|
+
scope,
|
|
149
|
+
scopeId,
|
|
150
|
+
keyRef,
|
|
151
|
+
key: Buffer.from(record.key),
|
|
152
|
+
});
|
|
153
|
+
if (record.active === true) {
|
|
154
|
+
if (byScope.has(binding))
|
|
155
|
+
throw new Error("scope has multiple active keys");
|
|
156
|
+
byScope.set(binding, byReference.get(keyRef));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (byScope.size < 1)
|
|
160
|
+
throw new Error("no active scope encryption key exists");
|
|
161
|
+
return { byScope, byReference };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function normalizePeers(peers, tenantId) {
|
|
165
|
+
if (!Array.isArray(peers) || peers.length < 1 || peers.length > 256) {
|
|
166
|
+
throw new TypeError("peerIdentities must contain 1..256 devices");
|
|
167
|
+
}
|
|
168
|
+
const result = new Map();
|
|
169
|
+
const keyIds = new Set();
|
|
170
|
+
for (const input of peers) {
|
|
171
|
+
const peer = plainRecord(input, "peer identity");
|
|
172
|
+
if (identifier(peer.tenantId, "peer tenantId") !== tenantId) {
|
|
173
|
+
throw new Error("peer identity crossed its tenant boundary");
|
|
174
|
+
}
|
|
175
|
+
const deviceId = identifier(peer.deviceId, "peer deviceId");
|
|
176
|
+
const publicKey = keyObject(peer.publicKey, "public", "peer publicKey");
|
|
177
|
+
const keyId = publicKeyId(publicKey);
|
|
178
|
+
if (peer.keyId !== undefined && peer.keyId !== keyId) {
|
|
179
|
+
throw new Error("peer keyId does not bind its Ed25519 public key");
|
|
180
|
+
}
|
|
181
|
+
if (result.has(deviceId))
|
|
182
|
+
throw new Error("peer device identity is duplicated");
|
|
183
|
+
if (keyIds.has(keyId)) {
|
|
184
|
+
throw new Error("peer Ed25519 key is assigned to multiple devices");
|
|
185
|
+
}
|
|
186
|
+
keyIds.add(keyId);
|
|
187
|
+
result.set(deviceId, { publicKey, keyId });
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function signatureMessage(envelopeDigest) {
|
|
193
|
+
if (!DIGEST.test(envelopeDigest ?? "")) {
|
|
194
|
+
throw new TypeError("envelopeDigest is invalid");
|
|
195
|
+
}
|
|
196
|
+
return Buffer.from(
|
|
197
|
+
`chainlesschain.governed-knowledge-envelope-signature/v1\0${envelopeDigest}`,
|
|
198
|
+
"utf8",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function createGovernedKnowledgeNodeCryptoAuthority({
|
|
203
|
+
tenantId: tenantIdInput,
|
|
204
|
+
deviceId: deviceIdInput,
|
|
205
|
+
privateKey: privateKeyInput,
|
|
206
|
+
scopeKeys,
|
|
207
|
+
peerIdentities,
|
|
208
|
+
random = randomBytes,
|
|
209
|
+
} = {}) {
|
|
210
|
+
const tenantId = identifier(tenantIdInput, "tenantId");
|
|
211
|
+
const deviceId = identifier(deviceIdInput, "deviceId");
|
|
212
|
+
if (typeof random !== "function" || utilTypes.isProxy(random)) {
|
|
213
|
+
throw new TypeError("random must be a non-proxy function");
|
|
214
|
+
}
|
|
215
|
+
const privateKey = keyObject(privateKeyInput, "private", "privateKey");
|
|
216
|
+
const ownPublicKey = createPublicKey(privateKey);
|
|
217
|
+
const ownKeyId = publicKeyId(ownPublicKey);
|
|
218
|
+
const keys = normalizeScopeKeys(scopeKeys, tenantId);
|
|
219
|
+
const peers = normalizePeers(peerIdentities, tenantId);
|
|
220
|
+
const ownPeer = peers.get(deviceId);
|
|
221
|
+
if (!ownPeer || ownPeer.keyId !== ownKeyId) {
|
|
222
|
+
throw new Error("device private key is not pinned in peer identities");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const authority = Object.freeze({
|
|
226
|
+
schema: GOVERNED_KNOWLEDGE_CRYPTO_AUTHORITY_SCHEMA,
|
|
227
|
+
tenantId,
|
|
228
|
+
deviceId,
|
|
229
|
+
keyId: ownKeyId,
|
|
230
|
+
async encrypt({ knowledge, plaintext } = {}) {
|
|
231
|
+
const aad = aadFor(knowledge);
|
|
232
|
+
const binding = `${knowledge.scope}\0${knowledge.scopeId}`;
|
|
233
|
+
const scopeKey = keys.byScope.get(binding);
|
|
234
|
+
if (!scopeKey)
|
|
235
|
+
throw new Error("no active key exists for knowledge scope");
|
|
236
|
+
if (
|
|
237
|
+
!Buffer.isBuffer(plaintext) ||
|
|
238
|
+
plaintext.length < 1 ||
|
|
239
|
+
plaintext.length > MAX_PLAINTEXT_BYTES
|
|
240
|
+
) {
|
|
241
|
+
throw new TypeError("knowledge plaintext is empty or unbounded");
|
|
242
|
+
}
|
|
243
|
+
const nonce = Buffer.from(random(NONCE_BYTES));
|
|
244
|
+
if (nonce.length !== NONCE_BYTES) {
|
|
245
|
+
throw new Error("crypto authority returned an invalid nonce");
|
|
246
|
+
}
|
|
247
|
+
const cipher = createCipheriv("aes-256-gcm", scopeKey.key, nonce);
|
|
248
|
+
cipher.setAAD(aad);
|
|
249
|
+
const encrypted = Buffer.concat([
|
|
250
|
+
cipher.update(plaintext),
|
|
251
|
+
cipher.final(),
|
|
252
|
+
]);
|
|
253
|
+
const ciphertext = Buffer.concat([
|
|
254
|
+
Buffer.from([CIPHER_VERSION]),
|
|
255
|
+
nonce,
|
|
256
|
+
cipher.getAuthTag(),
|
|
257
|
+
encrypted,
|
|
258
|
+
]);
|
|
259
|
+
return Object.freeze({
|
|
260
|
+
ciphertext,
|
|
261
|
+
ciphertextDigest: sha256(ciphertext),
|
|
262
|
+
keyRef: scopeKey.keyRef,
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
async decrypt({ envelope } = {}) {
|
|
266
|
+
const source = plainRecord(envelope, "knowledge envelope");
|
|
267
|
+
if (source.tenantId !== tenantId)
|
|
268
|
+
throw new Error("envelope tenant mismatch");
|
|
269
|
+
const scopeKey = keys.byReference.get(source.keyRef);
|
|
270
|
+
if (
|
|
271
|
+
!scopeKey ||
|
|
272
|
+
scopeKey.scope !== source.scope ||
|
|
273
|
+
scopeKey.scopeId !== source.scopeId
|
|
274
|
+
) {
|
|
275
|
+
throw new Error("envelope key is not authorized for its scope");
|
|
276
|
+
}
|
|
277
|
+
const ciphertext = Buffer.from(source.ciphertext ?? "", "base64");
|
|
278
|
+
if (
|
|
279
|
+
ciphertext.length <= 1 + NONCE_BYTES + TAG_BYTES ||
|
|
280
|
+
ciphertext[0] !== CIPHER_VERSION ||
|
|
281
|
+
sha256(ciphertext) !== source.ciphertextDigest
|
|
282
|
+
) {
|
|
283
|
+
throw new Error("encrypted knowledge payload is invalid");
|
|
284
|
+
}
|
|
285
|
+
const nonce = ciphertext.subarray(1, 1 + NONCE_BYTES);
|
|
286
|
+
const tag = ciphertext.subarray(
|
|
287
|
+
1 + NONCE_BYTES,
|
|
288
|
+
1 + NONCE_BYTES + TAG_BYTES,
|
|
289
|
+
);
|
|
290
|
+
const body = ciphertext.subarray(1 + NONCE_BYTES + TAG_BYTES);
|
|
291
|
+
const decipher = createDecipheriv("aes-256-gcm", scopeKey.key, nonce);
|
|
292
|
+
decipher.setAAD(aadFor(source));
|
|
293
|
+
decipher.setAuthTag(tag);
|
|
294
|
+
try {
|
|
295
|
+
return Object.freeze({
|
|
296
|
+
plaintext: Buffer.concat([decipher.update(body), decipher.final()]),
|
|
297
|
+
});
|
|
298
|
+
} catch {
|
|
299
|
+
throw new Error("encrypted knowledge authentication failed");
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
async sign({ envelopeDigest } = {}) {
|
|
303
|
+
return Object.freeze({
|
|
304
|
+
algorithm: "Ed25519",
|
|
305
|
+
keyId: ownKeyId,
|
|
306
|
+
value: sign(
|
|
307
|
+
null,
|
|
308
|
+
signatureMessage(envelopeDigest),
|
|
309
|
+
privateKey,
|
|
310
|
+
).toString("base64url"),
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
async verify({ core, envelopeDigest, signature } = {}) {
|
|
314
|
+
const source = plainRecord(core, "knowledge envelope core");
|
|
315
|
+
const peer = peers.get(source.senderDeviceId);
|
|
316
|
+
if (
|
|
317
|
+
source.tenantId !== tenantId ||
|
|
318
|
+
!peer ||
|
|
319
|
+
!signature ||
|
|
320
|
+
typeof signature !== "object" ||
|
|
321
|
+
Array.isArray(signature) ||
|
|
322
|
+
Object.keys(signature).some((key) => !SIGNATURE_KEYS.has(key)) ||
|
|
323
|
+
signature.algorithm !== "Ed25519" ||
|
|
324
|
+
signature.keyId !== peer.keyId ||
|
|
325
|
+
typeof signature.value !== "string"
|
|
326
|
+
) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
let bytes;
|
|
330
|
+
try {
|
|
331
|
+
bytes = Buffer.from(signature.value, "base64url");
|
|
332
|
+
} catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return (
|
|
336
|
+
bytes.length === 64 &&
|
|
337
|
+
bytes.toString("base64url") === signature.value &&
|
|
338
|
+
verify(null, signatureMessage(envelopeDigest), peer.publicKey, bytes)
|
|
339
|
+
);
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
AUTHORITIES.add(authority);
|
|
343
|
+
return authority;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function isGovernedKnowledgeNodeCryptoAuthority(value) {
|
|
347
|
+
return AUTHORITIES.has(value);
|
|
348
|
+
}
|