Skip to content

Latest commit

 

History

History

lsag-ts

Alice's Ring LSAG-ts

This repository contains a TypeScript implementation of the Linkable Spontaneous Anonymous Group (LSAG) signature algorithm, built on Elliptic Curve Cryptography (ECC) for secure key generation and message signing. The implementation follows the LSAG algorithm as detailed in the SAG paper and takes inspiration from Zero to Monero (p.36).

Table of Contents

Linkable Spontaneous Anonymous Group (LSAG)

Linkable Spontaneous Anonymous Group (LSAG) signatures are an advanced cryptographic technique designed to provide both anonymity and linkability in a set of public keys (referred to as a "ring"). They extend the concept of ring signatures by allowing the detection of whether multiple signatures were created by the same signer, without revealing the signer's identity.

Key Features of LSAG

  1. Anonymity: LSAG ensures that the identity of the signer is concealed among a group of participants. This is achieved by constructing a "ring" of public keys, where the actual signer’s key is indistinguishable from the others. As a result, an external observer cannot determine which member of the ring actually signed the message.

  2. Linkability: While the signer’s identity remains hidden, LSAG signatures introduce a "linkability" feature that allows anyone to check if two signatures were made by the same private key. This is done through the creation of a key image, which is derived from the private key but cannot be used to reveal the private key itself. If the same key image appears in multiple signatures, those signatures can be linked to the same signer.

  3. Unlinkability in Different Contexts: To prevent key image reuse from leaking information across multiple contexts, LSAG allows the use of a linkability flag, a unique identifier for each context in which the signature is used. By changing the linkability flag, the signer can ensure that the key image is valid only within a specific context, providing unlinkability across different scenarios.

Group Setup

When setting up a group for cryptographic purposes, such as for a Spontaneous Anonymous Group (SAG) signature scheme, there are two primary methods to establish the group members' public keys:

  • using existing public keys of the members from publicly available data such as blockchains. This method is suitable for scenarios where the group members have a common caracteristics, such as being part of the same organization or having a specific role. For example, a group of members of a company's board of directors can be identified by their public keys, which are publicly available on the company's website.
  • using a group key generation algorithm to generate the public keys of the members. This method is suitable for scenarios where the group members are not known in advance, such as in a voting system. For example, a group of voters can be identified by their public keys, which are generated by the voting system's key generation algorithm.

Signature Generation

Let $l$ be the number of members in the group.
Let $R$ be a set of public keys of the group members such as $R$ = { $K_{0}$ , $K_{1}$ , ..., $K_{n}$ } where n be the number of members in the group minus 1 ($l = n + 1$).
Let $m$ be the digest of the message to be signed.
Let $H$ be a hash function.
Let $H_p$ be a hash function that maps some data to a point on the elliptic curve.
Let $k$ be a random integer in the range $[1, N-1]$. This is the private key of the signer.
Let $\pi$ be the signer position in the group. This is a random integer in the range $[0, n]$.

The signer computes the following:

  • Computes their key image: $\tilde{K} = k * H_p(K_{\pi})$
  • Generates a random integer $\alpha$ in the range [1, N-1]
  • Generates random responses r = { $r_{0}$ , $r_{1}$ , ... , $r_{\pi-1}$, $r_{\pi+1}$, ... , $r_{n}$ } where $r_{i}$ ($0 <= i <= n$ excluding $\pi$) is a random integer in the range $[1, N-1]$
  • Computes $c_{\pi+1} = H(R, m, [\alpha G], [\alpha H_p(K_{\pi})])$
  • For $j$ in $[\pi + 1, l + \pi]$ computes the following:
    • $i = mod(j, l)$ -> allows to loop over the group members
    • $s = i - 1$ if $i > 0$ else $l - 1$ -> $s = i - 1$ except when $i = 0$. In this case, $s = l - 1$
    • $c_{i+1} = H(R, m, [r_{s}G + c_{s}K_{s}], [r_{s}H_p(K_{s}) + c_{s}\tilde{K}])$
  • Define the signer's response to verify $\alpha = r_{\pi} + c_{\pi}k$ ($mod$ $N$)

The signature contains the following:

  • the ring of public keys $R$
  • the challenge $c_{1}$
  • the responses $r$ = { $r_{0}$ , $r_{1}$ , ... , $r_{n}$ }

Signature Verification

Known data:

  • the ring of public keys $R$
  • the seed $c_{0}$
  • the responses $r$ = { $r_{0}$ , $r_{1}$ , ... , $r_{n}$ }
  • the message $m$

The signature is valid if and only if the signature has been generated using one of the group member's private keys.

