=============== LIBRARY RULES =============== From library maintainers: - ring-sig uses @noble/curves v2 for elliptic curve operations. - SAG proves membership. LSAG adds linkability — use LSAG when you need to detect double-signing. - Ring size affects anonymity set — larger rings are more private but slower to verify. ### Project Setup Commands Source: https://github.com/forgesworn/ring-sig/blob/main/CONTRIBUTING.md Initializes the local development environment by cloning the repository and installing dependencies. ```bash git clone https://github.com/forgesworn/ring-sig.git cd ring-sig npm install npm test ``` -------------------------------- ### Install dependencies Source: https://github.com/forgesworn/ring-sig/blob/main/examples/README.md Run this command from the repository root to install necessary dependencies. ```bash npm install ``` -------------------------------- ### Install @forgesworn/ring-sig Source: https://github.com/forgesworn/ring-sig/blob/main/README.md Install the library using npm. ```bash npm install @forgesworn/ring-sig ``` -------------------------------- ### Run SAG example Source: https://github.com/forgesworn/ring-sig/blob/main/examples/README.md Executes the basic anonymous group membership proof example. ```bash npx tsx examples/basic-sag.ts ``` -------------------------------- ### Run LSAG example Source: https://github.com/forgesworn/ring-sig/blob/main/examples/README.md Executes the anonymous voting example with double-vote detection. ```bash npx tsx examples/basic-lsag.ts ``` -------------------------------- ### Generate a SAG ring signature Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Example demonstrating how to generate a signature as a specific member of a ring. ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { ringSign } from '@forgesworn/ring-sig'; const priv0 = bytesToHex(secp256k1.utils.randomPrivateKey()); const priv1 = bytesToHex(secp256k1.utils.randomPrivateKey()); const priv2 = bytesToHex(secp256k1.utils.randomPrivateKey()); // x-only public keys (drop the 02/03 prefix from compressed form) const pub0 = bytesToHex(secp256k1.getPublicKey(priv0, true)).slice(2); const pub1 = bytesToHex(secp256k1.getPublicKey(priv1, true)).slice(2); const pub2 = bytesToHex(secp256k1.getPublicKey(priv2, true)).slice(2); const ring = [pub0, pub1, pub2]; // Sign as ring member at index 1 — identity hidden from verifier const sig = ringSign('Hello, world!', ring, 1, priv1); // Serialisable — safe to send over the wire console.log(JSON.stringify(sig)); ``` -------------------------------- ### Custom Domain Separator Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Example of using a custom domain string to namespace signatures per application. ```typescript // Signatures from my-app-v1 cannot be replayed as my-other-app-v1 signatures const sig = ringSign(message, ring, index, privateKey, 'my-app-v1'); ``` -------------------------------- ### Development Workflow Commands Source: https://github.com/forgesworn/ring-sig/blob/main/CONTRIBUTING.md Standard commands for building, testing, and type-checking the project. ```bash npm run build # Compile TypeScript → dist/ npm test # Run test suite (vitest) npm run typecheck # Type-check without emitting (tsc --noEmit) ``` -------------------------------- ### Build and Test Commands Source: https://github.com/forgesworn/ring-sig/blob/main/CLAUDE.md Standard npm commands for building the project, running tests with Vitest, and performing type checking with tsc. Always run tests after changes and type check before committing. ```bash npm run build # tsc → dist/ npm test # vitest (single run) npm run typecheck # tsc --noEmit ``` -------------------------------- ### Pre-Computing Key Images for Eligibility Checks Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Demonstrates how to compute a key image beforehand, which is useful for checking if a voter has already participated in an election before they cast their vote. ```typescript import { computeKeyImage } from '@forgesworn/ring-sig'; // Compute before signing — useful to check eligibility first const image = computeKeyImage(voters[4].priv, voters[4].pub, electionId); // Check if this voter has already participated if (hasDuplicateKeyImage(image, acceptedKeyImages)) { console.log('Voter has already participated in this election.'); } ``` -------------------------------- ### Generate x-only public key Source: https://github.com/forgesworn/ring-sig/blob/main/examples/README.md Demonstrates creating a 32-byte x-only public key compatible with BIP-340 conventions. ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; const priv = bytesToHex(secp256k1.utils.randomPrivateKey()); // x-only: compressed (33 bytes) minus the 02/03 prefix = 32 bytes const pub = bytesToHex(secp256k1.getPublicKey(priv, true)).slice(2); ``` -------------------------------- ### Import library components Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt The library is ESM-only and provides a single entry point for all core functions and error types. ```typescript import { ringSign, ringVerify, lsagSign, lsagVerify, computeKeyImage, hasDuplicateKeyImage, RingSignatureError, ValidationError, CryptoError, MAX_RING_SIZE, } from '@forgesworn/ring-sig'; ``` -------------------------------- ### Import library functions Source: https://github.com/forgesworn/ring-sig/blob/main/llms.txt The library is ESM-only; import the required functions from the main entry point. ```typescript import { ringSign, ringVerify, lsagSign, lsagVerify, computeKeyImage, hasDuplicateKeyImage } from '@forgesworn/ring-sig'; ``` -------------------------------- ### Pre-compute Key Images Source: https://context7.com/forgesworn/ring-sig/llms.txt Generates a deterministic key image for a private key and election context. Useful for checking eligibility or participation status before generating a full signature. ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { computeKeyImage, hasDuplicateKeyImage } from '@forgesworn/ring-sig'; const priv = bytesToHex(secp256k1.utils.randomPrivateKey()); const pub = bytesToHex(secp256k1.getPublicKey(priv, true)).slice(2); // Pre-compute key image before signing const image = computeKeyImage(priv, pub, 'community-grant-vote-2026'); console.log(image); // 66-char hex string starting with '02' or '03' // Check eligibility without requiring full signature const acceptedKeyImages: string[] = []; // Load from database const hasVoted = hasDuplicateKeyImage(image, acceptedKeyImages); console.log(`Has already voted: ${hasVoted}`); // Key images change completely with different electionIds (unlinkable) const image2026 = computeKeyImage(priv, pub, 'vote-2026'); const image2027 = computeKeyImage(priv, pub, 'vote-2027'); console.log(image2026 === image2027); // false - unlinkable across contexts ``` -------------------------------- ### Compute Key Image Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Computes a deterministic key image for a signer. Use this before signing or to check for prior participation. ```typescript function computeKeyImage( privateKey: string, publicKey: string, electionId: string, ): string ``` ```typescript import { computeKeyImage } from '@forgesworn/ring-sig'; const image = computeKeyImage(priv0, pub0, 'election-2026-q1'); // image is a 66-character hex string beginning with '02' or '03' ``` -------------------------------- ### computeKeyImage Source: https://context7.com/forgesworn/ring-sig/llms.txt Computes the deterministic key image for a given private key and election context. ```APIDOC ## computeKeyImage ### Description Computes the deterministic key image I = x * H_p(P || electionId). Useful for checking if a signer has already participated. ### Parameters - **priv** (string) - Required - The signer's private key. - **pub** (string) - Required - The signer's public key. - **electionId** (string) - Required - The unique identifier for the election context. ### Response - **string** - A 66-char hex string representing the key image. ``` -------------------------------- ### Implement Custom Domain Separators Source: https://context7.com/forgesworn/ring-sig/llms.txt Applies domain parameters to signatures to ensure cross-protocol isolation. This prevents replay attacks where a signature valid in one application is incorrectly accepted in another. ```typescript import { ringSign, ringVerify, lsagSign, lsagVerify } from '@forgesworn/ring-sig'; // SAG with custom domain (default is 'sag-v1') const sagSig = ringSign( message, ring, signerIndex, privateKey, 'my-app-attestation-v1', ); console.log(sagSig.domain); // 'my-app-attestation-v1' // LSAG with custom domain (default is 'lsag-v1') const lsagSig = lsagSign( message, ring, signerIndex, privateKey, electionId, 'my-app-voting-v1', ); console.log(lsagSig.domain); // 'my-app-voting-v1' // Cross-domain replay prevention const crossDomain = { ...sagSig, domain: 'wrong-domain' }; console.log(ringVerify(crossDomain)); // false ``` -------------------------------- ### hasDuplicateKeyImage Source: https://context7.com/forgesworn/ring-sig/llms.txt Performs constant-time comparison to detect if a key image has already been seen. ```APIDOC ## hasDuplicateKeyImage ### Description Checks if a key image appears in a list of previously seen images to detect double-voting. ### Parameters - **keyImage** (string) - Required - The key image to check. - **acceptedKeyImages** (string[]) - Required - The list of previously recorded key images. ### Response - **boolean** - Returns true if the key image is found in the list. ``` -------------------------------- ### Errors and Constants Source: https://github.com/forgesworn/ring-sig/blob/main/llms.txt Details on error types and constants provided by the library. ```APIDOC ## Errors and Constants ### Errors - **`RingSignatureError`** - Description: Base error class for all errors originating from the ring signature operations. - **`ValidationError`** - Description: Thrown for malformed inputs, such as invalid key formats, out-of-bounds indices, or excessively large/small rings. - **`CryptoError`** - Description: Thrown when cryptographic operations fail, e.g., invalid keys provided or issues during curve point arithmetic. ### Constants - **`MAX_RING_SIZE`** - Description: The maximum number of members allowed in a ring. Currently set to 1000. - Value: `1000` ``` -------------------------------- ### Access Library Constants Source: https://context7.com/forgesworn/ring-sig/llms.txt Retrieves the maximum permitted ring size to ensure compliance with library constraints. ```typescript import { MAX_RING_SIZE } from '@forgesworn/ring-sig'; console.log(MAX_RING_SIZE); // 1000 - maximum permitted ring members (DoS guard) ``` -------------------------------- ### Handle Validation Errors Source: https://context7.com/forgesworn/ring-sig/llms.txt Demonstrates catching specific validation errors when signing messages with invalid inputs. ```typescript import { ringSign, RingSignatureError, ValidationError, CryptoError, } from '@forgesworn/ring-sig'; try { // Ring too small (minimum 2 members) ringSign('message', [pub0], 0, priv0); } catch (e) { if (e instanceof ValidationError) { console.log(e.message); // Ring must have at least 2 members } } try { // Signer index out of bounds ringSign('message', [pub0, pub1], 5, priv0); } catch (e) { if (e instanceof ValidationError) { console.log(e.message); // Signer index out of bounds } } try { // Private key doesn't match ring member ringSign('message', [pub0, pub1], 0, priv1); } catch (e) { if (e instanceof ValidationError) { console.log(e.message); // Private key does not match ring member } } ``` -------------------------------- ### Detect Duplicate Actions Source: https://context7.com/forgesworn/ring-sig/llms.txt Uses constant-time comparison to check if a key image has already been recorded. Typically used after signature verification to prevent double-voting. ```typescript import { lsagSign, lsagVerify, hasDuplicateKeyImage, type LsagSignature, } from '@forgesworn/ring-sig'; // Ballot box state - accumulated key images const acceptedKeyImages: string[] = []; let voteCount = 0; function processVote(sig: LsagSignature): boolean { // Step 1: Verify signature is cryptographically valid if (!lsagVerify(sig)) { console.log('REJECTED: invalid signature'); return false; } // Step 2: Check for double-voting (constant-time comparison) if (hasDuplicateKeyImage(sig.keyImage, acceptedKeyImages)) { console.log('REJECTED: duplicate key image - double vote detected'); return false; } // Accept vote and record key image acceptedKeyImages.push(sig.keyImage); voteCount++; console.log(`Vote accepted (#${voteCount})`); return true; } // Example: Process votes from multiple voters const vote1 = lsagSign('proposal-A', ring, 0, voters[0].priv, electionId); processVote(vote1); // Accepted const vote2 = lsagSign('proposal-B', ring, 1, voters[1].priv, electionId); processVote(vote2); // Accepted // Same voter tries again - detected const vote3 = lsagSign('proposal-A', ring, 0, voters[0].priv, electionId); processVote(vote3); // REJECTED: duplicate key image ``` -------------------------------- ### SAG API Source: https://github.com/forgesworn/ring-sig/blob/main/llms.txt Functions for generating and verifying Spontaneous Anonymous Group (SAG) ring signatures. ```APIDOC ## SAG API ### Description Functions for generating and verifying Spontaneous Anonymous Group (SAG) ring signatures. SAG provides pure anonymity without linkability. ### Functions #### `ringSign(message, ring, signerIndex, privateKey, domain?)` - **Description**: Produces a ring signature for a given message. - **Parameters**: - `message` (string | Uint8Array) - The message to sign. - `ring` (Array) - An array of public keys (hex strings) forming the ring. - `signerIndex` (number) - The index of the signer's public key within the `ring` array. - `privateKey` (string) - The signer's private key (hex string). - `domain` (string, optional) - A domain separator string. Defaults to 'sag-v1'. - **Returns**: `RingSignature` object. #### `ringVerify(sig)` - **Description**: Verifies a given ring signature. - **Parameters**: - `sig` (RingSignature) - The ring signature object to verify. - **Returns**: `boolean` - True if the signature is valid, false otherwise. ### Interfaces #### `RingSignature` - **Description**: Interface for a SAG ring signature object. - **Fields**: - `ring` (Array) - The array of public keys. - `c0` (string) - The initial commitment value. - `responses` (Array) - The signature response values. - `message` (string | Uint8Array) - The original message. - `domain` (string, optional) - The domain separator used. ``` -------------------------------- ### Custom Domain Separation for Signatures Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Applies custom domain separators to signatures to namespace them per application. This prevents replay attacks and ensures signatures are only valid within their intended context. ```typescript import { ringSign, lsagSign } from '@forgesworn/ring-sig'; // Namespace signatures per application — prevents cross-context replay const sagSig = ringSign(message, ring, index, priv, 'my-app-sag-v1'); const lsagSig = lsagSign(message, ring, index, priv, electionId, 'my-app-lsag-v1'); ``` -------------------------------- ### Custom Domain Separator for Ring Signatures Source: https://github.com/forgesworn/ring-sig/blob/main/README.md Use a custom domain parameter with `ringSign` or `lsagSign` to ensure cross-protocol isolation between different applications or signature types. ```typescript const sig = ringSign(message, ring, index, privateKey, 'my-app-v1'); ``` -------------------------------- ### LSAG API Source: https://github.com/forgesworn/ring-sig/blob/main/llms.txt Functions for generating and verifying Linkable Spontaneous Anonymous Group (LSAG) ring signatures, including key image computation. ```APIDOC ## LSAG API ### Description Functions for generating and verifying Linkable Spontaneous Anonymous Group (LSAG) ring signatures. LSAG extends SAG with double-action detection using deterministic key images. ### Functions #### `lsagSign(message, ring, signerIndex, privateKey, electionId, domain?)` - **Description**: Produces a linkable ring signature for a given message and election context. - **Parameters**: - `message` (string | Uint8Array) - The message to sign. - `ring` (Array) - An array of public keys (hex strings) forming the ring. - `signerIndex` (number) - The index of the signer's public key within the `ring` array. - `privateKey` (string) - The signer's private key (hex string). - `electionId` (string | Uint8Array) - An identifier for the election or context. - `domain` (string, optional) - A domain separator string. Defaults to 'lsag-v1'. - **Returns**: `LsagSignature` object. #### `lsagVerify(sig)` - **Description**: Verifies a given linkable ring signature. - **Parameters**: - `sig` (LsagSignature) - The linkable ring signature object to verify. - **Returns**: `boolean` - True if the signature is valid, false otherwise. #### `computeKeyImage(privateKey, publicKey, electionId)` - **Description**: Computes the deterministic key image for a given private key, public key, and election ID. - **Parameters**: - `privateKey` (string) - The private key (hex string). - `publicKey` (string) - The corresponding public key (hex string). - `electionId` (string | Uint8Array) - The election identifier. - **Returns**: `string` - The computed key image as a compressed point hex string. #### `hasDuplicateKeyImage(keyImage, existingImages)` - **Description**: Performs a constant-time check to see if a given key image exists within a list of existing key images. - **Parameters**: - `keyImage` (string) - The key image to check (hex string). - `existingImages` (Array) - An array of existing key images (hex strings). - **Returns**: `boolean` - True if the key image is found, false otherwise. ### Interfaces #### `LsagSignature` - **Description**: Interface for an LSAG linkable ring signature object. - **Fields**: - `keyImage` (string) - The computed key image. - `c0` (string) - The initial commitment value. - `responses` (Array) - The signature response values. - `ring` (Array) - The array of public keys. - `message` (string | Uint8Array) - The original message. - `electionId` (string | Uint8Array) - The election identifier. - `domain` (string, optional) - The domain separator used. ``` -------------------------------- ### Check for Duplicate Key Image Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Performs a constant-time check to see if a key image exists in a provided list. Prevents timing side-channel attacks. ```typescript function hasDuplicateKeyImage( keyImage: string, existingImages: string[], ): boolean ``` ```typescript import { hasDuplicateKeyImage } from '@forgesworn/ring-sig'; const seenImages: string[] = []; // load from your database if (hasDuplicateKeyImage(sig.keyImage, seenImages)) { throw new Error('Duplicate vote detected'); } // Accept the vote and record the key image seenImages.push(sig.keyImage); ``` -------------------------------- ### Create Linkable Ring Signature with lsagSign Source: https://context7.com/forgesworn/ring-sig/llms.txt Use `lsagSign` to create a Linkable Spontaneous Anonymous Group (LSAG) signature. This extends SAG by including a deterministic key image for double-action detection within a specified `electionId`. Signatures from the same key in the same `electionId` will produce identical key images. ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { lsagSign, type LsagSignature } from '@forgesworn/ring-sig'; // Generate key pairs for eligible voters const voters = Array.from({ length: 8 }, () => { const priv = bytesToHex(secp256k1.utils.randomPrivateKey()); const pub = bytesToHex(secp256k1.getPublicKey(priv, true)).slice(2); return { priv, pub }; }); const ring = voters.map(v => v.pub); const electionId = 'community-grant-vote-2026'; // Voter at index 0 casts an anonymous vote const sig: LsagSignature = lsagSign( 'proposal-A', ring, 0, voters[0].priv, electionId, ); // Signature includes key image for double-vote detection console.log(sig.keyImage); // Compressed point hex (66 chars, 02/03 prefix) console.log(sig.electionId); // 'community-grant-vote-2026' console.log(sig.ring.length); // 8 eligible voters console.log(sig.c0); // Starting challenge scalar console.log(sig.responses); // 8 response scalars ``` -------------------------------- ### lsagSign - Create Linkable Ring Signature Source: https://context7.com/forgesworn/ring-sig/llms.txt Creates a Linkable Spontaneous Anonymous Group (LSAG) signature that includes a deterministic key image for double-action detection. ```APIDOC ## lsagSign ### Description Creates an LSAG signature that extends SAG with a deterministic key image, enabling double-action detection within a specific context. ### Parameters - **message** (string) - Required - The message to be signed. - **ring** (string[]) - Required - Array of x-only public keys. - **index** (number) - Required - The index of the signer in the ring. - **privateKey** (string) - Required - The private key of the signer. - **electionId** (string) - Required - The context identifier for linkability. ### Response - **LsagSignature** (object) - Returns an object containing the key image, electionId, ring, c0, and response scalars. ``` -------------------------------- ### Error Classes Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Base and specialized error classes for library-specific exceptions. ```typescript class RingSignatureError extends Error { name: 'RingSignatureError'; constructor(message: string); } ``` ```typescript class ValidationError extends RingSignatureError { name: 'ValidationError'; constructor(message: string); } ``` ```typescript class CryptoError extends RingSignatureError { name: 'CryptoError'; constructor(message: string); } ``` -------------------------------- ### ringVerify - Verify SAG Ring Signature Source: https://context7.com/forgesworn/ring-sig/llms.txt Verifies a SAG ring signature, returning true if valid and false otherwise. ```APIDOC ## ringVerify ### Description Verifies a SAG ring signature. This function is safe for untrusted input as it catches all errors and returns false. ### Parameters - **signature** (RingSignature) - Required - The signature object to verify. ### Response - **boolean** - Returns true if the signature is valid, false otherwise. ``` -------------------------------- ### Create SAG Ring Signature with ringSign Source: https://context7.com/forgesworn/ring-sig/llms.txt Use `ringSign` to create a Spontaneous Anonymous Group (SAG) ring signature. This proves membership in a group without revealing the signer's identity. Ensure all participants' public keys are in the x-only BIP-340 format. ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { ringSign, type RingSignature } from '@forgesworn/ring-sig'; // Generate key pairs for a group of five participants const participants = Array.from({ length: 5 }, () => { const priv = bytesToHex(secp256k1.utils.randomPrivateKey()); // x-only public key: compressed form minus the 02/03 prefix (BIP-340 convention) const pub = bytesToHex(secp256k1.getPublicKey(priv, true)).slice(2); return { priv, pub }; }); const ring = participants.map(p => p.pub); // Sign as member at index 2 - identity hidden from verifier const sig: RingSignature = ringSign( 'I attest that the audit was conducted fairly.', ring, 2, participants[2].priv, ); // Signature structure console.log(sig.ring.length); // 5 - ring of public keys console.log(sig.c0); // Starting challenge scalar (hex) console.log(sig.responses.length); // 5 - one response scalar per ring member console.log(sig.message); // 'I attest that the audit was conducted fairly.' console.log(sig.domain); // undefined (defaults to 'sag-v1') // Signatures are JSON-serializable for transmission const json = JSON.stringify(sig); ``` -------------------------------- ### SAG: Anonymous Group Membership Proof Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Generates a signature for a group of public keys, allowing a member to prove their membership without revealing their identity. Verifiers can confirm a valid signature from the group, but not the specific signer. ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { ringSign, ringVerify } from '@forgesworn/ring-sig'; // Generate keys for a group of five const privKeys = Array.from({ length: 5 }, () => bytesToHex(secp256k1.utils.randomPrivateKey()) ); const pubKeys = privKeys.map(priv => bytesToHex(secp256k1.getPublicKey(priv, true)).slice(2) ); // Member at index 3 proves membership without revealing which one const sig = ringSign( 'I witnessed the incident at 14:32 UTC', pubKeys, 3, privKeys[3], ); // Any verifier can confirm a group member signed it console.log(ringVerify(sig)); // true console.log(sig.ring.length); // 5 — verifier knows the group, not the signer ``` -------------------------------- ### Define RingSignature interface Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt The structure of a generated ring signature object. ```typescript interface RingSignature { /** The ring of public keys (x-only hex, 32 bytes each) */ ring: string[]; /** Starting challenge scalar c_0 (hex) */ c0: string; /** Response scalars s_0 … s_{n-1} (hex) — one per ring member */ responses: string[]; /** The signed message (plaintext, stored verbatim) */ message: string; /** Domain separator — omitted when using the default 'sag-v1' */ domain?: string; } ``` -------------------------------- ### Verify SAG Ring Signature with ringVerify Source: https://context7.com/forgesworn/ring-sig/llms.txt Use `ringVerify` to validate a SAG ring signature. This function is safe for untrusted input as it returns `false` on any error instead of throwing exceptions. It can verify signatures after deserialization from JSON. ```typescript import { ringSign, ringVerify, type RingSignature } from '@forgesworn/ring-sig'; // Verify a valid signature const valid = ringVerify(sig); // true // Tamper detection - modified message fails verification const tamperedMessage: RingSignature = { ...sig, message: 'Different message' }; console.log(ringVerify(tamperedMessage)); // false // Tamper detection - modified ring fails verification const tamperedRing: RingSignature = { ...sig, ring: [...sig.ring.slice(0, -1), sig.ring[4].replace('a', 'b')], }; console.log(ringVerify(tamperedRing)); // false // Deserialize and verify transmitted signatures const restored: RingSignature = JSON.parse(json); console.log(ringVerify(restored)); // true ``` -------------------------------- ### LSAG Signature Generation Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Produces an LSAG ring signature. Extends SAG with a deterministic key image that enables double-action detection within the same `electionId`, without cross-context linkability. ```APIDOC ## `lsagSign` ### Description Produces an LSAG ring signature. Extends SAG with a deterministic key image that enables double-action detection within the same `electionId`, without cross-context linkability. ### Method `lsagSign` ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - **message** (`string`) - Required - Message to sign. Any UTF-8 string. - **ring** (`string[]`) - Required - Array of x-only public key hex strings. Minimum 2, maximum 1000, no duplicates. - **signerIndex** (`number`) - Required - Integer index of the actual signer. Must satisfy `0 <= signerIndex < ring.length`. - **privateKey** (`string`) - Required - Signer's private key as hex. Must match `ring[signerIndex]`. - **electionId** (`string`) - Required - Non-empty string binding the key image to a specific context (election, vote round, rate-limit window, etc.). - **domain** (`string`) - Optional - Domain separator. Defaults to `'lsag-v1'`. ### Request Example ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { lsagSign } from '@forgesworn/ring-sig'; const priv0 = bytesToHex(secp256k1.utils.randomPrivateKey()); const priv1 = bytesToHex(secp256k1.utils.randomPrivateKey()); const priv2 = bytesToHex(secp256k1.utils.randomPrivateKey()); const pub0 = bytesToHex(secp256k1.getPublicKey(priv0, true)).slice(2); const pub1 = bytesToHex(secp256k1.getPublicKey(priv1, true)).slice(2); const pub2 = bytesToHex(secp256k1.getPublicKey(priv2, true)).slice(2); const ring = [pub0, pub1, pub2]; const electionId = 'election-2026-q1'; // Sign as ring member at index 0 const sig = lsagSign('candidate-A', ring, 0, priv0, electionId); console.log(sig.keyImage); // deterministic 33-byte compressed point ``` ### Response #### Success Response (`LsagSignature`) - **Return Value** (`LsagSignature`) - An `LsagSignature` object. Fully serialisable to JSON. #### Response Example ```json { "keyImage": "02...", "c0": "hex...", "responses": ["hex...", "hex..."], "ring": ["hex...", "hex..."], "message": "candidate-A", "electionId": "election-2026-q1" } ``` ### Errors - `ValidationError` — ring too small/large, duplicate members, index out of range, key mismatch, empty electionId. - `CryptoError` — invalid key or curve operation failure. ``` -------------------------------- ### ringSign - Create SAG Ring Signature Source: https://context7.com/forgesworn/ring-sig/llms.txt Creates a Spontaneous Anonymous Group (SAG) ring signature to prove membership in a group without revealing the signer's identity. ```APIDOC ## ringSign ### Description Creates a Spontaneous Anonymous Group (SAG) ring signature proving membership in a group without revealing the signer's identity. ### Parameters - **message** (string) - Required - The message to be signed. - **ring** (string[]) - Required - Array of x-only public keys (32 bytes, 64 hex characters). - **index** (number) - Required - The index of the signer in the ring. - **privateKey** (string) - Required - The private key of the signer. ### Response - **RingSignature** (object) - Returns an object containing the ring, starting challenge scalar (c0), response scalars, message, and domain. ``` -------------------------------- ### Maximum Ring Size Constant Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Defines the maximum number of ring members allowed to prevent DoS attacks. ```typescript const MAX_RING_SIZE: number = 1000; ``` -------------------------------- ### lsagVerify Source: https://context7.com/forgesworn/ring-sig/llms.txt Verifies an LSAG ring signature, including cryptographic validity and key image integrity. ```APIDOC ## lsagVerify ### Description Verifies an LSAG ring signature including validation that the key image is a valid compressed secp256k1 point. Returns true if valid, false otherwise. ### Parameters - **sig** (LsagSignature) - Required - The signature object to verify. ### Response - **boolean** - Returns true if the signature is valid, false otherwise. ``` -------------------------------- ### ringSign Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Produces a SAG ring signature, allowing a signer to prove membership in a ring of public keys without revealing their identity. ```APIDOC ## ringSign ### Description Produces a SAG ring signature. The signer proves membership in the provided ring without revealing their specific position or identity. ### Method Function Call ### Parameters - **message** (string) - Required - Message to sign. Hashed internally. - **ring** (string[]) - Required - Array of x-only public key hex strings (32 bytes each). Minimum 2, maximum 1000. - **signerIndex** (number) - Required - Integer index of the actual signer in the ring. - **privateKey** (string) - Required - Signer's private key as hex. - **domain** (string) - Optional - Domain separator for cross-protocol isolation. Defaults to 'sag-v1'. ### Response - **RingSignature** (object) - Returns a serializable object containing the ring, starting challenge scalar (c0), response scalars, and the signed message. ``` -------------------------------- ### Verify SAG Ring Signature Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Verifies a SAG ring signature. Returns true if valid, false otherwise. All errors are caught and returned as false. ```typescript function ringVerify(sig: RingSignature): boolean ``` ```typescript import { ringVerify } from '@forgesworn/ring-sig'; const valid = ringVerify(sig); console.log(valid); // true // Tamper with the message — verification fails const tampered = { ...sig, message: 'Different message' }; console.log(ringVerify(tampered)); // false ``` -------------------------------- ### Ring Signature Verification Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Verifies a SAG ring signature. Returns `true` if valid, `false` otherwise. Never throws — all errors are caught and returned as `false`. ```APIDOC ## `ringVerify` ### Description Verifies a SAG ring signature. Returns `true` if valid, `false` otherwise. Never throws — all errors are caught and returned as `false`. ### Method `ringVerify` ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - **sig** (`RingSignature`) - Required - The ring signature to verify. ### Request Example ```typescript import { ringVerify } from '@forgesworn/ring-sig'; const valid = ringVerify(sig); console.log(valid); // true // Tamper with the message — verification fails const tampered = { ...sig, message: 'Different message' }; console.log(ringVerify(tampered)); // false ``` ### Response #### Success Response (boolean) - **Return Value** (`boolean`) - `true` if the signature is valid, `false` for any malformed input or verification failure. #### Response Example ```typescript // Returns true or false ``` -------------------------------- ### LSAG Signature Interface Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Defines the structure of an LSAG signature, including key image, challenges, responses, public keys, message, and election ID. ```typescript interface LsagSignature { /** Key image I = x * H_p(P || electionId) — compressed point hex (66 hex chars / 33 bytes) */ keyImage: string; /** Starting challenge scalar c_0 (hex) */ c0: string; /** Response scalars s_0 … s_{n-1} (hex) — one per ring member */ responses: string[]; /** The ring of public keys (x-only hex, 32 bytes each) */ ring: string[]; /** The signed message (plaintext, stored verbatim) */ message: string; /** Binds the key image to this specific election/context */ electionId: string; /** Domain separator — omitted when using the default 'lsag-v1' */ domain?: string; } ``` -------------------------------- ### LSAG Linkable Ring Signature Source: https://github.com/forgesworn/ring-sig/blob/main/README.md Sign a message with LSAG, enabling double-action detection via deterministic key images. Verification confirms membership and allows checking for duplicate key images within a specific context. ```typescript import { lsagSign, lsagVerify, computeKeyImage, hasDuplicateKeyImage } from '@forgesworn/ring-sig'; const electionId = 'vote-2026-q1'; const ring = [pubkey0, pubkey1, pubkey2]; // Sign const sig = lsagSign('candidate-A', ring, 0, privateKey0, electionId); // Verify const valid = lsagVerify(sig); // true // Detect double-signing: key images are deterministic per (key, electionId) pair const keyImage = computeKeyImage(privateKey0, pubkey0, electionId); const isDuplicate = hasDuplicateKeyImage(keyImage, existingKeyImages); ``` -------------------------------- ### Sign LSAG Ring Signature Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Produces an LSAG ring signature. Extends SAG with a deterministic key image for double-action detection within the same electionId. Throws ValidationError or CryptoError on failure. ```typescript function lsagSign( message: string, ring: string[], signerIndex: number, privateKey: string, electionId: string, domain?: string, // default: 'lsag-v1' ): LsagSignature ``` ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { lsagSign } from '@forgesworn/ring-sig'; const priv0 = bytesToHex(secp256k1.utils.randomPrivateKey()); const priv1 = bytesToHex(secp256k1.utils.randomPrivateKey()); const priv2 = bytesToHex(secp256k1.utils.randomPrivateKey()); const pub0 = bytesToHex(secp256k1.getPublicKey(priv0, true)).slice(2); const pub1 = bytesToHex(secp256k1.getPublicKey(priv1, true)).slice(2); const pub2 = bytesToHex(secp256k1.getPublicKey(priv2, true)).slice(2); const ring = [pub0, pub1, pub2]; const electionId = 'election-2026-q1'; // Sign as ring member at index 0 const sig = lsagSign('candidate-A', ring, 0, priv0, electionId); console.log(sig.keyImage); // deterministic 33-byte compressed point ``` -------------------------------- ### LSAG: Anonymous Voting with Double-Vote Detection Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Enables anonymous voting within a group, with built-in detection to prevent a voter from casting multiple votes. Each vote is tied to a unique key image derived from the voter's private key and the election ID. ```typescript import { secp256k1 } from '@noble/curves/secp256k1'; import { bytesToHex } from '@noble/hashes/utils'; import { lsagSign, lsagVerify, computeKeyImage, hasDuplicateKeyImage, } from '@forgesworn/ring-sig'; // Eligible voters const voters = Array.from({ length: 10 }, () => { const priv = bytesToHex(secp256k1.utils.randomPrivateKey()); const pub = bytesToHex(secp256k1.getPublicKey(priv, true)).slice(2); return { priv, pub }; }); const ring = voters.map(v => v.pub); const electionId = 'community-vote-2026-q2'; const acceptedKeyImages: string[] = []; // Voter 4 casts a vote const vote = lsagSign('option-B', ring, 4, voters[4].priv, electionId); if (!lsagVerify(vote)) throw new Error('Invalid signature'); if (hasDuplicateKeyImage(vote.keyImage, acceptedKeyImages)) { throw new Error('Duplicate vote rejected'); } acceptedKeyImages.push(vote.keyImage); console.log('Vote accepted. Key image recorded.'); // Voter 4 tries to vote again — detected const secondVote = lsagSign('option-A', ring, 4, voters[4].priv, electionId); if (lsagVerify(secondVote) && hasDuplicateKeyImage(secondVote.keyImage, acceptedKeyImages)) { console.log('Double-vote detected and rejected.'); // this branch runs } ``` -------------------------------- ### Verify LSAG Signatures Source: https://context7.com/forgesworn/ring-sig/llms.txt Validates an LSAG signature, ensuring the key image is a valid compressed secp256k1 point and the signature is cryptographically sound. Returns a boolean without throwing exceptions. ```typescript import { lsagSign, lsagVerify, type LsagSignature } from '@forgesworn/ring-sig'; // Verify a valid LSAG signature const valid = lsagVerify(sig); // true // Verification includes: // - Cryptographic signature validity // - Key image is valid compressed point (02/03 prefix, 33 bytes) // - Key image is not the identity/zero point // - electionId is non-empty string // Invalid signatures return false without throwing const tamperedSig: LsagSignature = { ...sig, message: 'proposal-B' }; console.log(lsagVerify(tamperedSig)); // false ``` -------------------------------- ### LSAG Signature Verification Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Verifies an LSAG ring signature. Returns `true` if valid, `false` otherwise. Never throws. ```APIDOC ## `lsagVerify` ### Description Verifies an LSAG ring signature. Returns `true` if valid, `false` otherwise. Never throws. Also validates: - `keyImage` is a valid compressed secp256k1 point (02/03 prefix, 33 bytes) - `keyImage` is not the identity/zero point - `electionId` is a non-empty string ### Method `lsagVerify` ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - **sig** (`LsagSignature`) - Required - The LSAG signature to verify. ### Request Example ```typescript import { lsagVerify } from '@forgesworn/ring-sig'; const valid = lsagVerify(sig); console.log(valid); // true ``` ### Response #### Success Response (boolean) - **Return Value** (`boolean`) - `true` if the signature is valid, `false` otherwise. #### Response Example ```typescript // Returns true or false ``` ``` -------------------------------- ### Define ringSign function signature Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt The function signature for producing a SAG ring signature. ```typescript function ringSign( message: string, ring: string[], signerIndex: number, privateKey: string, domain?: string, // default: 'sag-v1' ): RingSignature ``` -------------------------------- ### SAG Ring Signature Source: https://github.com/forgesworn/ring-sig/blob/main/README.md Sign a message as a member of a ring of public keys. Verification confirms membership without revealing the signer's identity. ```typescript import { ringSign, ringVerify } from '@forgesworn/ring-sig'; // A ring of x-only public keys (32 bytes, hex) const ring = [pubkey0, pubkey1, pubkey2, pubkey3]; // Sign as ring member at index 2 const sig = ringSign('my message', ring, 2, privateKey2); // Anyone can verify the signature is from a ring member const valid = ringVerify(sig); // true ``` -------------------------------- ### Verify LSAG Ring Signature Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Verifies an LSAG ring signature. Returns true if valid, false otherwise. Also validates key image format and electionId. ```typescript function lsagVerify(sig: LsagSignature): boolean ``` ```typescript import { lsagVerify } from '@forgesworn/ring-sig'; const valid = lsagVerify(sig); console.log(valid); // true ``` -------------------------------- ### LSAG Signature Type Source: https://github.com/forgesworn/ring-sig/blob/main/llms-full.txt Defines the structure of an LSAG (Linkable Spontaneous Anonymous Group) signature. ```APIDOC ## LSAG Signature Type: `LsagSignature` ### Description Represents a Linkable Spontaneous Anonymous Group (LSAG) signature. This type extends SAG with a deterministic key image for double-action detection within the same `electionId`, without cross-context linkability. ### Fields - **keyImage** (`string`) - Required - The key image `I = x * H_p(P || electionId)` (compressed point hex, 66 hex chars / 33 bytes). It is deterministically derived from the signer's private key and the `electionId`. Two LSAG signatures share a key image if and only if they were produced by the same private key for the same `electionId`. - **c0** (`string`) - Required - Starting challenge scalar (hex). - **responses** (`string[]`) - Required - Response scalars `s_0 … s_{n-1}` (hex) — one per ring member. - **ring** (`string[]`) - Required - The ring of public keys (x-only hex, 32 bytes each). Minimum 2, maximum 1000, no duplicates. - **message** (`string`) - Required - The signed message (plaintext, stored verbatim). - **electionId** (`string`) - Required - Binds the key image to this specific election/context. Non-empty string. - **domain** (`string`) - Optional - Domain separator. Defaults to `'lsag-v1'`. ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.