{"_id":"@attomus/semafore-crypto","_rev":"3-33e682713f8af4e0aa9b18379df12bfe","name":"@attomus/semafore-crypto","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@attomus/semafore-crypto","version":"1.0.0","keywords":["semafore","x3dh","double-ratchet","e2e","encryption"],"author":{"name":"Attomus"},"license":"Apache-2.0","_id":"@attomus/semafore-crypto@1.0.0","maintainers":[{"name":"sampleford","email":"github@attomus.com"}],"homepage":"https://github.com/Attomus/semafore-crypto#readme","bugs":{"url":"https://github.com/Attomus/semafore-crypto/issues"},"dist":{"shasum":"a04b6f69e32f0c8eb42b2285335a5dafebf95692","tarball":"https://registry.npmjs.org/@attomus/semafore-crypto/-/semafore-crypto-1.0.0.tgz","fileCount":10,"integrity":"sha512-PXiPZBmgx4vRlvYDM+yOBGvNujAKG6aGT2AEXvkIywhtPrzOPgVy3v0lJN0ssQWRkw6JzpoVMPaURUm2QeisGQ==","signatures":[{"sig":"MEUCICwr8GQD5218M86tgrC7IshtE7K1ER92l8tBojT6Pc0XAiEA3J7VGGTHSZz/ghyGw/RiW/VZAhirNwBAMdaxoA0eNss=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":170505},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"bfd8fe6977eb907de22cdd56b045664236660e5a","scripts":{"docs":"typedoc src/index.ts","lint":"eslint .","test":"vitest run","build":"tsup","check":"tsc --noEmit","verify":"npm run lint && npm run check && npm run test && npm run build && npm audit --audit-level=high","prepack":"npm run verify"},"_npmUser":{"name":"sampleford","email":"github@attomus.com"},"repository":{"url":"git+https://github.com/Attomus/semafore-crypto.git","type":"git"},"_npmVersion":"11.12.1","description":"TypeScript implementation of SemaFore cryptographic wire-format primitives","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@noble/curves":"^2.2.0","@noble/hashes":"^2.2.0","@noble/ciphers":"^2.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^10.4.0","vitest":"^4.1.6","typedoc":"^0.28.19","prettier":"^3.8.3","@eslint/js":"^10.0.1","fast-check":"^4.8.0","typescript":"^6.0.3","@types/node":"^25.9.0","typescript-eslint":"^8.59.3"},"_npmOperationalInternal":{"tmp":"tmp/semafore-crypto_1.0.0_1779901670590_0.35789262275169476","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@attomus/semafore-crypto","version":"1.0.1","keywords":["semafore","x3dh","double-ratchet","e2e","encryption"],"author":{"name":"Attomus"},"license":"Apache-2.0","_id":"@attomus/semafore-crypto@1.0.1","maintainers":[{"name":"sampleford","email":"github@attomus.com"}],"homepage":"https://github.com/Attomus/semafore-crypto#readme","bugs":{"url":"https://github.com/Attomus/semafore-crypto/issues"},"dist":{"shasum":"9f042f0a80f9a7386e73485eea4e655a6e424f22","tarball":"https://registry.npmjs.org/@attomus/semafore-crypto/-/semafore-crypto-1.0.1.tgz","fileCount":10,"integrity":"sha512-DBw5L81tvXGbGZMEuWDzS5382cNHFRBHKgaPqWxCXhcm3IYkQvM2GOemMJ6GWRJNbAF4ufS/+4SYj9pCS/Q7Ww==","signatures":[{"sig":"MEUCICF4haS+Ab3q5S0cDWC826mU5+xJWk4ctsfwZZRWssHFAiEAqTZOQv85DffcJAM6KQovNMK9DRbjmrPOriUxmNHc+/o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@attomus%2fsemafore-crypto@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":170505},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"0a549c565e7d5a56a7ded75d63bcd2b3aa687691","scripts":{"docs":"typedoc src/index.ts","lint":"eslint .","test":"vitest run","build":"tsup","check":"tsc --noEmit","verify":"npm run lint && npm run check && npm run test && npm run build && npm audit --audit-level=high","prepack":"npm run verify"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb8c35c3-2a5a-4a1a-bd2a-61ce5867f209"}},"repository":{"url":"git+https://github.com/Attomus/semafore-crypto.git","type":"git"},"_npmVersion":"11.12.1","description":"TypeScript implementation of SemaFore cryptographic wire-format primitives","directories":{},"_nodeVersion":"24.15.0","dependencies":{"@noble/curves":"^2.2.0","@noble/hashes":"^2.2.0","@noble/ciphers":"^2.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^10.4.0","vitest":"^4.1.6","typedoc":"^0.28.19","prettier":"^3.8.3","@eslint/js":"^10.0.1","fast-check":"^4.8.0","typescript":"^6.0.3","@types/node":"^25.9.0","typescript-eslint":"^8.59.3"},"_npmOperationalInternal":{"tmp":"tmp/semafore-crypto_1.0.1_1779913078847_0.951713730479087","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"_id":"@attomus/semafore-crypto@1.0.2","bugs":{"url":"https://github.com/Attomus/semafore-crypto/issues"},"dist":{"shasum":"db7f367e54765e63f7ca33921cf19a1eb155d7b8","tarball":"https://registry.npmjs.org/@attomus/semafore-crypto/-/semafore-crypto-1.0.2.tgz","fileCount":10,"integrity":"sha512-RRhcXza84SEnNhITc653RBaZrbx3JqPE9mM9sLiAOthPekeGouRy1HqtLXIJjEd7/jHmVAhrp2q9kTzvLDdTlQ==","signatures":[{"sig":"MEUCIQCgVfgP+Spm7d1FmMlc/gOrSioVzP2H0X2v61K+BSfA7AIgc/nAv3EPmuWpsrRd1mZ/Lbw4hoaDV+NWH6GnZSAmCec=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCxWvLPy+K38Maavi7B1lt3z1/Zac1KaRgJokLuhOZnMwIhAOTEg0w1OOyfEhBo7MAZhfYpFLvTCA4+q7mQ6/Qz73Im"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@attomus%2fsemafore-crypto@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":171348},"main":"./dist/index.cjs","name":"@attomus/semafore-crypto","type":"module","types":"./dist/index.d.ts","author":{"name":"Attomus"},"module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"a666a412f9466b1071cafcfd3be2dfd357c0fe19","license":"Apache-2.0","scripts":{"docs":"typedoc src/index.ts","lint":"eslint .","test":"vitest run","build":"tsup","check":"tsc --noEmit","verify":"npm run lint && npm run check && npm run test && npm run build && npm audit --audit-level=high","prepack":"npm run verify"},"version":"1.0.2","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bb8c35c3-2a5a-4a1a-bd2a-61ce5867f209"}},"homepage":"https://github.com/Attomus/semafore-crypto#readme","keywords":["semafore","x3dh","double-ratchet","e2e","encryption"],"repository":{"url":"git+https://github.com/Attomus/semafore-crypto.git","type":"git"},"_npmVersion":"11.19.0","description":"TypeScript implementation of SemaFore cryptographic wire-format primitives","directories":{},"maintainers":[{"name":"sampleford","email":"github@attomus.com"}],"_nodeVersion":"24.20.0","dependencies":{"@noble/curves":"^2.2.0","@noble/hashes":"^2.2.0","@noble/ciphers":"^2.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^10.4.0","vitest":"^4.1.6","typedoc":"^0.28.19","prettier":"^3.8.3","@eslint/js":"^10.0.1","fast-check":"^4.8.0","typescript":"^6.0.3","@types/node":"^25.9.0","typescript-eslint":"^8.59.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/semafore-crypto_1.0.2_1788462816316_0.23573998407267105"}}},"time":{"created":"2026-05-27T17:07:50.393Z","modified":"2026-09-03T19:13:36.859Z","1.0.0":"2026-05-27T17:07:50.841Z","1.0.1":"2026-05-27T20:17:59.012Z","1.0.2":"2026-09-03T19:13:36.460Z"},"bugs":{"url":"https://github.com/Attomus/semafore-crypto/issues"},"author":{"name":"Attomus"},"license":"Apache-2.0","homepage":"https://github.com/Attomus/semafore-crypto#readme","keywords":["semafore","x3dh","double-ratchet","e2e","encryption"],"repository":{"url":"git+https://github.com/Attomus/semafore-crypto.git","type":"git"},"description":"TypeScript implementation of SemaFore cryptographic wire-format primitives","maintainers":[{"name":"sampleford","email":"github@attomus.com"}],"readme":"# @attomus/semafore-crypto\n\nTypeScript implementation of SemaFore's end-to-end encrypted messaging wire\nformat.\n\nThis package is for integration runtimes that need to encrypt SemaFore message\ncontent before it leaves the caller-controlled environment. It contains the\ncryptographic and wire-format layer only: no network calls, no service-token\nhandling, no storage, and no aggregation.\n\n## Status\n\nVersion `1.0.1` is the current release. The cryptographic wire-format surface\nis covered by byte-level conformance vectors, but the surrounding SemaFore\nintegration surface is still in active development while the GitHub Action\nmoves toward Marketplace readiness.\n\nImplemented today:\n\n- X25519 identity and ratchet keys\n- Ed25519 signed-prekey validation\n- AES-256-GCM payload encryption\n- HKDF-SHA256 key derivation\n- X3DH sender and receiver bootstrap helpers\n- SMX1 first-contact envelopes\n- SMD1 Double Ratchet follow-up messages\n- bounded skipped-message-key handling\n- byte-level conformance tests for DR-v1 and X3DH/SMX1 vectors\n\nThe implementation is wire-compatible with the current SemaFore iOS Swift and\nAndroid Kotlin clients for the checked-in conformance vectors, including\nOPK-present and OPK-absent SMX1 cases.\n\n## Install\n\nAfter the npm release is published:\n\n```sh\nnpm install @attomus/semafore-crypto\n```\n\n## Quick Start\n\n```ts\nimport {\n  generateIdentityKeyPair,\n  generateEd25519KeyPair,\n  generateSignedPrekey,\n  generateOneTimePrekey,\n  initReceiverSession,\n  initSenderSession,\n  encryptMessage,\n  decryptMessage\n} from '@attomus/semafore-crypto';\n\nconst decode = (bytes: Uint8Array) => new TextDecoder().decode(bytes);\n\n// Long-lived identity keys: X25519 for ECDH, Ed25519 for signatures.\nconst aliceIdentity = generateIdentityKeyPair();\nconst bobIdentity = generateIdentityKeyPair();\nconst bobSigning = generateEd25519KeyPair();\n\n// Bob publishes a signed prekey and one-time prekeys. Alice fetches them as a bundle.\nconst bobSpk = generateSignedPrekey(bobSigning.secretKey, 'spk-current');\nconst bobOpk = generateOneTimePrekey('opk-001');\n\nconst aliceSession = initSenderSession({\n  localIdentity: aliceIdentity,\n  recipientBundle: {\n    identityAgreementKey: bobIdentity.publicKey,\n    identitySigningKey: bobSigning.publicKey,\n    signedPrekey: bobSpk,\n    oneTimePrekey: bobOpk\n  }\n});\n\n// First message: carries the X3DH bootstrap. Later messages on this session\n// use the Double Ratchet continuation path.\nconst firstEnvelope = encryptMessage(aliceSession, 'Hello SemaFore');\n\n// Bob's prekey lookups typically hit on-device storage; stubbed here.\nconst { session: bobSession } = initReceiverSession({\n  localIdentity: bobIdentity,\n  peerIdentityPublicKey: aliceIdentity.publicKey,\n  envelope: firstEnvelope,\n  signedPrekeyLookup: () => bobSpk,\n  oneTimePrekeyLookup: () => bobOpk\n});\n\nconst plaintext = decryptMessage(bobSession, firstEnvelope);\nconsole.log(decode(plaintext)); // 'Hello SemaFore'\n\n// Second message: same session, now continuing under Double Ratchet.\nconst secondEnvelope = encryptMessage(aliceSession, 'and again');\nconsole.log(decode(decryptMessage(bobSession, secondEnvelope))); // 'and again'\n```\n\n## Wire Formats\n\nSemaFore currently uses two message envelope formats:\n\n- **SMX1**: first-contact X3DH prekey envelope.\n- **SMD1**: Double Ratchet message envelope after session bootstrap.\n\nThe wire layout is documented in [docs/wire-format.md](./docs/wire-format.md).\nChanging either format is a breaking protocol change.\n\n## Conformance\n\nThe test suite includes pinned SemaFore vectors:\n\n- `dr-v1-interop.json`\n- `x3dh-prekey-v1.json`\n\nThe X3DH/SMX1 vectors were extracted from the Android implementation and cover\nboth one-time-prekey-present and one-time-prekey-absent first-contact flows.\n\nRun the full local check with:\n\n```sh\nnpm run verify\n```\n\n## Security Model\n\n- Message plaintext is encrypted before it leaves the caller's runtime.\n- AES-256-GCM uses a fresh random 12-byte nonce for each encryption.\n- X25519, Ed25519, HKDF-SHA256, and AES-GCM are implemented using the\n  `@noble/*` packages.\n- Callers own key storage, ratchet-state persistence, service-token handling,\n  recipient lookup, and transport.\n- Private keys and ratchet state must not be stored in plaintext by callers.\n\n## Responsible Disclosure\n\nPlease report security issues privately. See [SECURITY.md](./SECURITY.md).\n\n## Maintainer Releases\n\nReleases publish through npm Trusted Publishing from the GitHub-hosted\n[`ci.yml`](./.github/workflows/ci.yml) workflow. No long-lived npm write token\nis required.\n\n1. Update `package.json` and `package-lock.json` to the intended version and\n   merge the verified change to `main`.\n2. Create a `vX.Y.Z` tag on that exact `main` commit, matching the package\n   version exactly, and push the tag.\n3. Confirm the tag-triggered CI test and publish jobs pass, then verify the new\n   version and provenance on npm.\n\nThe workflow refuses a mismatched tag or a version that already exists on npm.\nPublished package versions are immutable. If a release is defective, deprecate\nthat version with `npm deprecate @attomus/semafore-crypto@X.Y.Z \"reason\"`, fix\nthe issue, increment the version, and publish the replacement.\n\n## License\n\nApache-2.0.\n","readmeFilename":"README.md"}