The verifier computes the following:

  • For $i = 2$ to $n$, with $i$ wrapping around to 1 after $n$:
    • $c_{i}$' = $H( R, m, [ r_{i-1} G + c_{i-1}' K_{i-1}], [r_{i-1}H_p(K_{i-1}) + c_{i-1}\tilde{K}])$ if $i ≠ 1$ else $c_{i}$' = $H(R, m, [r_{1}G + c_{1}K_{1}], [r_{1}H_p(K_{1}) + c_{1}\tilde{K}])$
  • If $c_{1}$' = $c_{1}$ then the signature is valid, else it is invalid.

Note: It’s important to use the hash that returns curve points directly, rather than computing some integer that is then multiplied by $G$. $H_p$ would be broken if someone discovered a way to find $n_x$ such that $n_xG = H_p(x)$.

Installation

To install the LSAG-ts library, use either npm or yarn:

npm install @cypher-laboratory/alicesring-lsag

or

yarn add @cypher-laboratory/alicesring-lsag

Usage

Import Classes

To get started, import the required classes from the LSAG-ts library:

import { RingSignature, Curve, CurveName, Point } from '@cypher-laboratory/alicesring-lsag';

Initialize Curve

Select the elliptic curve that matches your keys, such as SECP256K1 (for Bitcoin/Ethereum) or ED25519:

const curve = new Curve(CurveName.SECP256K1);

Create Ring of Public Keys

Create a ring of public keys to form the anonymous signing group:

const publicKeys = [
  '030066ba293cc22d0eadbe494e9bd4d6d05c3e09d74dff0e991075de74b2359678',
  '0316d7da70ba247a6a40bb310187e8789b80c45fa6dc0061abb8ced49cbe7f887f',
  '0221869ca3ae33be3a7327e9a0272203afa72c52a5460ceb9f4a50930531bd926a',
];

const ring: Point[] = publicKeys.map(compressed => Point.deserialize(compressed));

Sign a Message

To sign a message, use your private key and optionally a linkability flag to prevent signature leakage across contexts:

const signerPrivateKey = BigInt('0x...');
const message = 'Hello, Alice\'s Ring with LSAG!';
const linkabilityFlag = 'unique-context-string';

const signature = RingSignature.sign(ring, signerPrivateKey, message, curve, linkabilityFlag);

Verify the Signature

To verify the signature:

const isValid = signature.verify();
console.log('Is the signature valid?', isValid); // Outputs: true or false

Serialize and Deserialize

You can serialize and deserialize signatures as JSON or Base64:

const jsonString = signature.toJsonString();
const importedSignature = RingSignature.fromJson(jsonString);

Example Code

import { RingSignature, Curve, CurveName, Point } from '@cypher-laboratory/alicesring-lsag';

const curve = new Curve(CurveName.SECP256K1);
const ring = [new Point(curve, [BigInt('0x1...'), BigInt('0x2...')])];
const signerPrivateKey = BigInt('0x5...');
const message = 'Confidential message with LSAG';
const linkabilityFlag = 'transaction-context';

const signature = RingSignature.sign(ring, signerPrivateKey, message, curve, linkabilityFlag);

console.log('Is signature valid?', signature.verify());

const signatureJson = signature.toJsonString();
const importedSignature = RingSignature.fromJson(signatureJson);
console.log('Is the imported signature valid?', importedSignature.verify());

Key Classes

Point Class

Represents a point on an elliptic curve. Key methods include:

  • mult(scalar: bigint): Point
  • add(point: Point): Point
  • equals(point: Point): boolean
  • serialize(): string
  • static deserialize(compressed: string): Point

Curve Class

Represents an elliptic curve. Key properties include:

  • name: Name of the curve (SECP256K1, ED25519).
  • G: Generator point.
  • isOnCurve(point: Point): boolean: Validates if a point is on the curve.

RingSignature Class

Handles signing and verifying LSAG signatures. Key methods include:

  • static sign(ring: Point[], signerPrivateKey: bigint, message: string, curve: Curve, linkabilityFlag: string): RingSignature
  • verify(): boolean
  • toJsonString(): string
  • static fromJson(json: string | object): RingSignature

Security Considerations

  • Private Key Security: Ensure the safe handling of private keys.
  • Random Number Generation: Use secure random number generators.
  • Ring Size: A larger ring increases anonymity.
  • Key Image Uniqueness: Avoid using the same key image in different contexts by utilizing the linkability flag.

Sponsor

Polygon | We would like to thank the Polygon Foundation for supporting this project through the Polygon Community Grants Program Season 01.

Contribution

We welcome contributions. Please open issues or submit pull requests on our GitHub repository.


For further reference on SAG and LSAG, see the Zero to Monero document, which served as a guide for this implementation.