{"id":31376521,"url":"https://github.com/substrate-system/frost","last_synced_at":"2026-01-20T16:58:21.742Z","repository":{"id":315832272,"uuid":"1056792342","full_name":"substrate-system/frost","owner":"substrate-system","description":"FROST in typescript","archived":false,"fork":false,"pushed_at":"2025-09-21T01:36:32.000Z","size":81,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-21T03:26:57.962Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/substrate-system.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-14T20:09:22.000Z","updated_at":"2025-09-21T01:36:35.000Z","dependencies_parsed_at":"2025-09-21T03:27:01.911Z","dependency_job_id":"b8ca2550-a59c-45a9-83c9-c88750a7180e","html_url":"https://github.com/substrate-system/frost","commit_stats":null,"previous_names":["substrate-system/frost"],"tags_count":1,"template":false,"template_full_name":"nichoth/template-ts-browser","purl":"pkg:github/substrate-system/frost","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/substrate-system%2Ffrost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/substrate-system%2Ffrost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/substrate-system%2Ffrost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/substrate-system%2Ffrost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/substrate-system","download_url":"https://codeload.github.com/substrate-system/frost/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/substrate-system%2Ffrost/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277322052,"owners_count":25798726,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-28T02:00:08.834Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2025-09-28T03:53:55.609Z","updated_at":"2026-01-20T16:58:21.734Z","avatar_url":"https://github.com/substrate-system.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FROST\n[![tests](https://img.shields.io/github/actions/workflow/status/substrate-system/frost/nodejs.yml?style=flat-square)](https://github.com/substrate-system/frost/actions/workflows/nodejs.yml)\n[![types](https://img.shields.io/npm/types/@substrate-system/frost?style=flat-square)](README.md)\n[![module](https://img.shields.io/badge/module-ESM%2FCJS-blue?style=flat-square)](README.md)\n[![semantic versioning](https://img.shields.io/badge/semver-2.0.0-blue?logo=semver\u0026style=flat-square)](https://semver.org/)\n[![Common Changelog](https://nichoth.github.io/badge/common-changelog.svg)](./CHANGELOG.md)\n[![install size](https://flat.badgen.net/packagephobia/install/@substrate-system/frost)](https://packagephobia.com/result?p=@substrate-system/frost?dummy=unused)\n[![gzip size](https://img.shields.io/bundlephobia/minzip/@substrate-system/frost?style=flat-square)](https://bundlephobia.com/@substrate-system/frost)\n[![license](https://img.shields.io/badge/license-Big_Time-blue?style=flat-square)](LICENSE)\n\n__Two use-cases__: [private key backup](#key-backup-and-recovery) and\n[threshold signatures](#distributed-threshold-signing).\n\nThis is a TypeScript implementation of the FROST threshold signature scheme as\nspecified in [RFC 9591](https://www.rfc-editor.org/rfc/rfc9591.html).\n\n\u003cdetails\u003e\u003csummary\u003e\u003ch2\u003eContents\u003c/h2\u003e\u003c/summary\u003e\n\n\u003c!-- toc --\u003e\n\n- [Installation](#installation)\n- [Example](#example)\n  * [Key Backup and Recovery](#key-backup-and-recovery)\n  * [Distributed Threshold Signing](#distributed-threshold-signing)\n- [Try it](#try-it)\n- [Test](#test)\n- [API](#api)\n  * [Key Backup](#key-backup)\n  * [Distributed Signing](#distributed-signing)\n- [Standards](#standards)\n- [See Also](#see-also)\n- [Internals](#internals)\n  * [Signing Protocol](#signing-protocol)\n- [Protocol Flow](#protocol-flow)\n  * [1. Key Generation (Setup)](#1-key-generation-setup)\n  * [2. Signing Ceremony](#2-signing-ceremony)\n- [Step-by-Step Guide](#step-by-step-guide)\n  * [Example](#example-1)\n\n\u003c!-- tocstop --\u003e\n\n\u003c/details\u003e\n\n-------\n\nFROST (Flexible Round-Optimized Schnorr Threshold signatures) is a threshold\nsignature scheme that allows a group of participants to collectively generate\nsignatures, requiring a minimum number of participants during\nthe signing process.\n\nThe [threshold signature](https://en.wikipedia.org/wiki/Threshold_cryptosystem)\n**reveals only that the threshold was met. It does not reveal _who_ signed**.\nIt is cryptographically impossible to determine which participants signed.\n\nA single private key gets split into multiple shards during setup.\nEach participant gets one shard of the key. The original private key can\nbe discarded/lost at this point.\n\nThe participants use their individual key shards to\ncollectively create signatures that are mathematically equivalent to what\nthe original private key would have produced, but the original private key\nitself is never reconstructed.\n\nEven after successful signing ceremonies, no single\nparticipant ever gains access to the complete private key. The threshold\nproperty is maintained permanently \u0026mdash; you always need the minimum number of\nparticipants to create future signatures.\n\n\n_Featuring:_\n\n- **Simple Key Backup**: Split any Ed25519 key with `split()`, recover\n  with `recover()`\n- **Easy Signing**: Sign with recovered keys using `sign()` - no\n  ceremony complexity\n- **Flexible Input**: Accepts CryptoKey, PKCS#8, or raw 32-byte keys\n- **Threshold Signatures**: Configurable m-of-n threshold signing for advanced\n  use cases\n- **RFC 9591 Compliant**: [See the doc](https://www.rfc-editor.org/rfc/rfc9591.html)\n\n## Installation\n\n```bash\nnpm i -S @substrate-system/frost\n```\n\n## Example\n\n### Key Backup and Recovery\n\nFROST can be used to backup an existing Ed25519 private key by splitting it\ninto threshold shares. This is useful for creating secure key storage where\nyou need multiple shares to recover the original key.\n\nIn [Dark Crystal](https://darkcrystal.pw/), for example, the intended use is\nto give the shards of your private key to several of your friends, using\nthe social graph to securely backup your key. But this works just as well\nby distributing your key shards amongst multiple of your own devices, in case\nyou lose one device.\n\n\n\u003e [!NOTE]  \n\u003e  We do not create a CryptoKey in `recover`.\n\nThe value returned by `recover()` is a **scalar**\n(the mathematical secret used in signing), not a seed. WebCrypto's `importKey`\nexpects a seed, which it then hashes with SHA-512 and bit-clamps to derive a\nscalar. Since we can't reverse this one-way process, we can't convert our\nrecovered scalar back into a CryptoKey. Instead, use the `sign()` function,\nwhich handles the FROST signing ceremony internally using the scalar\ndirectly. **Signatures from `sign()` will verify correctly with the**\n**original public key**.\n\n\n```ts\nimport { webcrypto } from '@substrate-system/one-webcrypto'\nimport {\n    createFrostConfig,\n    split,\n    recover,\n    sign\n} from '@substrate-system/frost'\n\n// 1. Generate or use existing Ed25519 keypair\nconst keyPair = await webcrypto.subtle.generateKey(\n    { name: 'Ed25519' },\n    true,  // extractable so we can split the private key\n    ['sign', 'verify']\n)\n\n// 2. Split into 3 shares (require 2 to recover)\nconst config = createFrostConfig(2, 3)\nconst { groupPublicKey, keyPackages } = await split(\n    keyPair.privateKey,\n    config\n)\n\n// 3. Distribute shares to different locations\n// - Share 1: USB drive in safe\n// - Share 2: Cloud backup (encrypted)\n// - Share 3: Paper backup\n\n// 4. Later, recover using any 2 of 3 shares\nconst availableShares = [keyPackages[0], keyPackages[2]]\nconst recoveredKey = recover(availableShares, config)\n\n// 5. Use the recovered key to sign\nconst message = new TextEncoder().encode('Important message')\nconst signature = await sign(recoveredKey, message, config)\n\n// 6. Verify the signature with the original public key\nconst isValid = await webcrypto.subtle.verify(\n    'Ed25519',\n    keyPair.publicKey,\n    signature,\n    message\n)\n```\n\n\u003e [!NOTE]  \n\u003e   - `split` accepts CryptoKey, Uint8Array (PKCS#8), or Uint8Array\n\u003e     (32-byte raw scalar)\n\u003e   - The recovered key will produce the same public key as the original\n\u003e   - You need at least the threshold number of shares to recover\n\u003e   - Different combinations of shares all recover the same key\n\u003e \n\n\n-------------\n\n\n### Distributed Threshold Signing\n\nCollaboratively sign a message. **The final signature reveals only that the**\n**threshold was met. It does not reveal _who_ signed**. It is cryptographically\nimpossible to determine which participants signed.\n\n```ts\nimport {\n  createFrostConfig,\n  generateKeys,\n  thresholdSign\n} from '@substrate-system/frost'\n\n// 1. Alice creates a 3-of-4 FROST setup\nconst config = createFrostConfig(3, 4)  // Need 3 out of 4 to sign\nconst { groupPublicKey, keyPackages } = generateKeys(config)\n\n// 2. Distribute key packages to participants\nconst [aliceKey, bobKey, carolKey, desmondKey] = keyPackages\n\n// 3. Later, any 3 participants can create a signature\nconst message = new TextEncoder().encode('Hello, FROST!')\nconst signature = await thresholdSign(\n    [bobKey, carolKey, desmondKey],  // Any 3 participants\n    message,\n    groupPublicKey,\n    config\n)\n\n// 4. Verify signature\nconst isValid = await crypto.subtle.verify(\n    'Ed25519',\n    new Uint8Array(groupPublicKey.point),\n    signature,\n    message\n)\n```\n\n## Try it\n\nRun the example locally.\n\n```bash\nnpm run example:node\n```\n\nThis will execute the complete example showing:\n1. Alice creating a 3-of-4 threshold keypair\n2. Getting key shares for Alice, Bob, Carol, and Desmond\n3. Using any 3 participants to create threshold signatures\n4. Verifying the signature is valid\n\n\n## Test\n\nRun the tests:\n\n```sh\nnpm test\n```\n\nStart the example:\n\n```sh\nnpm start\n```\n\n\n-------------------------------------------------------\n\n\n## API\n\n### Key Backup\n\n#### `createFrostConfig`\n\nCreates a FROST configuration with Ed25519 cipher suite.\n\n```ts\nfunction createFrostConfig (\n  minSigners: number,\n  maxSigners: number\n): FrostConfig\n```\n\n```ts\nconst config = createFrostConfig(2, 3)  // 2-of-3 threshold\n```\n\n#### `split`\n\n```ts\nasync function split (\n  privateKey: CryptoKey | Uint8Array,\n  config: FrostConfig\n): Promise\u003cSigners\u003e\n```\n\n```ts\nconst { groupPublicKey, keyPackages } = await split(keyPair.privateKey, config)\n```\n\n#### `recover`\n\nRecover the private key from threshold shares.\n\n```ts\nfunction recover (\n  keyPackages: KeyPackage[],\n  config: FrostConfig\n): Uint8Array\n```\n\n```ts\nconst recoveredKey = recover(keyPackages.slice(0, 2), config)\n```\n\n#### `sign`\n\nSign a message with a recovered key.\n\n```ts\nasync function sign (\n  recoveredKey:Uint8Array,\n  message:Uint8Array,\n  config:FrostConfig\n):Promise\u003cUint8Array\u003cArrayBuffer\u003e\u003e\n```\n\n```ts\nconst signature = await sign(recoveredKey, message, config)\n```\n\n\n-------\n\n\n### Distributed Signing\n\n#### `thresholdSign`\n\nCreate a threshold signature from multiple participants.\n\n```ts\nasync function thresholdSign (\n  keyPackages:KeyPackage[],\n  message:Uint8Array,\n  groupPublicKey:GroupElement,\n  config:FrostConfig\n):Promise\u003cUint8Array\u003e\n```\n\n```ts\nconst signature = await thresholdSign(\n    [aliceKey, bobKey, carolKey],  // Participant key packages\n    message,\n    groupPublicKey,\n    config\n)\n```\n\n#### `generateKeys`\n\nGenerate keys for all participants.\n\n```ts\nfunction generateKeys (config:FrostConfig):Signers\n```\n\n```ts\nconst { groupPublicKey, keyPackages } = generateKeys(config)\n// groupPublicKey: The collective public key\n// keyPackages: Individual key packages for each participant\n```\n\n#### `verifyKeyPackage`\n\nVerifies that a key package is valid.\n\n```ts\nfunction verifyKeyPackage (\n  keyPackage:KeyPackage,\n  config:FrostConfig\n):boolean\n```\n\n```ts\nconst isValid = verifyKeyPackage(keyPackage, config)\n```\n\n\n---------------------------------------------------------------\n\n\n## Standards\n\nThis implementation follows:\n\n- [RFC 9591](https://www.rfc-editor.org/rfc/rfc9591.html) - The Flexible\n  Round-Optimized Schnorr Threshold (FROST) Protocol\n\n\n## See Also\n\n* [FROST RFC 9591](https://www.rfc-editor.org/rfc/rfc9591.html)\n* [Ed25519 Signature Scheme](https://ed25519.cr.yp.to/)\n* [Improving Geographical Resilience For Distributed Open Source Teams with FREEON](https://soatok.blog/2025/08/09/improving-geographical-resilience-for-distributed-open-source-teams-with-freeon/)\n* [Threshold Cryptography](https://en.wikipedia.org/wiki/Threshold_cryptosystem)\n* [soatok/frost](https://github.com/soatok/frost) \u0026mdash; Go implementation\n* [Lose your device, but keep your keys](https://www.iroh.computer/blog/frost-threshold-signatures)\n  \u0026mdash; FROST in [iroh](https://www.iroh.computer/)\n\n\n-----------------------------------------------------------------------\n\n\n## Internals\n\n### Signing Protocol\n\n#### `FrostSigner`\n\nRepresents an individual participant in the signing ceremony.\n\n```ts\nconst signer = new FrostSigner(keyPackage, config)\n\n// Round 1: Generate nonce commitments\nconst round1 = signer.sign_round1()\n\n// Round 2: Generate signature share\nconst round2 = signer.sign_round2(signingPackage, round1.nonces)\n```\n\n#### `FrostCoordinator`\n\nManages the signing ceremony and aggregates signatures.\n\n```ts\nconst coordinator = new FrostCoordinator(config)\n\n// Create signing package\nconst signingPackage = coordinator.createSigningPackage(\n    message,\n    commitmentShares,\n    participantIds\n)\n\n// Aggregate signature shares\nconst signature = coordinator.aggregateSignatures(\n    signingPackage,\n    signatureShares\n)\n\n// Verify signature\nconst isValid = coordinator.verify(signature, message, groupPublicKey)\n```\n\n## Protocol Flow\n\nThe FROST protocol consists of the following phases:\n\n### 1. Key Generation (Setup)\n\n```ts\n// Generate keys for all participants\nconst { groupPublicKey, keyPackages } = generateKeys(config)\n\n// Distribute key packages to participants securely\n```\n\n### 2. Signing Ceremony\n\n#### Round 1: Commitment Phase\n\nEach participant generates nonces and creates commitments:\n\n```ts\nconst round1Results = signers.map(signer =\u003e signer.sign_round1())\n```\n\n#### Round 2: Signature Share Generation\n\nParticipants receive the signing package and generate signature shares:\n\n```ts\nconst signingPackage = coordinator.createSigningPackage(\n    message, commitmentShares, participantIds\n)\n\nconst signatureShares = signers.map((signer, i) =\u003e\n    signer.sign_round2(signingPackage, round1Results[i].nonces)\n)\n```\n\n#### Aggregation\n\nThe coordinator combines signature shares into a final signature:\n\n```ts\nconst signature = coordinator.aggregateSignatures(\n    signingPackage,\n    signatureShares.map(r =\u003e r.signatureShare)\n)\n```\n\n## Step-by-Step Guide\n\n### Example\n\nAlice can create a threshold keypair and later create signatures\nwith her trusted friends.\n\n#### Step 1: Alice Creates the Initial Setup\n\n```ts\nimport {\n    createFrostConfig,\n    generateKeys,\n    FrostCoordinator,\n    FrostSigner\n} from '@substrate-system/frost'\n\n// Alice decides she wants a 3-of-4 threshold scheme\nconst config = createFrostConfig(3, 4)  // Need 3 out of 4 to sign\nconst { groupPublicKey, keyPackages } = generateKeys(config)\n\n// Distribute key shares to Alice, Bob, Carol, and Desmond\nconst [aliceKey, bobKey, carolKey, desmondKey] = keyPackages\n```\n\n#### Step 2: Create a Signature\n\nLater, Alice wants to sign a message but needs help from 3 of her 4\ntrusted friends:\n\n```ts\n// Alice chooses Carol and Desmond to help (any 3 would work)\nconst participants = [aliceKey, carolKey, desmondKey]\nconst signers = participants.map(pkg =\u003e new FrostSigner(pkg, config))\nconst coordinator = new FrostCoordinator(config)\n```\n\n#### Step 3: Sign\n\nThis process creates a threshold signature:\n\n```ts\nconst message = new TextEncoder().encode('Alice\\'s important message')\n\n// Round 1: Each participant generates commitments\nconst round1 = signers.map(s =\u003e s.sign_round1())\nconst commitmentShares = round1.map((r, i) =\u003e ({\n    participantId: participants[i].participantId,\n    commitment: r.commitment\n}))\n\n// Create the signing package\nconst participantIds = participants.map(p =\u003e p.participantId)\nconst signingPackage = await coordinator.createSigningPackage(\n    message,\n    commitmentShares,\n    participantIds\n)\n\n// Round 2: Generate signature shares\nconst signatureShares = []\nfor (let i = 0; i \u003c signers.length; i++) {\n    const res = await signers[i].sign_round2(signingPackage, round1[i].nonces)\n    signatureShares.push(res.signatureShare)\n}\n\n// Combine into final signature\nconst finalSignature = coordinator.aggregateSignatures(\n  signingPackage,\n  signatureShares\n)\n\n// Verify it worked\nconst valid = await coordinator.verify(finalSignature, message, groupPublicKey)\nconsole.log('Threshold signature valid:', valid)  // Should be true\n```\n\nThe signature is mathematically equivalent to a single-key signature\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubstrate-system%2Ffrost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsubstrate-system%2Ffrost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsubstrate-system%2Ffrost/lists"}