{"id":31192248,"url":"https://github.com/defucc/hashkeys","last_synced_at":"2026-01-20T17:03:28.465Z","repository":{"id":312910816,"uuid":"1049047258","full_name":"DeFUCC/hashkeys","owner":"DeFUCC","description":"Reactive Noble cryptography for p2p identity","archived":false,"fork":false,"pushed_at":"2025-12-21T07:51:11.000Z","size":1164,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-31T09:58:20.761Z","etag":null,"topics":["composable","cryptography","encryption","js","noble-curves","signing","vue"],"latest_commit_sha":null,"homepage":"http://hashkeys.js.org/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DeFUCC.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-02T12:11:58.000Z","updated_at":"2025-12-21T07:51:15.000Z","dependencies_parsed_at":"2025-09-02T20:17:51.171Z","dependency_job_id":"52896027-d9df-4878-bdf5-0144d21ce4ed","html_url":"https://github.com/DeFUCC/hashkeys","commit_stats":null,"previous_names":["defucc/hashkeys"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/DeFUCC/hashkeys","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DeFUCC%2Fhashkeys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DeFUCC%2Fhashkeys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DeFUCC%2Fhashkeys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DeFUCC%2Fhashkeys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DeFUCC","download_url":"https://codeload.github.com/DeFUCC/hashkeys/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DeFUCC%2Fhashkeys/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["composable","cryptography","encryption","js","noble-curves","signing","vue"],"created_at":"2025-09-19T23:47:57.446Z","updated_at":"2026-01-20T17:03:28.453Z","avatar_url":"https://github.com/DeFUCC.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HashKeys\n\nReactive Noble cryptography for local‑first apps and p2p identity. `hashkeys` exposes a Vue 3 composable `useAuth()` that returns a reactive object running all cryptography in a Web Worker and provides a simple API for:\n\n- Authentication from a passphrase, a bech32 master key or Shamir shares\n- Identity and public keys\n- Sign/verify\n- Symmetric and end‑to‑end encryption\n- HKDF key derivation\n- PassKeys (WebAuthn) helper flows\n- Session persistence\n\n---\n\n## Install\n\n- Peer dependency: Vue 3\n- Modern bundler (Vite recommended)\n\n```bash\nnpm i hashkeys\n# or\npnpm add hashkeys\n```\n\n---\n\n## Quick start (Vue 3)\n\n```vue\n\u003cscript setup\u003e\nimport { useAuth } from 'hashkeys';\n\nconst auth = useAuth();\n\nasync function onLogin() {\n  try {\n    // Pass a strong passphrase OR a bech32 master key (hkmk…)\n    await auth.login('correct horse battery staple');\n    console.log('identity', auth.identity); \n  } catch (e) {\n    console.error(e);\n  }\n}\n\nasync function doSign() {\n  const { signature, publicKey } = await auth.send('sign',{ message: 'hello world' });\n  console.log(signature, publicKey);\n}\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cdiv\u003e\n    \u003cbutton @click=\"onLogin\"\u003eLogin\u003c/button\u003e\n    \u003cp v-if=\"auth.loading\"\u003eLoading…\u003c/p\u003e\n    \u003cp v-else\u003eAuthenticated: {{ auth.authenticated }}\u003c/p\u003e\n\n    \u003cbutton :disabled=\"!auth.authenticated\" @click=\"doSign\"\u003eSign message\u003c/button\u003e\n\n    \u003cdiv v-if=\"auth.error\" class=\"error\"\u003e{{ auth.error }}\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n---\n\n## API\n\nThe package exports a composable `useAuth()` (also as the default export) that returns a Vue `reactive` object with state and async methods that proxy to an internal Worker. The worker itself is also provided as `AuthWorker` for direct use.\n\n### Constructing\n\n- `useAuth(prefixOrOptions?)`\n  - `prefixOrOptions` can be:\n    - string: e.g. `'hk'`\n    - Vue Ref('string'): e.g. `ref('hk')`\n    - object: `{ prefix: 'hk' }` or `{ prefix: ref('hk') }`\n  - The prefix must be exactly 2 lowercase letters; invalid inputs fall back to `'hk'`.\n\n### State\n\n- `authenticated: boolean`\n- `loading: boolean`\n- `error: string | null`\n- `publicKey: string | null` — bech32 `hkpk…`\n- `identity: string | null` — bech32 `hkid…` (`SHA256(publicKey)`)\n- `encryptionKey: string | null` — bech32 `hkek…` (X25519 public key for E2E on ed25519)\n- `curve: 'ed25519' | 'secp256k1' | null`\n\n### Methods\n\n#### login(secret)\n\nAuthenticate using a passphrase or a bech32 master key.\n\nParameters:\n\n- `secret` — string. Either a strong passphrase or `hkmk…` bech32 master key.\n\nReturns: object with `authenticated: true` and current keys (`publicKey`, `identity`, optionally `encryptionKey`, `curve`).\n\n#### logout()\n\nEnd the session and clear in-memory state. Also clears the stored master key in session storage.\n\nReturns: object with `authenticated: false`.\n\n#### auth.send('sign',{ message })\n\nCreate a detached signature for the provided data.\n\nParameters:\n\n- `message` — string or Uint8Array. The data to sign.\n\nReturns: `{ signature, publicKey }` (bech32-encoded).\n\nRequires authentication.\n\n#### auth.send('verify',{ message, signature, publicKey })\n\nVerify a detached signature using the provided public key. Does not require authentication.\n\nParameters:\n\n- `message` — string or Uint8Array. The original message that was signed.\n- `signature` — bech32 `hksg…`. The signature to verify.\n- `publicKey` — bech32 `hkpk…`. The public key to verify against.\n\nReturns: `{ valid: boolean }`.\n\n#### auth.send('encrypt',{ data, recipientPublicKey, algorithm })\n\nEncrypt data. If `recipientPublicKey` is omitted, uses a symmetric key derived from your master key. If provided, performs E2E encryption (ed25519/X25519) to the recipient.\n\nParameters:\n\n- `data` — string or Uint8Array. The data to encrypt.\n- `recipientPublicKey` — optional bech32 `hkek…` (recipient's X25519 public key).\n- `algorithm` — optional string. Defaults to `xchacha20poly1305`. With a recipient on ed25519, the algorithm becomes `xchacha20poly1305-x25519`.\n\nReturns: `{ ciphertext, nonce, algorithm }` (nonce/ciphertext bech32-encoded).\n\nRequires authentication.\n\n#### auth.send('decrypt',{ ciphertext, nonce, senderPublicKey, algorithm })\n\nDecrypt data. For symmetric self-encryption, only `ciphertext` and `nonce` are required. For E2E, provide the sender's public key so the worker can derive the shared secret via ECDH (your private key never leaves the worker).\n\nParameters:\n\n- `ciphertext` — bech32 `hkct…`.\n- `nonce` — bech32 `hknc…`.\n- `senderPublicKey` — optional bech32 `hkek…` (sender's X25519 public key). Required for E2E.\n- `algorithm` — optional string. Usually the value returned by `encrypt()`.\n\nReturns: `{ decrypted, decryptedHex }`.\n\nRequires authentication.\n\n#### auth.send('derive-key',{ context, length })\n\nDerive application- or feature-specific key material using HKDF.\n\nParameters:\n\n- `context` — string. A namespace for the derivation (e.g., app or feature name).\n- `length` — number, optional. Bytes of key material to derive (default 32).\n\nReturns: `{ derivedKey, context, length }` (bech32-derived key material).\n\nRequires authentication.\n\n#### auth.send('get-identity')\n\nFetch identity information.\n\nReturns: `{ identity, publicKey, curve }`.\n\nRequires authentication.\n\n#### auth.send('get-public-key')\n\nFetch the current public key and associated metadata.\n\nReturns: `{ publicKey, identity, curve }`.\n\nRequires authentication.\n\n#### auth.send('get-private-key')\n\nExport the current bech32 master key.\n\nReturns: `hkmk…` string.\n\nRequires authentication.\n\n#### auth.send('get-split-key',{ shares, threshold })\n\nSplit the master key into multiple shares using Shamir's Secret Sharing. A subset of these shares (specified by `threshold`) is required to reconstruct the master key.\n\nParameters:\n- `shares` — number. Total number of shares to create.\n- `threshold` — number. Minimum number of shares required to reconstruct the master key.\n\nReturns: `Array\u003cstring\u003e` of bech32-encoded shares (prefixed with `hksh1…`).\n\nRequires authentication.\n\n#### auth.send('combine-key',{ shares })\n\nCombine Shamir shares to reconstruct the master key. The number of shares provided must be at least equal to the threshold used when splitting.\n\nParameters:\n- `shares` — Array of bech32-encoded shares (each prefixed with `hksh1…`).\n\nReturns: `hkmk…` string (bech32-encoded master key).\n\nRequires authentication.\n\n#### recall()\n\nAttempt to read a stored bech32 master key from `sessionStorage` and log in automatically.\n\nReturns: `true` if a stored key was found and a login attempt was made, otherwise `false`.\n\n#### clearError()\n\nReset the `error` field to `null`.\n\n#### passKeyAuth(name)\n\nCreate/register a WebAuthn credential (PassKey) for the given user name and log in using the generated credential ID (encoded as `hkwa…`).\n\nParameters:\n\n- `name` — string (user handle).\n\nReturns: boolean indicating whether login was initiated.\n\n#### passKeyLogin()\n\nPrompt for an existing PassKey and log in using its credential ID (encoded as `hkwa…`).\n\nReturns: boolean indicating whether login was initiated.\n\nNotes:\n\n- All methods throw if not authenticated, except `verify`, which operates on provided public inputs.\n- PassKeys helpers encode the WebAuthn `rawId` with Bech32 HRP `hkwa` and use it as the login secret. The worker derives keys from whatever string you pass to `login()`; `hkwa…` is simply a recognizable wrapper for the credential ID.\n\n### Authentication with Shamir Shares\n\nYou can now authenticate using Shamir shares instead of a passphrase or master key. Simply paste one or more shares (one per line) into the login field. The system will automatically detect and combine valid shares.\n\nExample:\n```js\n// Split the master key into 3 shares, requiring 2 to reconstruct\nconst shares = await auth.send('get-split-key',{ shares: 3, threshold: 2 });\n// shares = ['hksh1...', 'hksh1...', 'hksh1...']\n\n// Later, authenticate with at least 2 shares\nawait auth.login('hksh1...\\nhksh1...');\n// or with all 3 shares\nawait auth.login(shares.join('\\n'));\n```\n\n---\n\n## Bech32 prefixes\n\nShort, readable Bech32 encodings are used with app prefix `hk` + tag:\n\n- `nsec1…` private key\n- `npub1…` public/verify key\n- `note1…` identity (`SHA256(publicKey)`)\n- `sig1…` signature\n- `nc1…` nonce\n- `ct1…` ciphertext\n- `npd1…` derived key material\n- `webauthn1…` WebAuthn credential ID (used as a login secret by helpers)\n- `share1…` Shamir share\n\n---\n\n## Session persistence\n\nWhen you authenticate, hashkeys will fetch the bech32 private key (`nsec…`) from the worker and store it in `sessionStorage` under `privateKey`. When you logout or the page is refreshed without recalling, it is cleared. Use `auth.recall()` at startup to restore the session if a key is present.\n\n- Uses `sessionStorage`, so the key persists for the lifetime of the browser tab/window only.\n- The stored value is the bech32-encoded master key, not raw bytes.\n- Calling `auth.logout()` clears the stored key.\n\n---\n\n## Examples\n\n### Password to identity\n\n```js\nimport { useAuth } from 'hashkeys'\nconst auth = useAuth('hk')\nawait auth.login('correct horse battery staple');\nconsole.log(auth.identity); // note1…\n```\n\n### End‑to‑end encrypt to a recipient\n\n```js\nconst { ciphertext, nonce, algorithm } = await auth.send('encrypt',{ \n  data: 'hi', \n  recipientPublicKey: 'hkek1…',\n  algorithm: 'xchacha20poly1305'\n});\n// send {ciphertext, nonce, algorithm} to recipient\n```\n\n### Decrypt from a sender\n\n```js\nconst { decrypted } = await auth.send('decrypt',{ \n  ciphertext: 'hkct1…', \n  nonce: 'hknc1…', \n  senderPublicKey: 'hkek1…', \n  algorithm: 'xchacha20poly1305-x25519' \n});\n```\n\n### Verify someone else’s signature (no login required)\n\n```js\nconst { valid } = await auth.send('verify',{ \n  message: 'msg', \n  signature: 'hksg1…', \n  publicKey: 'hkpk1…' \n});\n```\n\n### PassKeys (WebAuthn)\n\n```js\n// Create/register a new PassKey for a username and login\nawait auth.passKeyAuth('alice@example.com');\nconsole.log(auth.identity); // hkid…\n\n// Use an existing PassKey to login\nawait auth.passKeyLogin();\n```\n\n### Multiple instances (local peer / Alice)\n\n```js\nimport { useAuth } from 'hashkeys'\nconst me = useAuth('hk')\nconst alice = useAuth('hk')\n\nawait me.login('correct horse battery staple')\nawait alice.login('alice-secret')\n\n// You -\u003e Alice\nconst env = await me.send('encrypt',{ \n  data: 'hi Alice', \n  recipientPublicKey: alice.encryptionKey \n})\nconst { decrypted } = await alice.send('decrypt',{ \n  ciphertext: env.ciphertext, \n  nonce: env.nonce, \n  senderPublicKey: me.encryptionKey, \n  algorithm: env.algorithm \n})\n```\n\n### Session persistence (auto-recall)\n\n```vue\n\u003cscript setup\u003e\nimport { onMounted } from 'vue'\nimport { useAuth } from 'hashkeys'\n\nconst auth = useAuth('hk')\n\nonMounted(() =\u003e {\n  auth.recall()\n})\n\u003c/script\u003e\n```\n\n---\n\n## Minimal HTML example\n\nThis mirrors `public/test.html` but targets consumers of the package. Uses Vue's `watch` to react to auth changes.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\" /\u003e\n    \u003cscript type=\"importmap\"\u003e{ \"imports\": { \"vue\": \"https://esm.sh/vue\", \"hashkeys\": \"https://esm.sh/hashkeys\" } }\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"app\"\u003e\n      \u003cinput id=\"input\" placeholder=\"passphrase\" /\u003e\n      \u003cbutton id=\"login\" disabled\u003eLOGIN\u003c/button\u003e\n      \u003cbutton id=\"get-key\" disabled\u003eGET KEY\u003c/button\u003e\n      \u003cpre id=\"id\"\u003e\u003c/pre\u003e\n      \u003cpre id=\"master\"\u003e\u003c/pre\u003e\n    \u003c/div\u003e\n    \u003cscript type=\"module\"\u003e\n      import { watch } from 'vue';\n      import { useAuth } from 'hashkeys';\n\n      const auth = useAuth('ex');\n\n      document.getElementById('get-key').addEventListener('click', async () =\u003e {\n        document.getElementById('master').textContent = await auth.send('get-private-key');\n      });\n\n      document.getElementById('input').addEventListener('input', (e) =\u003e {\n        document.getElementById('login').disabled = !e.target.value;\n      });\n\n      document.getElementById('login').addEventListener('click', () =\u003e {\n        auth.login(document.getElementById('input').value);\n      });\n\n      watch(auth, ({ authenticated, identity }) =\u003e {\n        if (authenticated) {\n          document.getElementById('id').textContent = identity;\n          document.getElementById('get-key').disabled = false;\n        }\n      });\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n---\n\n## Environment\n\n- Built and tested with Vite + Vue 3; the Worker is bundled for package consumers.\n- Works in modern browsers with Web Worker support.\n- Avoid persisting raw key bytes; if you must export, prefer the bech32 forms.\n\n---\n\n## License\n\nMIT (c) 2025 davay42\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefucc%2Fhashkeys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdefucc%2Fhashkeys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdefucc%2Fhashkeys/lists"}