@weaveprotocol/core 0.1.0
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/README.md +1106 -0
- package/dist/elements/auth-styles.d.ts +18 -0
- package/dist/elements/auth-styles.d.ts.map +1 -0
- package/dist/elements/auth-styles.js +150 -0
- package/dist/elements/auth-styles.js.map +1 -0
- package/dist/elements/dom.d.ts +28 -0
- package/dist/elements/dom.d.ts.map +1 -0
- package/dist/elements/dom.js +74 -0
- package/dist/elements/dom.js.map +1 -0
- package/dist/elements/index.d.ts +13 -0
- package/dist/elements/index.d.ts.map +1 -0
- package/dist/elements/index.js +11 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/elements/weave-auth.d.ts +52 -0
- package/dist/elements/weave-auth.d.ts.map +1 -0
- package/dist/elements/weave-auth.js +427 -0
- package/dist/elements/weave-auth.js.map +1 -0
- package/dist/identity/account-store.d.ts +113 -0
- package/dist/identity/account-store.d.ts.map +1 -0
- package/dist/identity/account-store.js +305 -0
- package/dist/identity/account-store.js.map +1 -0
- package/dist/identity/account-vault.d.ts +199 -0
- package/dist/identity/account-vault.d.ts.map +1 -0
- package/dist/identity/account-vault.js +251 -0
- package/dist/identity/account-vault.js.map +1 -0
- package/dist/identity/agent-note.d.ts +19 -0
- package/dist/identity/agent-note.d.ts.map +1 -0
- package/dist/identity/agent-note.js +29 -0
- package/dist/identity/agent-note.js.map +1 -0
- package/dist/identity/crypto-p256.d.ts +7 -0
- package/dist/identity/crypto-p256.d.ts.map +1 -0
- package/dist/identity/crypto-p256.js +84 -0
- package/dist/identity/crypto-p256.js.map +1 -0
- package/dist/identity/device-key.d.ts +60 -0
- package/dist/identity/device-key.d.ts.map +1 -0
- package/dist/identity/device-key.js +103 -0
- package/dist/identity/device-key.js.map +1 -0
- package/dist/identity/did.d.ts +22 -0
- package/dist/identity/did.d.ts.map +1 -0
- package/dist/identity/did.js +37 -0
- package/dist/identity/did.js.map +1 -0
- package/dist/identity/folder-account.d.ts +65 -0
- package/dist/identity/folder-account.d.ts.map +1 -0
- package/dist/identity/folder-account.js +115 -0
- package/dist/identity/folder-account.js.map +1 -0
- package/dist/identity/identity-manager.d.ts +36 -0
- package/dist/identity/identity-manager.d.ts.map +1 -0
- package/dist/identity/identity-manager.js +115 -0
- package/dist/identity/identity-manager.js.map +1 -0
- package/dist/identity/index.d.ts +10 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +10 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/keys.d.ts +24 -0
- package/dist/identity/keys.d.ts.map +1 -0
- package/dist/identity/keys.js +38 -0
- package/dist/identity/keys.js.map +1 -0
- package/dist/identity/pairing.d.ts +83 -0
- package/dist/identity/pairing.d.ts.map +1 -0
- package/dist/identity/pairing.js +120 -0
- package/dist/identity/pairing.js.map +1 -0
- package/dist/identity/passkey-diagnostics.d.ts +54 -0
- package/dist/identity/passkey-diagnostics.d.ts.map +1 -0
- package/dist/identity/passkey-diagnostics.js +178 -0
- package/dist/identity/passkey-diagnostics.js.map +1 -0
- package/dist/identity/recovery-code.d.ts +50 -0
- package/dist/identity/recovery-code.d.ts.map +1 -0
- package/dist/identity/recovery-code.js +116 -0
- package/dist/identity/recovery-code.js.map +1 -0
- package/dist/identity/root-signer.d.ts +59 -0
- package/dist/identity/root-signer.d.ts.map +1 -0
- package/dist/identity/root-signer.js +44 -0
- package/dist/identity/root-signer.js.map +1 -0
- package/dist/identity/ucan.d.ts +164 -0
- package/dist/identity/ucan.d.ts.map +1 -0
- package/dist/identity/ucan.js +273 -0
- package/dist/identity/ucan.js.map +1 -0
- package/dist/identity/webauthn.d.ts +87 -0
- package/dist/identity/webauthn.d.ts.map +1 -0
- package/dist/identity/webauthn.js +155 -0
- package/dist/identity/webauthn.js.map +1 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/network/index.d.ts +17 -0
- package/dist/network/index.d.ts.map +1 -0
- package/dist/network/index.js +10 -0
- package/dist/network/index.js.map +1 -0
- package/dist/network/introductions.d.ts +85 -0
- package/dist/network/introductions.d.ts.map +1 -0
- package/dist/network/introductions.js +94 -0
- package/dist/network/introductions.js.map +1 -0
- package/dist/network/local-transport.d.ts +22 -0
- package/dist/network/local-transport.d.ts.map +1 -0
- package/dist/network/local-transport.js +55 -0
- package/dist/network/local-transport.js.map +1 -0
- package/dist/network/mesh.d.ts +31 -0
- package/dist/network/mesh.d.ts.map +1 -0
- package/dist/network/mesh.js +332 -0
- package/dist/network/mesh.js.map +1 -0
- package/dist/network/multi-signaling.d.ts +29 -0
- package/dist/network/multi-signaling.d.ts.map +1 -0
- package/dist/network/multi-signaling.js +125 -0
- package/dist/network/multi-signaling.js.map +1 -0
- package/dist/network/network-manager.d.ts +37 -0
- package/dist/network/network-manager.d.ts.map +1 -0
- package/dist/network/network-manager.js +64 -0
- package/dist/network/network-manager.js.map +1 -0
- package/dist/network/peer-auth.d.ts +98 -0
- package/dist/network/peer-auth.d.ts.map +1 -0
- package/dist/network/peer-auth.js +86 -0
- package/dist/network/peer-auth.js.map +1 -0
- package/dist/network/rtc-transport.d.ts +18 -0
- package/dist/network/rtc-transport.d.ts.map +1 -0
- package/dist/network/rtc-transport.js +149 -0
- package/dist/network/rtc-transport.js.map +1 -0
- package/dist/network/signaling.d.ts +40 -0
- package/dist/network/signaling.d.ts.map +1 -0
- package/dist/network/signaling.js +124 -0
- package/dist/network/signaling.js.map +1 -0
- package/dist/network/transport.d.ts +50 -0
- package/dist/network/transport.d.ts.map +1 -0
- package/dist/network/transport.js +13 -0
- package/dist/network/transport.js.map +1 -0
- package/dist/network/ws-transport.d.ts +42 -0
- package/dist/network/ws-transport.d.ts.map +1 -0
- package/dist/network/ws-transport.js +194 -0
- package/dist/network/ws-transport.js.map +1 -0
- package/dist/node/actions.d.ts +62 -0
- package/dist/node/actions.d.ts.map +1 -0
- package/dist/node/actions.js +500 -0
- package/dist/node/actions.js.map +1 -0
- package/dist/node/carrier.d.ts +85 -0
- package/dist/node/carrier.d.ts.map +1 -0
- package/dist/node/carrier.js +192 -0
- package/dist/node/carrier.js.map +1 -0
- package/dist/node/copy.d.ts +38 -0
- package/dist/node/copy.d.ts.map +1 -0
- package/dist/node/copy.js +53 -0
- package/dist/node/copy.js.map +1 -0
- package/dist/node/index.d.ts +16 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +11 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/node.d.ts +12 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +872 -0
- package/dist/node/node.js.map +1 -0
- package/dist/node/space-runtime.d.ts +144 -0
- package/dist/node/space-runtime.d.ts.map +1 -0
- package/dist/node/space-runtime.js +1221 -0
- package/dist/node/space-runtime.js.map +1 -0
- package/dist/node/stores.d.ts +45 -0
- package/dist/node/stores.d.ts.map +1 -0
- package/dist/node/stores.js +31 -0
- package/dist/node/stores.js.map +1 -0
- package/dist/node/types.d.ts +508 -0
- package/dist/node/types.d.ts.map +1 -0
- package/dist/node/types.js +2 -0
- package/dist/node/types.js.map +1 -0
- package/dist/privacy/index.d.ts +4 -0
- package/dist/privacy/index.d.ts.map +1 -0
- package/dist/privacy/index.js +4 -0
- package/dist/privacy/index.js.map +1 -0
- package/dist/privacy/key-distribution.d.ts +42 -0
- package/dist/privacy/key-distribution.d.ts.map +1 -0
- package/dist/privacy/key-distribution.js +50 -0
- package/dist/privacy/key-distribution.js.map +1 -0
- package/dist/privacy/privacy-guard.d.ts +24 -0
- package/dist/privacy/privacy-guard.d.ts.map +1 -0
- package/dist/privacy/privacy-guard.js +87 -0
- package/dist/privacy/privacy-guard.js.map +1 -0
- package/dist/privacy/space-encryption.d.ts +48 -0
- package/dist/privacy/space-encryption.d.ts.map +1 -0
- package/dist/privacy/space-encryption.js +61 -0
- package/dist/privacy/space-encryption.js.map +1 -0
- package/dist/query/engine.d.ts +27 -0
- package/dist/query/engine.d.ts.map +1 -0
- package/dist/query/engine.js +84 -0
- package/dist/query/engine.js.map +1 -0
- package/dist/query/filter.d.ts +15 -0
- package/dist/query/filter.d.ts.map +1 -0
- package/dist/query/filter.js +207 -0
- package/dist/query/filter.js.map +1 -0
- package/dist/query/types.d.ts +144 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/query/types.js +22 -0
- package/dist/query/types.js.map +1 -0
- package/dist/react/context.d.ts +81 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +95 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/index.d.ts +40 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +36 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/use-live.d.ts +16 -0
- package/dist/react/use-live.d.ts.map +1 -0
- package/dist/react/use-live.js +55 -0
- package/dist/react/use-live.js.map +1 -0
- package/dist/react/use-query.d.ts +17 -0
- package/dist/react/use-query.d.ts.map +1 -0
- package/dist/react/use-query.js +23 -0
- package/dist/react/use-query.js.map +1 -0
- package/dist/react/use-space.d.ts +29 -0
- package/dist/react/use-space.d.ts.map +1 -0
- package/dist/react/use-space.js +48 -0
- package/dist/react/use-space.js.map +1 -0
- package/dist/react/use-spaces.d.ts +19 -0
- package/dist/react/use-spaces.d.ts.map +1 -0
- package/dist/react/use-spaces.js +55 -0
- package/dist/react/use-spaces.js.map +1 -0
- package/dist/react/use-weave-auth.d.ts +10 -0
- package/dist/react/use-weave-auth.d.ts.map +1 -0
- package/dist/react/use-weave-auth.js +15 -0
- package/dist/react/use-weave-auth.js.map +1 -0
- package/dist/react/weave-auth.d.ts +17 -0
- package/dist/react/weave-auth.d.ts.map +1 -0
- package/dist/react/weave-auth.js +38 -0
- package/dist/react/weave-auth.js.map +1 -0
- package/dist/records/describe.d.ts +38 -0
- package/dist/records/describe.d.ts.map +1 -0
- package/dist/records/describe.js +101 -0
- package/dist/records/describe.js.map +1 -0
- package/dist/records/links.d.ts +14 -0
- package/dist/records/links.d.ts.map +1 -0
- package/dist/records/links.js +25 -0
- package/dist/records/links.js.map +1 -0
- package/dist/records/rules.d.ts +59 -0
- package/dist/records/rules.d.ts.map +1 -0
- package/dist/records/rules.js +118 -0
- package/dist/records/rules.js.map +1 -0
- package/dist/records/version.d.ts +54 -0
- package/dist/records/version.d.ts.map +1 -0
- package/dist/records/version.js +70 -0
- package/dist/records/version.js.map +1 -0
- package/dist/schema/collection-def.d.ts +96 -0
- package/dist/schema/collection-def.d.ts.map +1 -0
- package/dist/schema/collection-def.js +272 -0
- package/dist/schema/collection-def.js.map +1 -0
- package/dist/schema/expression.d.ts +69 -0
- package/dist/schema/expression.d.ts.map +1 -0
- package/dist/schema/expression.js +92 -0
- package/dist/schema/expression.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/schema-engine.d.ts +42 -0
- package/dist/schema/schema-engine.d.ts.map +1 -0
- package/dist/schema/schema-engine.js +36 -0
- package/dist/schema/schema-engine.js.map +1 -0
- package/dist/schema/signer.d.ts +27 -0
- package/dist/schema/signer.d.ts.map +1 -0
- package/dist/schema/signer.js +36 -0
- package/dist/schema/signer.js.map +1 -0
- package/dist/schemas/apps.d.ts +88 -0
- package/dist/schemas/apps.d.ts.map +1 -0
- package/dist/schemas/apps.js +168 -0
- package/dist/schemas/apps.js.map +1 -0
- package/dist/schemas/index.d.ts +410 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +208 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/screens.d.ts +67 -0
- package/dist/schemas/screens.d.ts.map +1 -0
- package/dist/schemas/screens.js +242 -0
- package/dist/schemas/screens.js.map +1 -0
- package/dist/session/agent-link.d.ts +79 -0
- package/dist/session/agent-link.d.ts.map +1 -0
- package/dist/session/agent-link.js +251 -0
- package/dist/session/agent-link.js.map +1 -0
- package/dist/session/auth.d.ts +233 -0
- package/dist/session/auth.d.ts.map +1 -0
- package/dist/session/auth.js +783 -0
- package/dist/session/auth.js.map +1 -0
- package/dist/session/connect.d.ts +211 -0
- package/dist/session/connect.d.ts.map +1 -0
- package/dist/session/connect.js +355 -0
- package/dist/session/connect.js.map +1 -0
- package/dist/session/connection.d.ts +70 -0
- package/dist/session/connection.d.ts.map +1 -0
- package/dist/session/connection.js +120 -0
- package/dist/session/connection.js.map +1 -0
- package/dist/session/credentials.d.ts +44 -0
- package/dist/session/credentials.d.ts.map +1 -0
- package/dist/session/credentials.js +60 -0
- package/dist/session/credentials.js.map +1 -0
- package/dist/session/index.d.ts +25 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +18 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/pairing.d.ts +59 -0
- package/dist/session/pairing.d.ts.map +1 -0
- package/dist/session/pairing.js +144 -0
- package/dist/session/pairing.js.map +1 -0
- package/dist/session/places.d.ts +62 -0
- package/dist/session/places.d.ts.map +1 -0
- package/dist/session/places.js +101 -0
- package/dist/session/places.js.map +1 -0
- package/dist/session/stay-signed-in.d.ts +37 -0
- package/dist/session/stay-signed-in.d.ts.map +1 -0
- package/dist/session/stay-signed-in.js +124 -0
- package/dist/session/stay-signed-in.js.map +1 -0
- package/dist/space/account-registry.d.ts +64 -0
- package/dist/space/account-registry.d.ts.map +1 -0
- package/dist/space/account-registry.js +66 -0
- package/dist/space/account-registry.js.map +1 -0
- package/dist/space/index.d.ts +3 -0
- package/dist/space/index.d.ts.map +1 -0
- package/dist/space/index.js +3 -0
- package/dist/space/index.js.map +1 -0
- package/dist/space/pass.d.ts +63 -0
- package/dist/space/pass.d.ts.map +1 -0
- package/dist/space/pass.js +54 -0
- package/dist/space/pass.js.map +1 -0
- package/dist/space/presets.d.ts +31 -0
- package/dist/space/presets.d.ts.map +1 -0
- package/dist/space/presets.js +24 -0
- package/dist/space/presets.js.map +1 -0
- package/dist/space/roles.d.ts +186 -0
- package/dist/space/roles.d.ts.map +1 -0
- package/dist/space/roles.js +500 -0
- package/dist/space/roles.js.map +1 -0
- package/dist/space/space-access.d.ts +80 -0
- package/dist/space/space-access.d.ts.map +1 -0
- package/dist/space/space-access.js +135 -0
- package/dist/space/space-access.js.map +1 -0
- package/dist/space/space-manager.d.ts +87 -0
- package/dist/space/space-manager.d.ts.map +1 -0
- package/dist/space/space-manager.js +187 -0
- package/dist/space/space-manager.js.map +1 -0
- package/dist/storage/directory-access.d.ts +78 -0
- package/dist/storage/directory-access.d.ts.map +1 -0
- package/dist/storage/directory-access.js +164 -0
- package/dist/storage/directory-access.js.map +1 -0
- package/dist/storage/encrypted-adapter.d.ts +49 -0
- package/dist/storage/encrypted-adapter.d.ts.map +1 -0
- package/dist/storage/encrypted-adapter.js +106 -0
- package/dist/storage/encrypted-adapter.js.map +1 -0
- package/dist/storage/folder-adapter.d.ts +105 -0
- package/dist/storage/folder-adapter.d.ts.map +1 -0
- package/dist/storage/folder-adapter.js +280 -0
- package/dist/storage/folder-adapter.js.map +1 -0
- package/dist/storage/folder-reconcile.d.ts +53 -0
- package/dist/storage/folder-reconcile.d.ts.map +1 -0
- package/dist/storage/folder-reconcile.js +65 -0
- package/dist/storage/folder-reconcile.js.map +1 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +9 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/indexeddb-adapter.d.ts +12 -0
- package/dist/storage/indexeddb-adapter.d.ts.map +1 -0
- package/dist/storage/indexeddb-adapter.js +190 -0
- package/dist/storage/indexeddb-adapter.js.map +1 -0
- package/dist/storage/mst.d.ts +121 -0
- package/dist/storage/mst.d.ts.map +1 -0
- package/dist/storage/mst.js +402 -0
- package/dist/storage/mst.js.map +1 -0
- package/dist/storage/storage-provider.d.ts +85 -0
- package/dist/storage/storage-provider.d.ts.map +1 -0
- package/dist/storage/storage-provider.js +220 -0
- package/dist/storage/storage-provider.js.map +1 -0
- package/dist/sync/anti-entropy.d.ts +49 -0
- package/dist/sync/anti-entropy.d.ts.map +1 -0
- package/dist/sync/anti-entropy.js +62 -0
- package/dist/sync/anti-entropy.js.map +1 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +4 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/sync-engine.d.ts +71 -0
- package/dist/sync/sync-engine.d.ts.map +1 -0
- package/dist/sync/sync-engine.js +309 -0
- package/dist/sync/sync-engine.js.map +1 -0
- package/dist/sync/sync-messages.d.ts +75 -0
- package/dist/sync/sync-messages.d.ts.map +1 -0
- package/dist/sync/sync-messages.js +12 -0
- package/dist/sync/sync-messages.js.map +1 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/encoding.d.ts +63 -0
- package/dist/utils/encoding.d.ts.map +1 -0
- package/dist/utils/encoding.js +130 -0
- package/dist/utils/encoding.js.map +1 -0
- package/dist/utils/errors.d.ts +43 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +28 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/events.d.ts +19 -0
- package/dist/utils/events.d.ts.map +1 -0
- package/dist/utils/events.js +27 -0
- package/dist/utils/events.js.map +1 -0
- package/dist/utils/hash.d.ts +23 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +49 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/validation/capability-gate.d.ts +50 -0
- package/dist/validation/capability-gate.d.ts.map +1 -0
- package/dist/validation/capability-gate.js +71 -0
- package/dist/validation/capability-gate.js.map +1 -0
- package/dist/validation/crypto-gate.d.ts +16 -0
- package/dist/validation/crypto-gate.d.ts.map +1 -0
- package/dist/validation/crypto-gate.js +37 -0
- package/dist/validation/crypto-gate.js.map +1 -0
- package/dist/validation/index.d.ts +6 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +6 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/stateful-gate.d.ts +15 -0
- package/dist/validation/stateful-gate.d.ts.map +1 -0
- package/dist/validation/stateful-gate.js +50 -0
- package/dist/validation/stateful-gate.js.map +1 -0
- package/dist/validation/structural-gate.d.ts +18 -0
- package/dist/validation/structural-gate.d.ts.map +1 -0
- package/dist/validation/structural-gate.js +54 -0
- package/dist/validation/structural-gate.js.map +1 -0
- package/dist/validation/validation-engine.d.ts +28 -0
- package/dist/validation/validation-engine.d.ts.map +1 -0
- package/dist/validation/validation-engine.js +38 -0
- package/dist/validation/validation-engine.js.map +1 -0
- package/package.json +107 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module pairing
|
|
3
|
+
* Handing an account to a phone.
|
|
4
|
+
*
|
|
5
|
+
* A phone cannot open a folder — no mobile browser has the File System Access
|
|
6
|
+
* API — so it keeps its own copy of the data, like any other peer. What it needs
|
|
7
|
+
* from the desktop is two things: who you are, and which lists exist.
|
|
8
|
+
*
|
|
9
|
+
* The first fits in a QR code. The second does not, and does not need to: once
|
|
10
|
+
* both sides know the seed they can each work out the same private meeting room
|
|
11
|
+
* and the same key, meet there over the ordinary peer connection, and hand the
|
|
12
|
+
* list of lists across encrypted. So the code stays small enough for a phone
|
|
13
|
+
* camera to read from across a desk.
|
|
14
|
+
*
|
|
15
|
+
* Deriving the room from the *seed* rather than from the DID matters. A DID
|
|
16
|
+
* appears in every expression the account has ever signed, so a room named after
|
|
17
|
+
* one would be a room anyone could find. A room named after the seed can only be
|
|
18
|
+
* found by someone who already has it.
|
|
19
|
+
*
|
|
20
|
+
* What this is not: the phone does not become a satellite of the desktop. It
|
|
21
|
+
* derives the same identity and becomes a full peer, syncing with anyone in the
|
|
22
|
+
* space. The desktop was a bootstrap, and can go offline immediately afterwards.
|
|
23
|
+
*/
|
|
24
|
+
import { base64UrlEncode, base64UrlDecode, utf8Encode, utf8Decode, concatBytes } from '../utils/encoding.js';
|
|
25
|
+
import { cidFromBytes } from '../utils/hash.js';
|
|
26
|
+
import { protocolError } from '../utils/errors.js';
|
|
27
|
+
const ROOM_PREFIX = utf8Encode('weave-pairing-room-v1');
|
|
28
|
+
const PAIRING_KEY_INFO = utf8Encode('weave-pairing-key-v1');
|
|
29
|
+
const IV_BYTES = 12;
|
|
30
|
+
/**
|
|
31
|
+
* The room both devices meet in.
|
|
32
|
+
*
|
|
33
|
+
* Derived from the seed, so finding it already requires the secret. Both sides
|
|
34
|
+
* compute it independently and neither has to send it.
|
|
35
|
+
*
|
|
36
|
+
* @param seed The account seed
|
|
37
|
+
* @returns An opaque room id for the signaling relay
|
|
38
|
+
*/
|
|
39
|
+
export async function pairingRoomId(seed) {
|
|
40
|
+
return cidFromBytes(concatBytes(ROOM_PREFIX, seed));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The key the handover is encrypted with.
|
|
44
|
+
*
|
|
45
|
+
* Separate from the vault key and from the signing key, so that a relay
|
|
46
|
+
* operator watching the room learns nothing but the size of the payload.
|
|
47
|
+
*
|
|
48
|
+
* @param seed The account seed
|
|
49
|
+
* @returns An AES-GCM key
|
|
50
|
+
*/
|
|
51
|
+
export async function derivePairingKey(seed) {
|
|
52
|
+
const material = await globalThis.crypto.subtle.importKey('raw', seed, { name: 'HKDF' }, false, ['deriveKey']);
|
|
53
|
+
return globalThis.crypto.subtle.deriveKey({
|
|
54
|
+
name: 'HKDF',
|
|
55
|
+
hash: 'SHA-256',
|
|
56
|
+
salt: new Uint8Array(0),
|
|
57
|
+
info: PAIRING_KEY_INFO,
|
|
58
|
+
}, material, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Packs a ticket for a URL fragment.
|
|
62
|
+
*
|
|
63
|
+
* A fragment, because browsers never put one in a request — so the seed reaches
|
|
64
|
+
* the phone without passing through the server hosting the page, the same way a
|
|
65
|
+
* space invite carries its key.
|
|
66
|
+
*
|
|
67
|
+
* @param ticket What the phone needs to know
|
|
68
|
+
* @returns A compact string for after the `#`
|
|
69
|
+
*/
|
|
70
|
+
export function encodePairingTicket(ticket) {
|
|
71
|
+
return base64UrlEncode(utf8Encode(JSON.stringify(ticket)));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Unpacks a ticket from a URL fragment.
|
|
75
|
+
* @param encoded The string after `#pair=`
|
|
76
|
+
* @returns The ticket it stands for
|
|
77
|
+
*/
|
|
78
|
+
export function decodePairingTicket(encoded) {
|
|
79
|
+
try {
|
|
80
|
+
const ticket = JSON.parse(utf8Decode(base64UrlDecode(encoded.trim())));
|
|
81
|
+
if (ticket?.v !== 1 || typeof ticket.code !== 'string' || typeof ticket.relay !== 'string') {
|
|
82
|
+
throw new Error('unexpected shape');
|
|
83
|
+
}
|
|
84
|
+
return ticket;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
throw protocolError('PAIRING_TICKET_UNREADABLE', 'That pairing link could not be read.', 'It may have been truncated, or produced by a different app. Show the code again.');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Encrypts the handover payload.
|
|
92
|
+
* @param plaintext What to send
|
|
93
|
+
* @param key From {@link derivePairingKey}
|
|
94
|
+
* @returns Nonce followed by ciphertext
|
|
95
|
+
*/
|
|
96
|
+
export async function sealPairingPayload(plaintext, key) {
|
|
97
|
+
const iv = globalThis.crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
98
|
+
const ciphertext = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, plaintext);
|
|
99
|
+
return concatBytes(iv, new Uint8Array(ciphertext));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Decrypts the handover payload.
|
|
103
|
+
* @param sealed What arrived
|
|
104
|
+
* @param key From {@link derivePairingKey}
|
|
105
|
+
* @returns The plaintext
|
|
106
|
+
*/
|
|
107
|
+
export async function openPairingPayload(sealed, key) {
|
|
108
|
+
if (sealed.length <= IV_BYTES) {
|
|
109
|
+
throw protocolError('PAIRING_TICKET_UNREADABLE', 'That handover was too short to be real.');
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const plain = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: sealed.slice(0, IV_BYTES) }, key, sealed.slice(IV_BYTES));
|
|
113
|
+
return new Uint8Array(plain);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Someone else in the room, or a ticket for a different account.
|
|
117
|
+
throw protocolError('PAIRING_TICKET_UNREADABLE', 'That handover was not meant for this account.');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=pairing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing.js","sourceRoot":"","sources":["../../src/identity/pairing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module passkey-diagnostics
|
|
3
|
+
* Reports what a browser and credential provider actually do with the PRF
|
|
4
|
+
* extension, so a failure can be pinned on a specific link in the chain rather
|
|
5
|
+
* than guessed at.
|
|
6
|
+
*/
|
|
7
|
+
/** Where a ceremony's PRF request ended up */
|
|
8
|
+
export interface CeremonyReport {
|
|
9
|
+
readonly attempted: boolean;
|
|
10
|
+
/** Raw client extension results, with binary values summarized */
|
|
11
|
+
readonly extensionResults: Record<string, unknown> | null;
|
|
12
|
+
/** Bytes of PRF output this ceremony produced, or null for none */
|
|
13
|
+
readonly prfOutputBytes: number | null;
|
|
14
|
+
readonly error?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PasskeyDiagnostics {
|
|
17
|
+
readonly webauthnAvailable: boolean;
|
|
18
|
+
readonly platformAuthenticatorAvailable: boolean;
|
|
19
|
+
/** AAGUID of the authenticator that answered, and its name when recognized */
|
|
20
|
+
readonly provider: {
|
|
21
|
+
readonly aaguid: string | null;
|
|
22
|
+
readonly name: string | null;
|
|
23
|
+
};
|
|
24
|
+
/** Whether the creation ceremony reported `prf.enabled` */
|
|
25
|
+
readonly prfDeclaredAtCreate: boolean | undefined;
|
|
26
|
+
readonly create: CeremonyReport;
|
|
27
|
+
readonly assert: CeremonyReport;
|
|
28
|
+
/** The only question that matters: did a secret come back? */
|
|
29
|
+
readonly prfWorks: boolean;
|
|
30
|
+
/** A sentence naming the most likely culprit */
|
|
31
|
+
readonly summary: string;
|
|
32
|
+
readonly credentialId: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface DiagnosticsOptions {
|
|
35
|
+
readonly rpId?: string;
|
|
36
|
+
readonly rpName?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Test this existing credential instead of creating one. Without it the run
|
|
39
|
+
* creates a throwaway passkey, which will appear in the user's authenticator.
|
|
40
|
+
*/
|
|
41
|
+
readonly credentialId?: string;
|
|
42
|
+
readonly userName?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Runs the PRF ceremonies and reports what came back at each step.
|
|
46
|
+
*
|
|
47
|
+
* Creating a passkey and asserting it are separate conversations with the
|
|
48
|
+
* provider, and PRF can be dropped in either — this shows which.
|
|
49
|
+
*
|
|
50
|
+
* @param options Which credential to test, and how to identify the site
|
|
51
|
+
* @returns A structured report, safe to display or copy into a bug report
|
|
52
|
+
*/
|
|
53
|
+
export declare function inspectPasskeyPrf(options?: DiagnosticsOptions): Promise<PasskeyDiagnostics>;
|
|
54
|
+
//# sourceMappingURL=passkey-diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passkey-diagnostics.d.ts","sourceRoot":"","sources":["../../src/identity/passkey-diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1D,mEAAmE;IACnE,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,8BAA8B,EAAE,OAAO,CAAC;IACjD,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACpF,2DAA2D;IAC3D,QAAQ,CAAC,mBAAmB,EAAE,OAAO,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AA8CD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwHjG"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module passkey-diagnostics
|
|
3
|
+
* Reports what a browser and credential provider actually do with the PRF
|
|
4
|
+
* extension, so a failure can be pinned on a specific link in the chain rather
|
|
5
|
+
* than guessed at.
|
|
6
|
+
*/
|
|
7
|
+
import { base64UrlEncode, base64UrlDecode } from '../utils/encoding.js';
|
|
8
|
+
const PRF_SALT = new TextEncoder().encode('weave-protocol-key-v1');
|
|
9
|
+
/** AAGUIDs of providers common enough to be worth naming. */
|
|
10
|
+
const KNOWN_AAGUIDS = {
|
|
11
|
+
'd548826e-79b4-db40-a3d8-11116f7e8349': 'Bitwarden',
|
|
12
|
+
'bada5566-a7aa-401f-bd96-45619a55120d': '1Password',
|
|
13
|
+
'fbfc3007-154e-4ecc-8c0b-6e020557d7bd': 'iCloud Keychain',
|
|
14
|
+
'ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4': 'Google Password Manager',
|
|
15
|
+
'08987058-cadc-4b81-b6e1-30de50dcbe96': 'Windows Hello',
|
|
16
|
+
'531126d6-e717-415c-9320-3d9aa6981239': 'Dashlane',
|
|
17
|
+
'fdb141b2-5d84-443e-8a35-4698c205a502': 'KeePassXC',
|
|
18
|
+
'00000000-0000-0000-0000-000000000000': 'Platform authenticator (no AAGUID)',
|
|
19
|
+
};
|
|
20
|
+
/** Formats the 16 AAGUID bytes that sit at offset 37 of attested credential data. */
|
|
21
|
+
function readAaguid(authenticatorData) {
|
|
22
|
+
if (!authenticatorData || authenticatorData.byteLength < 53)
|
|
23
|
+
return null;
|
|
24
|
+
const bytes = new Uint8Array(authenticatorData).slice(37, 53);
|
|
25
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
26
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
27
|
+
}
|
|
28
|
+
/** Replaces binary values with a readable summary so the report can be shown or copied. */
|
|
29
|
+
function summarizeExtensions(results) {
|
|
30
|
+
if (!results || typeof results !== 'object')
|
|
31
|
+
return null;
|
|
32
|
+
const walk = (value) => {
|
|
33
|
+
if (value instanceof ArrayBuffer)
|
|
34
|
+
return `<${value.byteLength} bytes>`;
|
|
35
|
+
if (ArrayBuffer.isView(value))
|
|
36
|
+
return `<${value.byteLength} bytes>`;
|
|
37
|
+
if (Array.isArray(value))
|
|
38
|
+
return value.map(walk);
|
|
39
|
+
if (value && typeof value === 'object') {
|
|
40
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, walk(v)]));
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
return walk(results);
|
|
45
|
+
}
|
|
46
|
+
function prfOutputLength(results) {
|
|
47
|
+
const first = results?.prf?.results?.first;
|
|
48
|
+
return first ? first.byteLength : null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Runs the PRF ceremonies and reports what came back at each step.
|
|
52
|
+
*
|
|
53
|
+
* Creating a passkey and asserting it are separate conversations with the
|
|
54
|
+
* provider, and PRF can be dropped in either — this shows which.
|
|
55
|
+
*
|
|
56
|
+
* @param options Which credential to test, and how to identify the site
|
|
57
|
+
* @returns A structured report, safe to display or copy into a bug report
|
|
58
|
+
*/
|
|
59
|
+
export async function inspectPasskeyPrf(options) {
|
|
60
|
+
const webauthnAvailable = !!globalThis.navigator?.credentials?.create;
|
|
61
|
+
const rpId = options?.rpId ?? (typeof globalThis.location !== 'undefined' ? globalThis.location.hostname : 'localhost');
|
|
62
|
+
let platformAuthenticatorAvailable = false;
|
|
63
|
+
try {
|
|
64
|
+
platformAuthenticatorAvailable =
|
|
65
|
+
(await globalThis.PublicKeyCredential?.isUserVerifyingPlatformAuthenticatorAvailable?.()) ?? false;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
platformAuthenticatorAvailable = false;
|
|
69
|
+
}
|
|
70
|
+
const create = { attempted: false, extensionResults: null, prfOutputBytes: null };
|
|
71
|
+
const assert = { attempted: false, extensionResults: null, prfOutputBytes: null };
|
|
72
|
+
let credentialId = options?.credentialId ?? null;
|
|
73
|
+
let aaguid = null;
|
|
74
|
+
let prfDeclaredAtCreate;
|
|
75
|
+
if (!webauthnAvailable) {
|
|
76
|
+
return Object.freeze({
|
|
77
|
+
webauthnAvailable,
|
|
78
|
+
platformAuthenticatorAvailable,
|
|
79
|
+
provider: { aaguid: null, name: null },
|
|
80
|
+
prfDeclaredAtCreate: undefined,
|
|
81
|
+
create: Object.freeze(create),
|
|
82
|
+
assert: Object.freeze(assert),
|
|
83
|
+
prfWorks: false,
|
|
84
|
+
summary: 'WebAuthn is unavailable in this browser, so passkeys cannot be used at all.',
|
|
85
|
+
credentialId: null,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// 1. Creation — unless we were pointed at an existing credential.
|
|
89
|
+
if (!credentialId) {
|
|
90
|
+
create.attempted = true;
|
|
91
|
+
try {
|
|
92
|
+
const credential = (await globalThis.navigator.credentials.create({
|
|
93
|
+
publicKey: {
|
|
94
|
+
rp: { id: rpId, name: options?.rpName ?? 'PRF diagnostic' },
|
|
95
|
+
user: {
|
|
96
|
+
id: globalThis.crypto.getRandomValues(new Uint8Array(32)),
|
|
97
|
+
name: options?.userName ?? 'prf-diagnostic',
|
|
98
|
+
displayName: options?.userName ?? 'PRF diagnostic (safe to delete)',
|
|
99
|
+
},
|
|
100
|
+
challenge: globalThis.crypto.getRandomValues(new Uint8Array(32)),
|
|
101
|
+
pubKeyCredParams: [
|
|
102
|
+
{ type: 'public-key', alg: -7 },
|
|
103
|
+
{ type: 'public-key', alg: -257 },
|
|
104
|
+
],
|
|
105
|
+
authenticatorSelection: {
|
|
106
|
+
residentKey: 'required',
|
|
107
|
+
requireResidentKey: true,
|
|
108
|
+
userVerification: 'required',
|
|
109
|
+
},
|
|
110
|
+
extensions: { prf: { eval: { first: PRF_SALT } } },
|
|
111
|
+
},
|
|
112
|
+
}));
|
|
113
|
+
const results = credential.getClientExtensionResults();
|
|
114
|
+
create.extensionResults = summarizeExtensions(results);
|
|
115
|
+
create.prfOutputBytes = prfOutputLength(results);
|
|
116
|
+
prfDeclaredAtCreate = results.prf?.enabled;
|
|
117
|
+
credentialId = base64UrlEncode(new Uint8Array(credential.rawId));
|
|
118
|
+
const response = credential.response;
|
|
119
|
+
aaguid = readAaguid(response.getAuthenticatorData?.());
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
create.error = error instanceof Error ? `${error.name}: ${error.message}` : 'Creation failed';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// 2. Assertion — the ceremony that actually has to yield a secret.
|
|
126
|
+
if (credentialId) {
|
|
127
|
+
assert.attempted = true;
|
|
128
|
+
try {
|
|
129
|
+
const credential = (await globalThis.navigator.credentials.get({
|
|
130
|
+
publicKey: {
|
|
131
|
+
challenge: globalThis.crypto.getRandomValues(new Uint8Array(32)),
|
|
132
|
+
rpId,
|
|
133
|
+
userVerification: 'required',
|
|
134
|
+
allowCredentials: [{ type: 'public-key', id: base64UrlDecode(credentialId) }],
|
|
135
|
+
extensions: { prf: { eval: { first: PRF_SALT } } },
|
|
136
|
+
},
|
|
137
|
+
}));
|
|
138
|
+
const results = credential.getClientExtensionResults();
|
|
139
|
+
assert.extensionResults = summarizeExtensions(results);
|
|
140
|
+
assert.prfOutputBytes = prfOutputLength(results);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
assert.error = error instanceof Error ? `${error.name}: ${error.message}` : 'Assertion failed';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const prfWorks = (assert.prfOutputBytes ?? 0) > 0 || (create.prfOutputBytes ?? 0) > 0;
|
|
147
|
+
const providerName = aaguid ? (KNOWN_AAGUIDS[aaguid] ?? null) : null;
|
|
148
|
+
return Object.freeze({
|
|
149
|
+
webauthnAvailable,
|
|
150
|
+
platformAuthenticatorAvailable,
|
|
151
|
+
provider: Object.freeze({ aaguid, name: providerName }),
|
|
152
|
+
prfDeclaredAtCreate,
|
|
153
|
+
create: Object.freeze(create),
|
|
154
|
+
assert: Object.freeze(assert),
|
|
155
|
+
prfWorks,
|
|
156
|
+
summary: describe({ prfWorks, prfDeclaredAtCreate, create, assert, providerName }),
|
|
157
|
+
credentialId,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/** Turns the raw findings into one sentence naming the likely culprit. */
|
|
161
|
+
function describe(facts) {
|
|
162
|
+
const who = facts.providerName ? `${facts.providerName} ` : 'This provider ';
|
|
163
|
+
if (facts.create.error)
|
|
164
|
+
return `The passkey could not be created: ${facts.create.error}`;
|
|
165
|
+
if (facts.prfWorks)
|
|
166
|
+
return `${who}supports PRF — identity derivation will work with this passkey.`;
|
|
167
|
+
if (facts.assert.error)
|
|
168
|
+
return `The assertion failed before PRF could be evaluated: ${facts.assert.error}`;
|
|
169
|
+
const sawPrfKey = facts.create.extensionResults !== null && 'prf' in facts.create.extensionResults;
|
|
170
|
+
if (!sawPrfKey) {
|
|
171
|
+
return `${who}returned no \`prf\` entry at all, which usually means the extension was dropped before reaching the authenticator — the provider handling the ceremony does not implement PRF.`;
|
|
172
|
+
}
|
|
173
|
+
if (facts.prfDeclaredAtCreate === false) {
|
|
174
|
+
return `${who}explicitly reported PRF as unavailable for this credential (\`prf.enabled: false\`).`;
|
|
175
|
+
}
|
|
176
|
+
return `${who}acknowledged the PRF extension but evaluated no secret, so the credential has no hmac-secret to derive from.`;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=passkey-diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passkey-diagnostics.js","sourceRoot":"","sources":["../../src/identity/passkey-diagnostics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module recovery-code
|
|
3
|
+
* A PRF-free way to anchor an identity: 128 bits of entropy the user can write
|
|
4
|
+
* down, type on another device, and derive the exact same root key from.
|
|
5
|
+
*
|
|
6
|
+
* Encoded in Crockford base32 — no padding, no ambiguity between 0/O or 1/I/L,
|
|
7
|
+
* and case-insensitive, so it survives being copied by hand.
|
|
8
|
+
*/
|
|
9
|
+
/** How many bytes a seed is. Anything else cannot be written as a code. */
|
|
10
|
+
export declare const RECOVERY_SEED_BYTES = 16;
|
|
11
|
+
/**
|
|
12
|
+
* Generates a fresh seed.
|
|
13
|
+
* @returns 128 bits of entropy, ready to derive an identity from
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateSeed(): Uint8Array;
|
|
16
|
+
/**
|
|
17
|
+
* Writes a seed out as a code a person can copy.
|
|
18
|
+
*
|
|
19
|
+
* The inverse of {@link recoveryCodeToSeed} — which matters once the seed is
|
|
20
|
+
* kept wrapped rather than written down, because showing the user their code
|
|
21
|
+
* then means encoding a seed that already exists rather than minting one.
|
|
22
|
+
*
|
|
23
|
+
* @param seed The seed bytes
|
|
24
|
+
* @returns A grouped code such as `K7M2-9QPX-...`
|
|
25
|
+
*/
|
|
26
|
+
export declare function seedToRecoveryCode(seed: Uint8Array): string;
|
|
27
|
+
/**
|
|
28
|
+
* Generates a fresh recovery code.
|
|
29
|
+
* @returns A grouped code such as `K7M2-9QPX-...`
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateRecoveryCode(): string;
|
|
32
|
+
/**
|
|
33
|
+
* Strips formatting and resolves look-alike characters.
|
|
34
|
+
* @param code A code as typed by a user
|
|
35
|
+
* @returns The canonical, ungrouped form
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeRecoveryCode(code: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether a code could decode to a seed.
|
|
40
|
+
* @param code A code as typed by a user
|
|
41
|
+
* @returns Whether it is well formed
|
|
42
|
+
*/
|
|
43
|
+
export declare function isValidRecoveryCode(code: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Decodes a recovery code into the seed bytes it stands for.
|
|
46
|
+
* @param code A code as typed by a user
|
|
47
|
+
* @returns The seed, ready for key derivation
|
|
48
|
+
*/
|
|
49
|
+
export declare function recoveryCodeToSeed(code: string): Uint8Array;
|
|
50
|
+
//# sourceMappingURL=recovery-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-code.d.ts","sourceRoot":"","sources":["../../src/identity/recovery-code.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,KAAa,CAAC;AAE9C;;;GAGG;AACH,wBAAgB,YAAY,IAAI,UAAU,CAEzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK3D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAqB3D"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module recovery-code
|
|
3
|
+
* A PRF-free way to anchor an identity: 128 bits of entropy the user can write
|
|
4
|
+
* down, type on another device, and derive the exact same root key from.
|
|
5
|
+
*
|
|
6
|
+
* Encoded in Crockford base32 — no padding, no ambiguity between 0/O or 1/I/L,
|
|
7
|
+
* and case-insensitive, so it survives being copied by hand.
|
|
8
|
+
*/
|
|
9
|
+
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
10
|
+
/** Crockford's substitutions for characters people confuse when transcribing. */
|
|
11
|
+
const SUBSTITUTIONS = { O: '0', I: '1', L: '1', U: 'V' };
|
|
12
|
+
/** 128 bits: 16 bytes, 26 base32 characters, shown in groups of four. */
|
|
13
|
+
const SEED_BYTES = 16;
|
|
14
|
+
/** How many bytes a seed is. Anything else cannot be written as a code. */
|
|
15
|
+
export const RECOVERY_SEED_BYTES = SEED_BYTES;
|
|
16
|
+
/**
|
|
17
|
+
* Generates a fresh seed.
|
|
18
|
+
* @returns 128 bits of entropy, ready to derive an identity from
|
|
19
|
+
*/
|
|
20
|
+
export function generateSeed() {
|
|
21
|
+
return globalThis.crypto.getRandomValues(new Uint8Array(SEED_BYTES));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Writes a seed out as a code a person can copy.
|
|
25
|
+
*
|
|
26
|
+
* The inverse of {@link recoveryCodeToSeed} — which matters once the seed is
|
|
27
|
+
* kept wrapped rather than written down, because showing the user their code
|
|
28
|
+
* then means encoding a seed that already exists rather than minting one.
|
|
29
|
+
*
|
|
30
|
+
* @param seed The seed bytes
|
|
31
|
+
* @returns A grouped code such as `K7M2-9QPX-...`
|
|
32
|
+
*/
|
|
33
|
+
export function seedToRecoveryCode(seed) {
|
|
34
|
+
if (seed.length !== SEED_BYTES) {
|
|
35
|
+
throw new Error(`A recovery code carries exactly ${SEED_BYTES} bytes, not ${seed.length}.`);
|
|
36
|
+
}
|
|
37
|
+
return formatRecoveryCode(encodeBase32(seed));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generates a fresh recovery code.
|
|
41
|
+
* @returns A grouped code such as `K7M2-9QPX-...`
|
|
42
|
+
*/
|
|
43
|
+
export function generateRecoveryCode() {
|
|
44
|
+
return seedToRecoveryCode(generateSeed());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Strips formatting and resolves look-alike characters.
|
|
48
|
+
* @param code A code as typed by a user
|
|
49
|
+
* @returns The canonical, ungrouped form
|
|
50
|
+
*/
|
|
51
|
+
export function normalizeRecoveryCode(code) {
|
|
52
|
+
return code
|
|
53
|
+
.toUpperCase()
|
|
54
|
+
.replace(/[\s-]/g, '')
|
|
55
|
+
.split('')
|
|
56
|
+
.map((char) => SUBSTITUTIONS[char] ?? char)
|
|
57
|
+
.join('');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Checks whether a code could decode to a seed.
|
|
61
|
+
* @param code A code as typed by a user
|
|
62
|
+
* @returns Whether it is well formed
|
|
63
|
+
*/
|
|
64
|
+
export function isValidRecoveryCode(code) {
|
|
65
|
+
const normalized = normalizeRecoveryCode(code);
|
|
66
|
+
if (normalized.length !== Math.ceil((SEED_BYTES * 8) / 5))
|
|
67
|
+
return false;
|
|
68
|
+
return normalized.split('').every((char) => ALPHABET.includes(char));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Decodes a recovery code into the seed bytes it stands for.
|
|
72
|
+
* @param code A code as typed by a user
|
|
73
|
+
* @returns The seed, ready for key derivation
|
|
74
|
+
*/
|
|
75
|
+
export function recoveryCodeToSeed(code) {
|
|
76
|
+
const normalized = normalizeRecoveryCode(code);
|
|
77
|
+
if (!isValidRecoveryCode(code)) {
|
|
78
|
+
throw new Error('That recovery code is not valid — check for a missing or mistyped character.');
|
|
79
|
+
}
|
|
80
|
+
const bytes = new Uint8Array(SEED_BYTES);
|
|
81
|
+
let buffer = 0;
|
|
82
|
+
let bitsInBuffer = 0;
|
|
83
|
+
let index = 0;
|
|
84
|
+
for (const char of normalized) {
|
|
85
|
+
buffer = (buffer << 5) | ALPHABET.indexOf(char);
|
|
86
|
+
bitsInBuffer += 5;
|
|
87
|
+
if (bitsInBuffer >= 8 && index < SEED_BYTES) {
|
|
88
|
+
bitsInBuffer -= 8;
|
|
89
|
+
bytes[index++] = (buffer >> bitsInBuffer) & 0xff;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return bytes;
|
|
93
|
+
}
|
|
94
|
+
/** Groups a code into readable blocks of four. */
|
|
95
|
+
function formatRecoveryCode(code) {
|
|
96
|
+
return (code.match(/.{1,4}/g) ?? []).join('-');
|
|
97
|
+
}
|
|
98
|
+
/** Encodes bytes as Crockford base32, most significant bit first. */
|
|
99
|
+
function encodeBase32(bytes) {
|
|
100
|
+
let out = '';
|
|
101
|
+
let buffer = 0;
|
|
102
|
+
let bitsInBuffer = 0;
|
|
103
|
+
for (const byte of bytes) {
|
|
104
|
+
buffer = (buffer << 8) | byte;
|
|
105
|
+
bitsInBuffer += 8;
|
|
106
|
+
while (bitsInBuffer >= 5) {
|
|
107
|
+
bitsInBuffer -= 5;
|
|
108
|
+
out += ALPHABET[(buffer >> bitsInBuffer) & 0x1f];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (bitsInBuffer > 0) {
|
|
112
|
+
out += ALPHABET[(buffer << (5 - bitsInBuffer)) & 0x1f];
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=recovery-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-code.js","sourceRoot":"","sources":["../../src/identity/recovery-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module root-signer
|
|
3
|
+
* Who holds the identity key, and how a session gets permission from it.
|
|
4
|
+
*
|
|
5
|
+
* The root key signs exactly one thing: a note saying "this session key may
|
|
6
|
+
* write for me, until this time". Everything else is signed by the session key.
|
|
7
|
+
* That one signature is the only reason an app needs the identity at all — so
|
|
8
|
+
* it is the only thing that has to be abstracted for the key to live somewhere
|
|
9
|
+
* other than the page.
|
|
10
|
+
*
|
|
11
|
+
* Somewhere else means an account home in another window, or anything that
|
|
12
|
+
* will hold a secret and answer questions about it. The page asks for a delegation
|
|
13
|
+
* and gets back a token; the seed never crosses the boundary.
|
|
14
|
+
*
|
|
15
|
+
* Everything downstream — DIDs, expressions, validation, sync — is unchanged by
|
|
16
|
+
* this, because none of it ever sees the root key either.
|
|
17
|
+
*/
|
|
18
|
+
import type { CryptoProvider } from '../types.js';
|
|
19
|
+
import type { Capability, Fact, UCANToken } from './ucan.js';
|
|
20
|
+
/** What a session needs from an identity, wherever that identity is kept */
|
|
21
|
+
export interface RootSigner {
|
|
22
|
+
/** The identity being acted for */
|
|
23
|
+
readonly did: string;
|
|
24
|
+
/**
|
|
25
|
+
* Where the key lives. Worth showing: "your key is in your account home" and
|
|
26
|
+
* "your key is in this tab" are different promises to make to someone.
|
|
27
|
+
*/
|
|
28
|
+
readonly custody: 'local' | 'remote';
|
|
29
|
+
/**
|
|
30
|
+
* Signs a delegation from the identity to a session key.
|
|
31
|
+
*
|
|
32
|
+
* @param params.audience The session key's DID
|
|
33
|
+
* @param params.capabilities What it may do
|
|
34
|
+
* @param params.expiration Unix seconds after which the note is worthless
|
|
35
|
+
* @param params.facts Facts the note carries — that its key is an agent's, say
|
|
36
|
+
* @returns The signed token
|
|
37
|
+
*/
|
|
38
|
+
delegate(params: {
|
|
39
|
+
audience: string;
|
|
40
|
+
capabilities: ReadonlyArray<Capability>;
|
|
41
|
+
expiration: number;
|
|
42
|
+
facts?: ReadonlyArray<Fact>;
|
|
43
|
+
}): Promise<UCANToken>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A signer for a key this page is holding.
|
|
47
|
+
*
|
|
48
|
+
* The ordinary case: the seed was unlocked here, so the key is in memory and
|
|
49
|
+
* signing is a local operation.
|
|
50
|
+
*
|
|
51
|
+
* @param identity The unlocked identity
|
|
52
|
+
* @param provider Crypto provider for signing
|
|
53
|
+
* @returns A signer over it
|
|
54
|
+
*/
|
|
55
|
+
export declare function createLocalRootSigner(identity: {
|
|
56
|
+
did: string;
|
|
57
|
+
privateKey: CryptoKey;
|
|
58
|
+
}, provider: CryptoProvider): RootSigner;
|
|
59
|
+
//# sourceMappingURL=root-signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-signer.d.ts","sourceRoot":"","sources":["../../src/identity/root-signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG7D,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;QACxC,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;KAC7B,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,SAAS,CAAA;CAAE,EAChD,QAAQ,EAAE,cAAc,GACvB,UAAU,CAuBZ"}
|