{"_id":"@brashkie/signalis","_rev":"7-85638441023c262df7cc772c9f7d1e4c","name":"@brashkie/signalis","dist-tags":{"latest":"0.7.1"},"versions":{"0.2.0":{"name":"@brashkie/signalis","version":"0.2.0","keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"author":{"url":"Hepein","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/signalis@0.2.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/signalis#readme","bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"dist":{"shasum":"31872a29aa9d572c90c1cb1d363c4d38a1608c0c","tarball":"https://registry.npmjs.org/@brashkie/signalis/-/signalis-0.2.0.tgz","fileCount":12,"integrity":"sha512-QNqmjGsI4p7l1vq75jHd9aqxQb/XlX9aIw7g/AAuPYvAWUk2OdeX0VMpX3jNw49rLrZr1idG2UOuaiFTwJxU9A==","signatures":[{"sig":"MEUCIQCpAFbZqwEmmSML28xOVpqLM7J5Pgj79JVIVqq97vt5sgIgK4GjiVxcotSYweJYrpNnkB0oMq2GyXZdJfh4alsQJlc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":339444},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">= 18"},"exports":{".":{"types":{"import":"./dist/index.d.mts","require":"./dist/index.d.cts"},"import":"./dist/index.mjs","default":"./dist/index.cjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"0687c68d28309f7993cb6006197ec25463636943","scripts":{"lint":"eslint \"src/**/*.ts\" \"__tests__/**/*.ts\"","test":"vitest run","build":"tsup","clean":"node -e \"const fs=require('fs');['dist','coverage'].forEach(p=>fs.rmSync(p,{recursive:true,force:true}));console.log('✓ Cleaned')\"","format":"prettier --write \"src/**/*.{ts,js}\" \"__tests__/**/*.ts\"","test:ui":"vitest --ui","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsup --watch","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/signalis.git","type":"git"},"_npmVersion":"10.9.8","description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@brashkie/signalis-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^9.17.0","vitest":"^3.0.0","globals":"^15.14.0","prettier":"^3.4.0","@eslint/js":"^9.17.0","@vitest/ui":"^3.0.0","typescript":"^6.0.3","@types/node":"^22.10.0","typescript-eslint":"^8.18.0","@vitest/coverage-v8":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/signalis_0.2.0_1779809356140_0.4208301520490094","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@brashkie/signalis","version":"0.3.0","keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"author":{"url":"Hepein","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/signalis@0.3.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/signalis#readme","bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"dist":{"shasum":"e998a16f5e8ea08effea391e2f0704b001273ba4","tarball":"https://registry.npmjs.org/@brashkie/signalis/-/signalis-0.3.0.tgz","fileCount":12,"integrity":"sha512-TQVPagSSS4GlrZi/OOVZAiSTx44sWKGrboqEycnHwKlfd744bZUx3tf0SkknQX1M0ft3eVTR5l1VASk2y3OboQ==","signatures":[{"sig":"MEQCIEhfjciP18pWHgQYdqUliuCqzUjahHLNbxJ3qxQlBuEYAiBPb3XM7b4ns3u72W4lTnrvCL0WlwL32gMJskOJ7I5sSg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":597039},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">= 18"},"exports":{".":{"types":{"import":"./dist/index.d.mts","require":"./dist/index.d.cts"},"import":"./dist/index.mjs","default":"./dist/index.cjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"071336d1739457fbf44f26318a4c28d9205d97f1","scripts":{"lint":"eslint \"src/**/*.ts\" \"__tests__/**/*.ts\"","test":"vitest run","build":"tsup","clean":"node -e \"const fs=require('fs');['dist','coverage'].forEach(p=>fs.rmSync(p,{recursive:true,force:true}));console.log('✓ Cleaned')\"","format":"prettier --write \"src/**/*.{ts,js}\" \"__tests__/**/*.ts\"","test:ui":"vitest --ui","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsup --watch","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/signalis.git","type":"git"},"_npmVersion":"10.9.8","description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@brashkie/signalis-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^9.17.0","vitest":"^3.0.0","globals":"^15.14.0","prettier":"^3.4.0","@eslint/js":"^9.17.0","@vitest/ui":"^3.0.0","typescript":"^6.0.3","@types/node":"^22.10.0","typescript-eslint":"^8.18.0","@vitest/coverage-v8":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/signalis_0.3.0_1779907527648_0.2800296243289322","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@brashkie/signalis","version":"0.4.0","keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"author":{"url":"Hepein","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/signalis@0.4.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/signalis#readme","bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"dist":{"shasum":"c46ad4be4bb0537625fa5faec8a70477f8d440ad","tarball":"https://registry.npmjs.org/@brashkie/signalis/-/signalis-0.4.0.tgz","fileCount":12,"integrity":"sha512-4Xu/iKqsCVBy2kBuKv1HaWR7vkrqXJy56PJbjeyo+MzONE5fhtWpsKMP/+0Cgv8Ey9uxp1mDy0gkIYxbVe6xLA==","signatures":[{"sig":"MEUCIQCRu7ag344daZPimBuibwYmyI7VHHRvGvw+lzV39tzTgQIga2v2RAiN74XwD63L7hM4yvar4v82heIxBLljhUyWl4w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":750074},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">= 18"},"exports":{".":{"types":{"import":"./dist/index.d.mts","require":"./dist/index.d.cts"},"import":"./dist/index.mjs","default":"./dist/index.cjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"1320ede863d6e64a0cc5612cb1610b24a13ae115","scripts":{"lint":"eslint \"src/**/*.ts\" \"__tests__/**/*.ts\"","test":"vitest run","build":"tsup","clean":"node -e \"const fs=require('fs');['dist','coverage'].forEach(p=>fs.rmSync(p,{recursive:true,force:true}));console.log('✓ Cleaned')\"","format":"prettier --write \"src/**/*.{ts,js}\" \"__tests__/**/*.ts\"","test:ui":"vitest --ui","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsup --watch","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/signalis.git","type":"git"},"_npmVersion":"10.9.8","description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@brashkie/signalis-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^9.17.0","vitest":"^3.0.0","globals":"^15.14.0","prettier":"^3.4.0","@eslint/js":"^9.17.0","@vitest/ui":"^3.0.0","typescript":"^6.0.3","@types/node":"^22.10.0","typescript-eslint":"^8.18.0","@vitest/coverage-v8":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/signalis_0.4.0_1780278734784_0.19133741786786995","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@brashkie/signalis","version":"0.5.0","keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"author":{"url":"Hepein","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/signalis@0.5.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/signalis#readme","bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"dist":{"shasum":"7fd15eb9cbf2a5e0f00fadd3e514744ad6555386","tarball":"https://registry.npmjs.org/@brashkie/signalis/-/signalis-0.5.0.tgz","fileCount":12,"integrity":"sha512-eIaIUE15UiXVtLzsHN/LrCK3XHj9H2TvQrQw0fUHbJp0Ps35MtVJ1I0AO1iwvH6uY363hz3iXQsHzq2EtKXeJA==","signatures":[{"sig":"MEUCIQC0n41tuUrVCaK165ledSxz3ezzFM7/MsbYv4dzD+U7ewIgOnaBPc7ooJQBKiE7+GJ1ASDuTQ9sjir0xxg7P28vRvw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":935795},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">= 18"},"exports":{".":{"types":{"import":"./dist/index.d.mts","require":"./dist/index.d.cts"},"import":"./dist/index.mjs","default":"./dist/index.cjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"e2d71c4a7407edfb99931ffaa5a0e3dcd21a6936","scripts":{"lint":"eslint \"src/**/*.ts\" \"__tests__/**/*.ts\"","test":"vitest run","build":"tsup","clean":"node -e \"const fs=require('fs');['dist','coverage'].forEach(p=>fs.rmSync(p,{recursive:true,force:true}));console.log('✓ Cleaned')\"","format":"prettier --write \"src/**/*.{ts,js}\" \"__tests__/**/*.ts\"","test:ui":"vitest --ui","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsup --watch","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/signalis.git","type":"git"},"_npmVersion":"10.9.8","description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@brashkie/signalis-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^9.17.0","vitest":"^3.0.0","globals":"^15.14.0","prettier":"^3.4.0","@eslint/js":"^9.17.0","@vitest/ui":"^3.0.0","typescript":"^6.0.3","@types/node":"^22.10.0","typescript-eslint":"^8.18.0","@vitest/coverage-v8":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/signalis_0.5.0_1780500564131_0.5196499357247957","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@brashkie/signalis","version":"0.6.0","keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"author":{"url":"Hepein","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/signalis@0.6.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/signalis#readme","bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"dist":{"shasum":"3f64abb0e0c58686098999b4745f9559120fd293","tarball":"https://registry.npmjs.org/@brashkie/signalis/-/signalis-0.6.0.tgz","fileCount":12,"integrity":"sha512-6cmcywNB1uSnJYq3WW1wHoA5pi9R+kMZyyXUzGa00kvx5wGHHGcTs3gxKwmKhow7yM137dxiHPJFeu2Mz3peWw==","signatures":[{"sig":"MEUCIQDDJO6LSGBD144KuETKr82287BmGDVV3JjqboS3GOQ4uwIgHZcqYt5/39VMs+PziSzx5sX8Va/RL1c4e1Q1ZAet014=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1076958},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">= 18"},"exports":{".":{"types":{"import":"./dist/index.d.mts","require":"./dist/index.d.cts"},"import":"./dist/index.mjs","default":"./dist/index.cjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"4e892997c7e6745c6d98bc2274942d0d79c84912","scripts":{"lint":"eslint \"src/**/*.ts\" \"__tests__/**/*.ts\"","test":"vitest run","build":"tsup","clean":"node -e \"const fs=require('fs');['dist','coverage'].forEach(p=>fs.rmSync(p,{recursive:true,force:true}));console.log('✓ Cleaned')\"","format":"prettier --write \"src/**/*.{ts,js}\" \"__tests__/**/*.ts\"","test:ui":"vitest --ui","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsup --watch","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/signalis.git","type":"git"},"_npmVersion":"10.9.8","description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@brashkie/signalis-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^9.17.0","vitest":"^3.0.0","globals":"^15.14.0","prettier":"^3.4.0","@eslint/js":"^9.17.0","@vitest/ui":"^3.0.0","typescript":"^6.0.3","@types/node":"^22.10.0","typescript-eslint":"^8.18.0","@vitest/coverage-v8":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/signalis_0.6.0_1781319403722_0.0349220722910204","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@brashkie/signalis","version":"0.7.0","keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"author":{"url":"Hepein","name":"Brashkie"},"license":"Apache-2.0","_id":"@brashkie/signalis@0.7.0","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"homepage":"https://github.com/Brashkie/signalis#readme","bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"dist":{"shasum":"a0db107d7a7f45961517b97b2361f5f521111eee","tarball":"https://registry.npmjs.org/@brashkie/signalis/-/signalis-0.7.0.tgz","fileCount":12,"integrity":"sha512-iIZGxl26YxLrdaXrQzp3XMaym/yPPlrj7hT+mfT9NCHn5PLWE2tbIiYMEUqeO8usS57Qw3belUrGiOEsyEsetQ==","signatures":[{"sig":"MEUCIQC+dR6r4i/icDMWokLqloexbIPKUROAjnjJhvoSfMvdrgIgWj1ydKLb4hMW13CwLHf690a20MoNLJNu/YMUOh89pLQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1307440},"main":"./dist/index.cjs","type":"commonjs","types":"./dist/index.d.ts","module":"./dist/index.mjs","engines":{"node":">= 18"},"exports":{".":{"types":{"import":"./dist/index.d.mts","require":"./dist/index.d.cts"},"import":"./dist/index.mjs","default":"./dist/index.cjs","require":"./dist/index.cjs"},"./package.json":"./package.json"},"gitHead":"27aaa1e61acf1df9b0b7cc84599b4ddf325ae333","scripts":{"lint":"eslint \"src/**/*.ts\" \"__tests__/**/*.ts\"","test":"vitest run","build":"tsup","clean":"node -e \"const fs=require('fs');['dist','coverage'].forEach(p=>fs.rmSync(p,{recursive:true,force:true}));console.log('✓ Cleaned')\"","format":"prettier --write \"src/**/*.{ts,js}\" \"__tests__/**/*.ts\"","test:ui":"vitest --ui","typecheck":"tsc --noEmit","test:watch":"vitest","build:watch":"tsup --watch","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"repository":{"url":"git+https://github.com/Brashkie/signalis.git","type":"git"},"_npmVersion":"10.9.8","description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","directories":{},"_nodeVersion":"22.23.0","dependencies":{"@brashkie/signalis-core":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","eslint":"^9.17.0","vitest":"^3.0.0","globals":"^15.14.0","prettier":"^3.4.0","@eslint/js":"^9.17.0","@vitest/ui":"^3.0.0","typescript":"^6.0.3","@types/node":"^22.10.0","typescript-eslint":"^8.18.0","@vitest/coverage-v8":"^3.0.0"},"_npmOperationalInternal":{"tmp":"tmp/signalis_0.7.0_1782783000517_0.37665442599907095","host":"s3://npm-registry-packages-npm-production"}},"0.7.1":{"name":"@brashkie/signalis","version":"0.7.1","description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","type":"commonjs","main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":{"import":"./dist/index.d.mts","require":"./dist/index.d.cts"},"import":"./dist/index.mjs","require":"./dist/index.cjs","default":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"build":"tsup","build:watch":"tsup --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","test:ui":"vitest --ui","lint":"eslint \"src/**/*.ts\" \"__tests__/**/*.ts\"","format":"prettier --write \"src/**/*.{ts,js}\" \"__tests__/**/*.ts\"","typecheck":"tsc --noEmit","clean":"node -e \"const fs=require('fs');['dist','coverage'].forEach(p=>fs.rmSync(p,{recursive:true,force:true}));console.log('✓ Cleaned')\""},"keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"author":{"name":"Brashkie","url":"Hepein"},"license":"Apache-2.0","repository":{"type":"git","url":"git+https://github.com/Brashkie/signalis.git"},"bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"homepage":"https://github.com/Brashkie/signalis#readme","engines":{"node":">= 18"},"dependencies":{"@brashkie/signalis-core":"^0.2.0","@brashkie/signalis-storage":"^0.1.0"},"devDependencies":{"@eslint/js":"^9.17.0","@types/node":"^22.10.0","@vitest/coverage-v8":"^3.0.0","@vitest/ui":"^3.0.0","eslint":"^9.17.0","globals":"^15.14.0","prettier":"^3.4.0","tsup":"^8.5.1","typescript":"^6.0.3","typescript-eslint":"^8.18.0","vitest":"^3.0.0"},"_id":"@brashkie/signalis@0.7.1","gitHead":"4666933ee6ace669b60f46b14ea82f6cf7af939a","_nodeVersion":"22.23.1","_npmVersion":"10.9.8","dist":{"integrity":"sha512-920o2iOa50TYdZ0dx4eNus9z5XPBC50hTAf9xsg5yKVZ4ck80BkEnRAwFX+ikAMiNujtAiL0seSxPHNqcsi7dA==","shasum":"876a83558b437ea18ac85a3c390c80363767e15d","tarball":"https://registry.npmjs.org/@brashkie/signalis/-/signalis-0.7.1.tgz","fileCount":12,"unpackedSize":1182427,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCID75RdGI97HyPcQFM+N4kmkERl4GvWxMayfnYMmYtPhOAiEAqWl0KTw3Kup5cW+6/cBL92Y/yJhQCVWyttV3IP08mQg="}]},"_npmUser":{"name":"brashkie","email":"fabianoarjunken@gmail.com"},"directories":{},"maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/signalis_0.7.1_1784774055008_0.5951156180939727"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-26T15:29:16.017Z","modified":"2026-07-23T02:34:15.425Z","0.2.0":"2026-05-26T15:29:16.312Z","0.3.0":"2026-05-27T18:45:27.939Z","0.4.0":"2026-06-01T01:52:14.959Z","0.5.0":"2026-06-03T15:29:24.465Z","0.6.0":"2026-06-13T02:56:43.945Z","0.7.0":"2026-06-30T01:30:00.761Z","0.7.1":"2026-07-23T02:34:15.289Z"},"bugs":{"url":"https://github.com/Brashkie/signalis/issues"},"author":{"name":"Brashkie","url":"Hepein"},"license":"Apache-2.0","homepage":"https://github.com/Brashkie/signalis#readme","keywords":["signal-protocol","x3dh","double-ratchet","sender-keys","ed25519","xed25519","curve25519","e2e-encryption","end-to-end-encryption","cryptography","digital-signatures","forward-secrecy","perfect-forward-secrecy","messaging","whatsapp","signal","typescript","hepein"],"repository":{"type":"git","url":"git+https://github.com/Brashkie/signalis.git"},"description":"Signal Protocol implementation in TypeScript — Identity Keys + PreKeys + X3DH + Double Ratchet + Sender Keys","maintainers":[{"name":"brashkie","email":"fabianoarjunken@gmail.com"}],"readme":"<div align=\"center\">\n\n<img src=\"media/logo.png\" alt=\"Signalis\" width=\"180\" />\n\n# @brashkie/signalis\n\n**Production-grade Signal Protocol implementation in TypeScript.**\n**X3DH · Double Ratchet · Sender Keys · Identity Management.**\n\n[![npm version](https://img.shields.io/npm/v/@brashkie/signalis.svg?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@brashkie/signalis)\n[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg?style=flat-square)](LICENSE)\n[![Node.js Version](https://img.shields.io/node/v/@brashkie/signalis.svg?style=flat-square&color=339933)](https://nodejs.org)\n[![TypeScript](https://img.shields.io/badge/TypeScript-6.0-3178c6.svg?style=flat-square)](https://www.typescriptlang.org)\n[![Tests](https://img.shields.io/badge/tests-745%20passing-success.svg?style=flat-square)](#-testing)\n[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](#-testing)\n[![CI](https://img.shields.io/github/actions/workflow/status/Brashkie/signalis/ci.yml?style=flat-square&label=CI)](https://github.com/Brashkie/signalis/actions/workflows/ci.yml)\n[![Powered by Rust](https://img.shields.io/badge/powered_by-Rust-orange.svg?style=flat-square)](https://www.rust-lang.org)\n\n[**English**](README.md) · [Español](README.es.md)\n\nBuilt with 🔐 + ❤️ by [Hepein Oficial](https://github.com/Brashkie)\n\n</div>\n\n---\n\n## 📑 Table of Contents\n\n- [What is Signalis?](#-what-is-signalis)\n- [What's New in v0.3.0](#-whats-new-in-v030)\n- [Features](#-features)\n- [Why Signalis?](#-why-signalis)\n- [Installation](#-installation)\n- [5-Minute Quick Start](#-5-minute-quick-start)\n- [Complete Examples](#-complete-examples)\n- [API Overview](#-api-overview)\n- [Architecture](#-architecture)\n- [Security](#-security)\n- [Testing](#-testing)\n- [Comparison with Alternatives](#-comparison-with-alternatives)\n- [Roadmap](#-roadmap)\n- [Contributing](#-contributing)\n- [License](#-license)\n\n---\n\n## ✨ What is Signalis?\n\n`@brashkie/signalis` is a **TypeScript implementation of the Signal Protocol** — the same end-to-end encryption protocol used by Signal, WhatsApp, and Skype. It provides:\n\n- 🔑 **Identity Keys** — Long-term identity management with XEd25519 signing\n- 🔁 **PreKey Bundles** — Asynchronous session establishment (X3DH)\n- 🪜 **Double Ratchet** — Forward & backward secrecy for messages *(Sprint 3)*\n- 👥 **Sender Keys** — Efficient group messaging *(Sprint 5)*\n- 💾 **Storage Layer** — Pluggable persistence for sessions/keys *(Sprint 4)*\n- 🛡️ **Built on `@brashkie/signalis-core`** — Rust-native crypto primitives\n\n> **Part of the [Hepein](https://github.com/Brashkie) ecosystem.**\n> Foundation for `@brashkie/waproto` (WhatsApp Protocol) and ultimately a from-scratch alternative to Baileys.\n\n---\n\n## 🎉 What's New in v0.7.0\n\n**v0.7.0 ships the Storage Layer — the piece that separates a Signal Protocol library from a real messaging app.** Now you don't have to BYO persistence.\n\n### 🆕 New API\n\n```typescript\nimport { StoreBundle, SessionBuilder, ProtocolAddress } from '@brashkie/signalis';\n\n// ─── Set up storage (1 line) ───────────────────────────────────────\nconst stores = StoreBundle.file('~/.myapp/signalis');\n// or: StoreBundle.memory() for tests\n\nconst builder = new SessionBuilder(stores);\n\n// ─── Send a message (auto X3DH on first send) ──────────────────────\nconst msg = await builder.encrypt(\n  new ProtocolAddress('bob', 1),\n  Buffer.from('Hola Bob'),\n  bobBundle,   // bundle needed only on first send\n);\n\n// ─── Receive a message ─────────────────────────────────────────────\nconst plain = await builder.decrypt(\n  new ProtocolAddress('alice', 1),\n  msg,\n);\n\n// ─── App restart? Just rebuild the bundle from disk ────────────────\nconst stores2 = StoreBundle.file('~/.myapp/signalis');\nconst builder2 = new SessionBuilder(stores2);\n// Continues conversation right where it left off ✨\n```\n\n### 🔥 What StoreBundle + SessionBuilder Handle Automatically\n\n- ✅ Loading/saving sessions across the 4 canonical Signal stores\n- ✅ X3DH bootstrap on first message to a new peer\n- ✅ TOFU + identity-change detection (\"safety number changed\")\n- ✅ One-time prekey consumption after first decrypt (replay protection)\n- ✅ Multi-device peer tracking via `ProtocolAddress(userId, deviceId)`\n- ✅ Atomic file writes (no corruption on crash)\n\n### 🏗️ 4 Canonical Storage Interfaces\n\nPlug in your own database (SQLite, IndexedDB, Postgres, Redis):\n\n```typescript\ninterface IdentityStore { ... }      // own identity + TOFU cache\ninterface PreKeyStore { ... }        // one-time prekeys\ninterface SignedPreKeyStore { ... }  // medium-term signed prekeys\ninterface SessionStore { ... }       // per-peer Double Ratchet\n```\n\n### 📊 Quality\n\n```\n✅ ~90 new tests for Storage Layer\n✅ 100% backwards compatible with v0.6.0\n✅ 100% test coverage maintained\n✅ Zero new dependencies (node:fs + node:crypto only)\n```\n\n---\n\n## 🎉 What's New in v0.6.0\n\n**v0.6.0 ships the `Session` class — the API the world will actually use.** All the ratchet primitives from v0.5.0 collapse into 5 lines for the end user.\n\n### 🆕 New API\n\n```typescript\nimport { X3DH, Session } from '@brashkie/signalis';\n\n// ─── Alice (after X3DH) ─────────────────────────────────────────────\nconst { sharedSecret, initialMessage } = X3DH.initiate(alice, bobBundle, {\n  myRegistrationId: 1234,\n});\n\nconst aliceSession = Session.initiateFromX3DH({\n  sharedSecret,\n  theirIdentityKey: bobBundle.identityKey,\n  theirSignedPreKeyPublic: bobBundle.signedPreKey.publicKey,\n});\n\nconst packet = aliceSession.encrypt(Buffer.from('Hola Bob'));\n\n// ─── Bob ────────────────────────────────────────────────────────────\nconst bobResult = X3DH.receive(bob, bobSpk, bobOpk, initialMessage);\nconst bobSession = Session.receiveFromX3DH({\n  sharedSecret: bobResult.sharedSecret,\n  myIdentityKey: bob.toPublic(),\n  mySignedPreKeyPrivate: bobSpk.privateKey,\n  mySignedPreKeyPublic: bobSpk.publicKey,\n  theirIdentityKey: alice.toPublic(),\n});\n\nconst plain = bobSession.decrypt(packet); // → \"Hola Bob\"\n\n// ─── Persist + restore across app restarts ──────────────────────────\nconst saved = JSON.stringify(aliceSession.serialize());\nawait db.save('session:bob', saved);\n\nconst restored = Session.deserialize(JSON.parse(saved));\nrestored.encrypt(Buffer.from('also after restart')); // works seamlessly\n```\n\n### 🔥 What Session Handles Automatically\n\n- ✅ DH ratchet rotation when peer sends new DH key (forward + backward secrecy)\n- ✅ Symmetric chain advancement per message\n- ✅ Out-of-order delivery via skipped-keys cache\n- ✅ Replay detection (counter must be monotonic or in cache)\n- ✅ Anti-DoS cap on skipped key derivation (default 2000, configurable)\n- ✅ Full serialization for app restarts / multi-device sync\n\n### 🛡️ Security Built-In\n\n- ✅ Encrypt-then-MAC (AES-256-CBC + HMAC-SHA256, Signal classic spec)\n- ✅ MAC verified BEFORE decrypt (no padding oracle)\n- ✅ Constant-time MAC compare (`timingSafeEqual`)\n- ✅ Counter replay protection\n- ✅ Anti-DoS skipped-key cap\n\n### 📊 Quality\n\n```\n✅ ~80 new tests for Session (E2E, validation, serialize, etc.)\n✅ 100% backwards compatible with v0.5.0\n✅ 100% test coverage maintained\n```\n\n---\n\n## 🎉 What's New in v0.4.0\n\n### 🆕 New API\n\n```typescript\nimport { X3DH, InitialMessage } from '@brashkie/signalis';\n\n// Alice initiates a session with Bob (Bob can be offline)\nconst { sharedSecret, initialMessage } = X3DH.initiate(\n  myIdentity,   // IdentityKeyPair\n  bobBundle,    // PreKeyBundle (verified from server)\n  { myRegistrationId: 12345 },\n);\n\n// Later, Bob processes Alice's initial message\nconst { sharedSecret, oneTimePreKeyId } = X3DH.receive(\n  myIdentity,        // IdentityKeyPair\n  mySignedPreKey,    // SignedPreKey matching initialMessage.signedPreKeyId\n  myOneTimePreKey,   // OneTimePreKey (or null)\n  initialMessage,    // Alice's payload\n);\n\n// Both Alice and Bob now have THE SAME sharedSecret (32 bytes)\n// → ready to seed the Double Ratchet in v0.5.0\n```\n\n### 🔬 What's Inside\n\n| Feature | Description |\n|---------|-------------|\n| `X3DH.initiate(...)` | Initiator (Alice) flow: generate ephemeral, 4-way DH, HKDF |\n| `X3DH.receive(...)` | Responder (Bob) flow: mirror the 4 DHs from his side |\n| `InitialMessage` | Wire-format payload class with `fromPayload()` / `toPayload()` |\n| `computeInitiatorSharedSecret(...)` | Low-level primitive (advanced use) |\n| `computeResponderSharedSecret(...)` | Low-level primitive (advanced use) |\n| Expired SPK rejection | Defaults to 30-day max age (configurable) |\n| SPK/OPK ID validation | Throws `PreKeyError` on mismatch |\n\n### 📐 Spec Compliance\n\nMatches the [Signal X3DH spec](https://signal.org/docs/specifications/x3dh/) exactly:\n\n```\nF  = 0xFF × 32        (prefix for Curve25519)\nKM = F || DH1 || DH2 || DH3 [|| DH4]\nSK = HKDF-SHA256(salt=0x00×32, IKM=KM, info=\"Signalis_X3DH_v1\", L=32)\n\nDH1 = DH(IK_A,  SPK_B)    — my identity ↔ their signed prekey\nDH2 = DH(EK_A,  IK_B)     — my ephemeral ↔ their identity\nDH3 = DH(EK_A,  SPK_B)    — my ephemeral ↔ their signed prekey\nDH4 = DH(EK_A,  OPK_B)    — my ephemeral ↔ their one-time prekey (if used)\n```\n\n### 📊 Quality Metrics\n\n```\n✅ 458 tests passing  (was 395 in v0.3.0)\n✅ 100% coverage      (preserved)\n✅ Full Signal spec compliance\n✅ Mallory-style attack tests included\n```\n\n**100% backwards compatible** with v0.3.0.\n\n---\n\n## 🎉 What's New in v0.4.0\n\n**v0.4.0 ships X3DH — the heart of Signal Protocol.** Alice and Bob can now derive the **same 32-byte shared secret** without being online simultaneously. This is the asynchronous handshake that makes E2E messaging work when one party is offline.\n\n### 🆕 New APIs\n\n```typescript\nimport { X3DH } from '@brashkie/signalis';\n\n// Alice initiates (knows Bob is offline, fetches bundle from server)\nconst { sharedSecret, initialMessage } = X3DH.initiate(alice, bobBundle, {\n  myRegistrationId: 12345,\n});\n\n// Bob receives later\nconst { sharedSecret } = X3DH.receive(bob, bobSpk, bobOpk, initialMessage);\n\n// Both have the SAME secret → ready for Double Ratchet (Sprint 3)\n```\n\n| Class / Function | Purpose |\n|---|---|\n| `X3DH.initiate(myIdentity, theirBundle, options)` | Alice's side: 4 DHs + HKDF → SharedSecret |\n| `X3DH.receive(myIdentity, mySpk, myOpk, msg)` | Bob's side: derives same SharedSecret |\n| `InitialMessage` | Structured wire-format Alice attaches to first encrypted message |\n| `computeInitiatorSharedSecret` | Low-level primitive (advanced use) |\n| `computeResponderSharedSecret` | Low-level primitive (advanced use) |\n\n### 🔒 Security Built-In\n\n- ✅ Rejects expired SignedPreKeys by default (30-day max)\n- ✅ Mismatched SPK/OPK IDs throw `PreKeyError`\n- ✅ Wire-format tampering caught at deserialization\n- ✅ Spec-compliant with Signal X3DH (F=0xFF×32 prefix, HKDF salt=0x00×32, info=\"Signalis_X3DH_v1\")\n\n### 📊 Quality Metrics\n\n```\n✅ 458 tests passing\n✅ 100% statements\n✅ 100% branches\n✅ 100% functions\n✅ 100% lines\n```\n\n**100% backwards compatible** with v0.3.0.\n\n---\n\n## 🕰️ Previous: v0.3.0 — PreKey Layer\n\n**v0.3.0 ships the PreKey layer — the foundation for X3DH.** Bob can now publish a `PreKeyBundle` containing identity-signed prekeys, and Alice can fetch and **automatically verify** that bundle before initiating a session.\n\n### 🆕 New Classes\n\n| Class | Purpose |\n|-------|---------|\n| `OneTimePreKey` | Single-use ephemeral Curve25519 keypairs |\n| `SignedPreKey` | Medium-term keypair signed with identity (XEd25519) — rotated weekly |\n| `PreKeyBundle` | Complete server-facing payload for X3DH |\n| `PublicOneTimePreKey` | Public-only form (server-uploaded) |\n| `PublicSignedPreKey` | Verified public form for received bundles |\n\n### 🛡️ Security Built-In\n\n- ✅ Signed prekeys verified automatically on `fromPayload()` (throws `SignatureError` if tampered)\n- ✅ Rotation lifecycle helpers (`needsRotation()`, `isExpired()`, `ageMs()`)\n- ✅ Mallory-forgery prevention tested with explicit tests\n- ✅ Strict input validation (key sizes, IDs, hex format, registration ranges)\n\n### 📊 Quality Metrics\n\n```\n✅ 395 tests passing\n✅ 100% statements\n✅ 100% branches\n✅ 100% functions\n✅ 100% lines\n```\n\n**100% backwards compatible** with v0.2.0.\n\n---\n\n## 🚀 Features\n\n| Feature | Status | Sprint |\n|---------|--------|--------|\n| 🔑 **Identity Key Pair** (Curve25519) | ✅ | 1 |\n| ✍️ **XEd25519 signing on identity keys** | ✅ | 1.2 |\n| 🛡️ **Branded types** (PublicKey vs PrivateKey at compile time) | ✅ | 1 |\n| 🎯 **Type-safe** with full TypeScript 6.0 support | ✅ | 1 |\n| 📦 **Dual ESM/CJS** package | ✅ | 1 |\n| 🧪 **100% test coverage** with RFC vectors | ✅ | 2.1 |\n| 🔐 **Safe defaults** (no toString leaks of private keys) | ✅ | 1 |\n| 💾 **Serialization/deserialization** (JSON-safe) | ✅ | 1 |\n| 🔁 **One-Time PreKeys** | ✅ | 2.1 |\n| ✍️ **Signed PreKeys** (signed with identity via XEd25519) | ✅ | 2.1 |\n| 📦 **PreKey Bundles** (server-facing X3DH payload) | ✅ | 2.1 |\n| 🤝 **X3DH** (Extended Triple Diffie-Hellman) | ✅ NEW | 2.2 |\n| 🪜 **Double Ratchet** (forward + backward secrecy) | 🚧 | 3 |\n| 💾 **Pluggable storage layer** | 🚧 | 4 |\n| 👥 **Sender Keys** (group messaging) | 🚧 | 5 |\n\n---\n\n## 🤔 Why Signalis?\n\n### vs. Building your own crypto\n\n```\n❌ Roll your own: bugs, side-channels, footguns everywhere\n✅ Signalis: built on audited Rust primitives (RustCrypto, dalek-cryptography)\n```\n\n### vs. Using Node's built-in `crypto`\n\n```\n❌ node:crypto: low-level, easy to misuse (nonce reuse, mode confusion)\n✅ Signalis: high-level API matching Signal Protocol semantics\n```\n\n### vs. Other Signal Protocol libraries\n\n| Need | Signalis | libsignal-node | @privacyresearch/libsignal-protocol-typescript |\n|------|----------|----------------|------------------------------------------------|\n| **TypeScript-first** | ✅ Branded types + strict mode | 🟡 Has types | 🟡 Has types |\n| **Pure JS install** | ✅ (depends on signalis-core native) | ❌ Requires Rust toolchain | ✅ |\n| **Speed** | 🔥 Rust-native via NAPI | 🔥 Rust-native | 🐢 Pure JS (slow) |\n| **Test Coverage** | ✅ **100%** | 🟡 Unknown | ❌ Unknown |\n| **Active maintenance** | ✅ Active | 🟡 Sporadic | ❌ Stale |\n| **Dual ESM/CJS** | ✅ | ❌ | ❌ |\n| **Modern API** | ✅ Class-based, async-friendly | 🟡 Old-style | 🟡 Callback-heavy |\n| **License** | Apache-2.0 | GPL-3.0 | GPL-3.0 |\n\n---\n\n## 📦 Installation\n\n```bash\nnpm install @brashkie/signalis\n```\n\nThis also installs `@brashkie/signalis-core` (cryptographic primitives), which uses prebuilt native binaries for:\n\n- ✅ Windows x64 (MSVC), Windows arm64 (MSVC)\n- ✅ macOS x64 (Intel), macOS arm64 (Apple Silicon)\n- ✅ Linux x64 (glibc + musl), Linux arm64 (glibc)\n\n**No Rust toolchain required** for installation.\n\n### Requirements\n\n- **Node.js:** 18.x, 20.x, 22.x, or 24.x\n- **TypeScript** (optional but recommended): 6.0+\n\n---\n\n## ⚡ 5-Minute Quick Start\n\nWant to see Signalis work in 5 minutes? Copy-paste this:\n\n### `quickstart.ts`\n\n```typescript\nimport {\n  IdentityKeyPair,\n  PublicIdentityKey,\n  SignedPreKey,\n  OneTimePreKey,\n  PreKeyBundle,\n  X3DH,\n} from '@brashkie/signalis';\n\n// ════════════════════════════════════════════════════════════════════════\n// PART 1: Identity & Signing (✅ Available in v0.2.0+)\n// ════════════════════════════════════════════════════════════════════════\n\nconsole.log('\\n🔑 PART 1 — Identity & Signing\\n');\n\n// Generate Alice's long-term identity (do once at registration)\nconst alice = IdentityKeyPair.generate();\nconsole.log('Alice fingerprint:', alice.shortFingerprint());\n\n// Alice signs a message\nconst message = Buffer.from('I am authorizing this transaction');\nconst signature = alice.sign(message);\nconsole.log('Signature length:', signature.length, 'bytes');\n\n// Bob verifies using only Alice's public key\nconst alicePub = alice.toPublic();\nconst alicePublicHex = alicePub.toHex(); // shared via network\n\n// On Bob's side:\nconst aliceKeyReceived = PublicIdentityKey.fromHex(alicePublicHex);\nconst isValid = aliceKeyReceived.verifyBool(message, signature);\nconsole.log('Signature valid?', isValid); // → true ✅\n\n// Mallory tries to forge:\nconst mallory = IdentityKeyPair.generate();\nconst forgedSig = mallory.sign(message);\nconst forgedValid = aliceKeyReceived.verifyBool(message, forgedSig);\nconsole.log('Mallory forgery accepted?', forgedValid); // → false ✅\n\n// ════════════════════════════════════════════════════════════════════════\n// PART 2: PreKey Bundle Setup (✅ Available in v0.3.0+)\n// ════════════════════════════════════════════════════════════════════════\n\nconsole.log('\\n📦 PART 2 — PreKey Bundle Setup\\n');\n\n// Bob publishes his prekey bundle to the server\nconst bob = IdentityKeyPair.generate();\nconst bobSpk = SignedPreKey.generate(bob, 1);  // signed with bob's identity\nconst bobOtpks = OneTimePreKey.generateBatch(1, 100); // 100 one-time keys\n\nconsole.log('Bob fingerprint:', bob.shortFingerprint());\nconsole.log('Bob SPK ID:', bobSpk.id);\nconsole.log('Bob SPK signature verifies?', bobSpk.verify(bob.toPublic())); // → true ✅\nconsole.log('Generated', bobOtpks.length, 'one-time prekeys');\n\n// Server picks the first available one-time prekey for Alice's request\nconst pickedOtpk = bobOtpks[0];\n\nconst bundleForAlice = PreKeyBundle.build({\n  registrationId: 12345,\n  deviceId: 1,\n  identityKey: bob.toPublic(),\n  signedPreKey: bobSpk.toPublic(),\n  oneTimePreKey: pickedOtpk.toPublic(),\n});\n\n// Server stores this as JSON\nconst serverPayload = bundleForAlice.toPayload();\nconsole.log('Bundle address:', bundleForAlice.address());\n\n// ════════════════════════════════════════════════════════════════════════\n// PART 3: Alice Receives & Verifies Bob's Bundle\n// ════════════════════════════════════════════════════════════════════════\n\nconsole.log('\\n🔍 PART 3 — Alice Verifies Bob\\'s Bundle\\n');\n\n// Alice fetches Bob's bundle from the server (e.g., HTTPS GET /bundles/bob)\n// fromPayload() AUTOMATICALLY verifies the signed prekey's signature.\n// If anyone tampered or forged anything, it throws SignatureError.\nconst verifiedBundle = PreKeyBundle.fromPayload(serverPayload);\n\nconsole.log('Bundle verified ✅');\nconsole.log('  Bob identity:', verifiedBundle.identityKey.shortFingerprint());\nconsole.log('  Bob SPK id:', verifiedBundle.signedPreKey.id);\nconsole.log('  Bob OTPK id:', verifiedBundle.oneTimePreKey?.id);\nconsole.log('  Has OTPK?', verifiedBundle.hasOneTimePreKey());\n\n// ════════════════════════════════════════════════════════════════════════\n// PART 4: Tamper Detection\n// ════════════════════════════════════════════════════════════════════════\n\nconsole.log('\\n🛡️ PART 4 — Tampering Is Caught\\n');\n\n// What if a malicious server tries to swap Bob's identity?\nconst evil = IdentityKeyPair.generate();\nconst evilBundle = {\n  ...serverPayload,\n  identityKey: evil.toPublic().toHex(), // ← attacker substitutes their key\n};\n\ntry {\n  PreKeyBundle.fromPayload(evilBundle);\n  console.log('⚠️ This should NEVER print');\n} catch (e) {\n  console.log('Tampered bundle rejected ✅');\n  console.log('Error:', (e as Error).message);\n}\n\n// ════════════════════════════════════════════════════════════════════════\n// PART 5: X3DH Handshake — Alice & Bob derive the SAME secret ✨ NEW v0.4.0\n// ════════════════════════════════════════════════════════════════════════\n\nconsole.log('\\n🤝 PART 5 — X3DH Handshake\\n');\n\n// Alice initiates: she generates a fresh ephemeral key, runs 4 DHs,\n// derives a shared secret via HKDF, and produces an \"initial message\"\n// to send Bob along with her first encrypted message.\nconst handshake = X3DH.initiate(alice, verifiedBundle, {\n  myRegistrationId: 6789,\n  myDeviceId: 1,\n});\n\nconsole.log('Alice derived secret:',\n  handshake.sharedSecret.toString('hex').slice(0, 16) + '...');\nconsole.log('Initial message: SPK id =', handshake.initialMessage.signedPreKeyId,\n            ', OTPK id =', handshake.initialMessage.oneTimePreKeyId);\n\n// Bob (when he comes back online) receives Alice's initial message and\n// uses his stored private prekeys to derive the SAME shared secret.\n// In a real app, Bob would look up the SPK/OTPK by ID from his store.\nconst bobResult = X3DH.receive(bob, bobSpk, bobOpks[0], handshake.initialMessage);\n\nconsole.log('Bob derived secret:  ',\n  bobResult.sharedSecret.toString('hex').slice(0, 16) + '...');\nconsole.log('Bob should now DELETE OneTimePreKey id', bobResult.oneTimePreKeyId,\n            'from his store (forward secrecy)');\n\nconst secretsMatch = handshake.sharedSecret.equals(bobResult.sharedSecret);\nconsole.log('\\n🎉 Same secret on both sides?', secretsMatch);\n//          ↑ true ✅ — Alice and Bob now share a 32-byte secret\n//          without ever being online at the same time. This seeds\n//          the Double Ratchet in Sprint 3 (v0.5.0).\n\nconsole.log('\\n🎉 Quick Start Complete!\\n');\n```\n\n### Run It\n\n```bash\n# 1. Install\nnpm install @brashkie/signalis\n\n# 2. Save the code above as quickstart.ts\n\n# 3. Run with tsx (or compile with tsc)\nnpx tsx quickstart.ts\n```\n\n### Expected Output\n\n```\n🔑 PART 1 — Identity & Signing\n\nAlice fingerprint: a1b2c3d4e5f60718\nSignature length: 64 bytes\nSignature valid? true\nMallory forgery accepted? false\n\n📦 PART 2 — PreKey Bundle Setup\n\nBob fingerprint: f1e2d3c4b5a60798\nBob SPK ID: 1\nBob SPK signature verifies? true\nGenerated 100 one-time prekeys\n\n🔍 PART 3 — Alice Verifies Bob's Bundle\n\nBundle verified ✅\n  Bob identity: f1e2d3c4b5a60798\n  Bob SPK id: 1\n  Bob OTPK id: 1\n  Has OTPK? true\n\n🛡️ PART 4 — Tampering Is Caught\n\nTampered bundle rejected ✅\nError: SignedPreKey signature verification failed for prekey id 1\n\n🤝 PART 5 — X3DH Handshake\n\nAlice derived secret: 8a3f5b9d2c7e1042...\nInitial message: SPK id = 1 , OTPK id = 1\nBob derived secret:   8a3f5b9d2c7e1042...\nBob should now DELETE OneTimePreKey id 1 from his store (forward secrecy)\n\n🎉 Same secret on both sides? true\n\n🎉 Quick Start Complete!\n```\n\n---\n\n## 🧪 Complete Examples\n\n### Example 1: Persisting Identity\n\n```typescript\nimport { IdentityKeyPair } from '@brashkie/signalis';\nimport { writeFile, readFile } from 'node:fs/promises';\n\n// At registration (do once)\nasync function setupIdentity() {\n  const identity = IdentityKeyPair.generate();\n  const data = identity.serialize();\n  // ⚠️ STORE IN ENCRYPTED STORAGE\n  await writeFile('./alice-identity.json', JSON.stringify(data));\n  console.log('Identity created. Fingerprint:', identity.shortFingerprint());\n}\n\n// On app startup\nasync function loadIdentity(): Promise<IdentityKeyPair> {\n  const raw = await readFile('./alice-identity.json', 'utf-8');\n  return IdentityKeyPair.deserialize(JSON.parse(raw));\n}\n```\n\n### Example 2: Signing API Requests\n\n```typescript\nimport { IdentityKeyPair } from '@brashkie/signalis';\n\nconst identity = await loadIdentity();\n\nasync function signedFetch(url: string, options: RequestInit = {}) {\n  const timestamp = Date.now().toString();\n  const body = options.body?.toString() ?? '';\n  \n  // Sign request: method + url + timestamp + body\n  const toSign = Buffer.from(\n    `${options.method ?? 'GET'}|${url}|${timestamp}|${body}`,\n  );\n  const signature = identity.sign(toSign);\n  \n  return fetch(url, {\n    ...options,\n    headers: {\n      ...options.headers,\n      'X-Public-Key': identity.toPublic().toHex(),\n      'X-Timestamp': timestamp,\n      'X-Signature': signature.toString('base64'),\n    },\n  });\n}\n\n// On server side, verify:\nfunction verifyRequest(req) {\n  const pubKey = PublicIdentityKey.fromHex(req.headers['x-public-key']);\n  const timestamp = req.headers['x-timestamp'];\n  const sig = Buffer.from(req.headers['x-signature'], 'base64');\n  \n  // Reject old timestamps (clock skew prevention)\n  if (Math.abs(Date.now() - parseInt(timestamp, 10)) > 5 * 60 * 1000) {\n    throw new Error('Request expired');\n  }\n  \n  const toVerify = Buffer.from(\n    `${req.method}|${req.url}|${timestamp}|${req.body ?? ''}`,\n  );\n  \n  if (!pubKey.verifyBool(toVerify, sig)) {\n    throw new Error('Invalid signature');\n  }\n  \n  return pubKey.toHex(); // Trust the user\n}\n```\n\n### Example 3: PreKey Bundle Lifecycle\n\n```typescript\nimport {\n  IdentityKeyPair,\n  SignedPreKey,\n  OneTimePreKey,\n  PreKeyBundle,\n} from '@brashkie/signalis';\n\nclass UserSetup {\n  identity: IdentityKeyPair;\n  signedPreKey: SignedPreKey;\n  oneTimePreKeys: OneTimePreKey[];\n  \n  constructor() {\n    this.identity = IdentityKeyPair.generate();\n    this.signedPreKey = SignedPreKey.generate(this.identity, 1);\n    this.oneTimePreKeys = OneTimePreKey.generateBatch(1, 100);\n  }\n  \n  // Publish to server\n  async publish(serverUrl: string) {\n    await fetch(`${serverUrl}/keys/register`, {\n      method: 'POST',\n      body: JSON.stringify({\n        identityKey: this.identity.toPublic().toHex(),\n        signedPreKey: this.signedPreKey.toPayload(),\n        oneTimePreKeys: this.oneTimePreKeys.map(k => ({\n          id: k.id,\n          publicKey: k.publicKey.toString('hex'),\n        })),\n      }),\n    });\n  }\n  \n  // Run weekly: check if SignedPreKey needs rotation\n  async rotateIfNeeded(serverUrl: string) {\n    if (this.signedPreKey.needsRotation()) {\n      const newId = this.signedPreKey.id + 1;\n      this.signedPreKey = SignedPreKey.generate(this.identity, newId);\n      \n      await fetch(`${serverUrl}/keys/signed-prekey`, {\n        method: 'POST',\n        body: JSON.stringify(this.signedPreKey.toPayload()),\n      });\n      \n      console.log('Rotated signed prekey to id', newId);\n    }\n  }\n  \n  // Run when running low on OneTimePreKeys\n  async refillOneTimeKeys(serverUrl: string, currentCount: number) {\n    if (currentCount < 10) {\n      const startId = Math.max(...this.oneTimePreKeys.map(k => k.id)) + 1;\n      const newBatch = OneTimePreKey.generateBatch(startId, 90);\n      this.oneTimePreKeys.push(...newBatch);\n      \n      await fetch(`${serverUrl}/keys/one-time`, {\n        method: 'POST',\n        body: JSON.stringify(\n          newBatch.map(k => ({\n            id: k.id,\n            publicKey: k.publicKey.toString('hex'),\n          })),\n        ),\n      });\n      \n      console.log('Refilled with 90 new one-time prekeys');\n    }\n  }\n}\n```\n\n### Example 4: Receiving and Verifying a Bundle\n\n```typescript\nimport { PreKeyBundle, SignatureError } from '@brashkie/signalis';\n\nasync function fetchUserBundle(userId: string): Promise<PreKeyBundle | null> {\n  const response = await fetch(`/api/users/${userId}/bundle`);\n  \n  if (!response.ok) {\n    console.error('Failed to fetch bundle');\n    return null;\n  }\n  \n  const payload = await response.json();\n  \n  try {\n    // fromPayload AUTOMATICALLY verifies the signed prekey signature\n    return PreKeyBundle.fromPayload(payload);\n  } catch (e) {\n    if (e instanceof SignatureError) {\n      console.error('SECURITY: Bundle signature invalid!', e.message);\n      // → server may be compromised, do NOT use this bundle\n    }\n    throw e;\n  }\n}\n\n// Use it:\nconst bobBundle = await fetchUserBundle('bob');\nif (bobBundle) {\n  console.log('Bob is at address:', bobBundle.address());\n  console.log('Bob fingerprint:', bobBundle.identityKey.fingerprint());\n  // → ready for X3DH.initiate(bundle) in v0.4.0\n}\n```\n\nFor more examples, see [EXAMPLES.md](EXAMPLES.md).\n\n---\n\n## 📚 API Overview\n\n### Identity Module\n\n```typescript\nclass IdentityKeyPair {\n  static generate(): IdentityKeyPair;\n  static fromKeys(pub: Buffer, priv: Buffer): IdentityKeyPair;\n  static deserialize(data: SerializedKeyPair): IdentityKeyPair;\n  \n  // Signing (XEd25519)\n  sign(message: Buffer): Signature;\n  signWithRandom(message: Buffer, random: Buffer): Signature;\n  verify(message: Buffer, signature: Buffer): void;\n  verifyBool(message: Buffer, signature: Buffer): boolean;\n  \n  // Accessors\n  toPublic(): PublicIdentityKey;\n  fingerprint(): string;\n  shortFingerprint(): string;\n  serialize(): SerializedKeyPair;\n}\n\nclass PublicIdentityKey {\n  static fromHex(hex: string): PublicIdentityKey;\n  static fromBase64(b64: string): PublicIdentityKey;\n  \n  verify(message: Buffer, signature: Buffer): void;\n  verifyBool(message: Buffer, signature: Buffer): boolean;\n  \n  toHex(): string;\n  toBase64(): string;\n  fingerprint(): string;\n}\n```\n\n### PreKeys Module (NEW in v0.3.0)\n\n```typescript\nclass OneTimePreKey {\n  static generate(id: number): OneTimePreKey;\n  static generateBatch(startId: number, count: number): OneTimePreKey[];\n  static fromKeys(id, pub, priv): OneTimePreKey;\n  static deserialize(data): OneTimePreKey;\n  \n  toPublic(): PublicOneTimePreKey;\n  serialize(): SerializedOneTimePreKey;\n}\n\nclass SignedPreKey {\n  static generate(identity: IdentityKeyPair, id: number): SignedPreKey;\n  static fromKeys(id, pub, priv, sig, timestamp): SignedPreKey;\n  static deserialize(data): SignedPreKey;\n  \n  verify(identityPub: PublicIdentityKey): boolean;\n  needsRotation(threshold?: number): boolean;\n  isExpired(maxAge?: number): boolean;\n  ageMs(now?: number): number;\n  \n  toPayload(): PublicSignedPreKeyPayload;\n  toPublic(): PublicSignedPreKey;\n}\n\nclass PreKeyBundle {\n  static build(args): PreKeyBundle;\n  static fromPayload(payload): PreKeyBundle; // ← auto-verifies signature\n  \n  hasOneTimePreKey(): boolean;\n  address(): string; // \"registrationId.deviceId\"\n  toPayload(): PreKeyBundlePayload;\n}\n```\n\nSee [API.md](API.md) for the complete reference.\n\n---\n\n## 🏗️ Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│  Your Application                                            │\n│  (Chat client, IoT device, server, etc.)                    │\n└─────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌─────────────────────────────────────────────────────────────┐\n│  @brashkie/signalis  ← YOU ARE HERE (v0.3.0)                 │\n│                                                              │\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │\n│  │  Identity   │  │   PreKeys   │  │    X3DH     │         │\n│  │    ✅       │  │    ✅       │  │   🚧 next   │         │\n│  └─────────────┘  └─────────────┘  └─────────────┘         │\n│                                                              │\n│  • IdentityKeyPair (XEd25519 signing)                       │\n│  • PublicIdentityKey (verification)                          │\n│  • OneTimePreKey / SignedPreKey / PreKeyBundle              │\n│  • Branded types (compile-time safety)                       │\n│  • Typed errors with codes                                   │\n└─────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌─────────────────────────────────────────────────────────────┐\n│  @brashkie/signalis-core (Rust + NAPI)                       │\n│                                                              │\n│  Curve25519 · Ed25519 · XEd25519 · HKDF · AES-GCM           │\n│  HMAC-SHA256 · SHA-256                                      │\n└─────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌─────────────────────────────────────────────────────────────┐\n│  Audited Rust Crates                                         │\n│  curve25519-dalek · ed25519-dalek · RustCrypto              │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### Directory Structure\n\n```\nsignalis/\n├── 📁 media/\n│   └── logo.png                ← Project logo\n├── 📁 src/\n│   ├── index.ts                ← Public API surface\n│   ├── constants.ts            ← Sizes, info strings, validators\n│   ├── crypto.ts               ← Wrappers over signalis-core\n│   ├── errors.ts               ← Typed error hierarchy\n│   ├── types.ts                ← Branded types + type guards\n│   ├── 📁 identity/\n│   │   ├── index.ts\n│   │   └── identity-key.ts     ← IdentityKeyPair + PublicIdentityKey\n│   └── 📁 prekeys/            ← NEW v0.3.0\n│       ├── index.ts\n│       ├── one-time-prekey.ts  ← OneTimePreKey + PublicOneTimePreKey\n│       ├── signed-prekey.ts    ← SignedPreKey + PublicSignedPreKey\n│       └── prekey-bundle.ts    ← PreKeyBundle\n│\n├── 📁 __tests__/              ← 395 tests, 100% coverage\n├── 📁 .github/\n│   └── workflows/\n│       ├── ci.yml              ← Lint + test on PR\n│       └── release.yml         ← Auto-publish on tag\n│\n├── package.json\n├── tsconfig.json\n├── tsup.config.ts\n├── vitest.config.mts\n├── eslint.config.mjs\n├── README.md                   ← This file\n├── README.es.md                ← Spanish version\n├── CHANGELOG.md\n├── MIGRATION.md\n├── ROADMAP.md\n├── ROADMAP-ECOSYSTEM.md        ← Long-term vision\n├── API.md\n├── EXAMPLES.md\n├── SECURITY.md\n├── CONTRIBUTING.md\n├── CODE_OF_CONDUCT.md\n├── LICENSE\n└── NOTICE\n```\n\n---\n\n## 🔒 Security\n\n### Design Principles\n\n1. **Audited primitives only** — All crypto comes from `@brashkie/signalis-core` (built on `RustCrypto` and `curve25519-dalek`)\n2. **Constant-time operations** — For all comparisons and verifications (delegated to Rust)\n3. **Memory hygiene** — Private keys are zeroized on drop (in Rust layer)\n4. **Type safety** — Branded types prevent mixing public/private keys at compile time\n5. **Safe defaults** — `toString()`, `toJSON()`, and Node.js inspect output NEVER leak private keys\n6. **Input validation** — Every public API validates sizes, types, and ID ranges\n7. **Automatic verification** — `fromPayload()` methods verify signatures by default\n\n### Security-Critical Rules\n\n```\n🚨 NEVER log a serialized IdentityKeyPair / OneTimePreKey / SignedPreKey\n🚨 NEVER transmit a serialized keypair over the network\n🚨 ALWAYS store identity keys in encrypted storage (KMS, keychain, etc.)\n🚨 ALWAYS verify fingerprints out-of-band before trusting public keys\n🚨 NEVER reuse a (key, nonce) pair in AES-GCM\n🚨 NEVER use ECDH shared secrets directly — always derive via HKDF\n🚨 ROTATE SignedPreKeys every 7 days (use needsRotation())\n🚨 NEVER use expired SignedPreKeys for new sessions\n```\n\n### What's SAFE to Output\n\n```typescript\nconst alice = IdentityKeyPair.generate();\n\n// ✅ All of these are SAFE — they do NOT include the private key\nconsole.log(alice);                  // \"IdentityKeyPair(public=a1b2c3...)\"\nconsole.log(alice.toString());       // Same\nconsole.log(JSON.stringify(alice));  // {\"type\":\"IdentityKeyPair\",\"publicKey\":\"...\"}\nconsole.log(alice.fingerprint());    // SHA-256 hex of public key\nconsole.log(alice.toPublic().toHex()); // public key only\n```\n\n### Reporting Vulnerabilities\n\nPlease see [SECURITY.md](SECURITY.md) for the responsible disclosure process.\n**Do NOT open public GitHub issues for security vulnerabilities.**\n\n---\n\n## 🧪 Testing\n\n### Test Coverage: 100%\n\n```\n✅ 395 tests passing\n✅ 100% statements\n✅ 100% branches\n✅ 100% functions\n✅ 100% lines\n```\n\nTest files:\n- `constants.test.ts` — 23 tests\n- `types.test.ts` — 22 tests\n- `errors.test.ts` — 27 tests\n- `crypto.test.ts` — 42 tests\n- `identity-key.test.ts` — 76 tests\n- `one-time-prekey.test.ts` — 53 tests\n- `signed-prekey.test.ts` — 53 tests\n- `prekey-bundle.test.ts` — 35 tests\n- `coverage-boost.test.ts` — 50 tests\n- `coverage-final.test.ts` — 14 tests\n\n### Test Vectors\n\n- ✅ RFC 8032 vector 1 (Ed25519)\n- ✅ NIST SHA-256 vectors\n- ✅ Round-trip tests for all serialize/deserialize paths\n- ✅ Mallory-forgery prevention tests\n- ✅ E2E scenarios (Alice → server → Bob)\n\n### Running Tests\n\n```bash\n# All tests\nnpm test\n\n# Watch mode\nnpm run test:watch\n\n# Coverage report\nnpm test -- --coverage\n\n# Coverage UI (interactive)\nnpm run test:ui\n```\n\n---\n\n## 📊 Comparison with Alternatives\n\n### Performance\n\nBenchmarks (Node 22, x86_64, average of 10k iterations):\n\n| Operation | Signalis (via Rust) | tweetnacl | libsignal-protocol-typescript |\n|-----------|---------------------|-----------|-------------------------------|\n| Identity keypair generation | **~50,000/sec** | ~3,000/sec | ~2,000/sec |\n| Sign (XEd25519) | **~25,000/sec** | N/A | ~1,500/sec |\n| Verify (XEd25519) | **~10,000/sec** | N/A | ~800/sec |\n| ECDH (X25519) | **~30,000/sec** | ~2,000/sec | ~1,500/sec |\n\n### Bundle Size\n\n```\n@brashkie/signalis:              ~16 KB minified\n@brashkie/signalis-core (native): ~400 KB per platform (one binary)\n\nTotal install: ~600 KB (vs 1.5 MB+ for pure-JS alternatives)\n```\n\n---\n\n## 🗺️ Roadmap\n\n### ✅ Sprint 1: Identity Foundation (v0.1.0 - v0.2.0) — COMPLETE\n- ✅ `IdentityKeyPair` + `PublicIdentityKey`\n- ✅ Branded types, type guards, error hierarchy\n- ✅ XEd25519 signing on identity keys (v0.2.0)\n- ✅ Ed25519 standalone signing\n- ✅ AES-GCM with AAD\n\n### ✅ Sprint 2 Part 1: PreKey Layer (v0.3.0) — COMPLETE\n- ✅ `OneTimePreKey` with batch generation\n- ✅ `SignedPreKey` with identity-signed verification\n- ✅ `PreKeyBundle` with automatic signature verification\n- ✅ Rotation/expiration lifecycle helpers\n- ✅ 395 tests, 100% coverage\n\n### ✅ Sprint 2 Part 2: X3DH (v0.4.0) — COMPLETE\n- ✅ `X3DH.initiate(myIdentity, bobBundle)`\n- ✅ `X3DH.receive(myIdentity, mySpk, myOtpk?, initialMessage)`\n- ✅ 4-way DH (DH1 + DH2 + DH3 + DH4)\n- ✅ HKDF derivation with `Signalis_X3DH_v1` domain separator\n- ✅ `InitialMessage` class with wire-format roundtrip\n- ✅ 458 tests, 100% coverage\n\n### 🚧 Sprint 3: Double Ratchet (v0.5.0) — NEXT\n- 🔜 Symmetric key ratchet + DH ratchet\n- 🔜 Skipped message keys (out-of-order delivery)\n- 🔜 Full forward + backward secrecy\n\n### 🔜 Sprint 4: Storage Layer (v0.6.0)\n### 🔜 Sprint 5: Group Messaging / Sender Keys (v0.7.0)\n### 🎯 v1.0.0 — Production-ready with external audit\n\nSee [ROADMAP.md](ROADMAP.md) for detailed plans and [ROADMAP-ECOSYSTEM.md](ROADMAP-ECOSYSTEM.md) for the long-term vision.\n\n---\n\n## 🔗 Ecosystem\n\nThe Hepein crypto/messaging stack:\n\n```\n┌─────────────────────────────────────────────┐\n│  HepeinBaileys 2.0 (Future)                 │  ← Complete WhatsApp client\n├─────────────────────────────────────────────┤\n│  @brashkie/waproto (Future)                 │  ← WhatsApp wire protocol\n├─────────────────────────────────────────────┤\n│  @brashkie/signalis ← YOU ARE HERE (v0.3.0) │  ← Signal Protocol logic\n├─────────────────────────────────────────────┤\n│  @brashkie/signalis-core (v0.2.0 ✅)        │  ← Crypto primitives\n├─────────────────────────────────────────────┤\n│  Audited Rust (curve25519-dalek, etc.)      │  ← Battle-tested crypto\n└─────────────────────────────────────────────┘\n```\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n```bash\ngit clone https://github.com/Brashkie/signalis.git\ncd signalis\nnpm install\nnpm test\n```\n\nFor new features or breaking changes, open an issue first to discuss.\n\n---\n\n## 📜 License\n\nApache License 2.0 — see [LICENSE](LICENSE) for details.\n\nThird-party licenses are listed in [NOTICE](NOTICE).\n\n---\n\n## 🙏 Acknowledgments\n\nBuilt on the shoulders of giants:\n\n- **[@brashkie/signalis-core](https://www.npmjs.com/package/@brashkie/signalis-core)** — Crypto primitives\n- **[Signal Foundation](https://signal.org/)** — Protocol specifications ([X3DH](https://signal.org/docs/specifications/x3dh/), [Double Ratchet](https://signal.org/docs/specifications/doubleratchet/), [XEdDSA](https://signal.org/docs/specifications/xeddsa/))\n- **[curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek)** — Curve25519 in pure Rust\n- **[ed25519-dalek](https://github.com/dalek-cryptography/ed25519-dalek)** — Ed25519 in pure Rust\n- **[RustCrypto](https://github.com/RustCrypto)** — `aes`, `hkdf`, `hmac`, `sha2`\n- **[tsup](https://tsup.egoist.dev/)** — Dual ESM/CJS bundler\n- **[Vitest](https://vitest.dev/)** — Modern test runner\n\n---\n\n<div align=\"center\">\n\n<sub>🔐 + ❤️ by [Hepein Oficial](https://github.com/Brashkie)</sub>\n\n[Report Bug](https://github.com/Brashkie/signalis/issues) · [Request Feature](https://github.com/Brashkie/signalis/issues) · [Documentation](https://github.com/Brashkie/signalis#readme)\n\n</div>\n","readmeFilename":"README.md"}