nostringer
Version:
Unlinkable ring signatures for Nostr-compatible secp256k1 keys
174 lines (119 loc) • 7.67 kB
Markdown
<div align="center">
<a href="https://github.com/AbdelStark/nostringer/actions/workflows/ci.yml"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/AbdelStark/nostringer/ci.yml?style=for-the-badge" height=30></a>
<a href="https://bitcoin.org/"> <img alt="Bitcoin" src="https://img.shields.io/badge/Bitcoin-000?style=for-the-badge&logo=bitcoin&logoColor=white" height=30></a>
<a href="https://www.getmonero.org/"> <img alt="Monero" src="https://img.shields.io/badge/Monero-000?style=for-the-badge&logo=monero&logoColor=white" height=30></a>
<a href="https://github.com/nostr-protocol/nostr"> <img alt="Nostr" src="https://img.shields.io/badge/Nostr-000?style=for-the-badge" height=30></a>
</div>
# Nostringer
<div align="center">
<img src="./assets/img/nostringer.png" alt="Nostringer" width="300">
</div>
An easy-to-use Javascript/Typescript library providing **unlinkable ring signatures** (SAG) for Nostr pubkeys. It allows a signer to prove membership in a group of Nostr accounts without revealing which specific account produced the signature.
Nostringer is largely inspired by [Monero's Ring Signatures](https://www.getmonero.org/library/Zero-to-Monero-2-0-0.pdf) using Spontaneous Anonymous Group signatures (SAG), and [beritani/ring-signatures](https://github.com/beritani/ring-signatures) implementation of ring signatures using the elliptic curve Ed25519 and Keccak for hashing.
## Table of Contents
- [Nostringer](#nostringer)
- [Table of Contents](#table-of-contents)
- [Disclaimer](#disclaimer)
- [Problem Statement](#problem-statement)
- [Key Features](#key-features)
- [Installation](#installation)
- [Module Formats](#module-formats)
- [Usage](#usage)
- [API Reference](#api-reference)
- [`sign(message: string | Uint8Array, privateKeyHex: string, publicKeysHex: string[]): RingSignature`](#signmessage-string--uint8array-privatekeyhex-string-publickeyshex-string-ringsignature)
- [`verify(signature: RingSignature, message: string | Uint8Array, publicKeysHex: string[]): boolean`](#verifysignature-ringsignature-message-string--uint8array-publickeyshex-string-boolean)
- [`RingSignature` Interface](#ringsignature-interface)
- [Security Considerations](#security-considerations)
- [License](#license)
- [References](#references)
## Disclaimer
> **This code is highly experimental**.
> **I am not a cryptographer** and this library has not been audited or formally verified.
> Use for educational exploration at your own risk. Production usage is **strongly** discouraged until further review and testing are performed.
## Problem Statement
In many scenarios, you want to prove that "someone among these N credentials produced this signature," but you do **not** want to reveal which credential or identity. For instance, you might have a set of recognized people / entities (Nostr pubkeys) who are allowed to post reviews or do priviledged actions, but you want them to be anonymous within that set.
A **ring signature** solves this problem by letting an **individual** sign a message with a group of possible public keys. A verifier can confirm that the message indeed came from **one** of those public keys, without knowing which.
## Key Features
- **Unlinkable**: Each signature hides the signer's identity. Two signatures from the same signer cannot be linked.
- **Compatible with Vanilla JS and Typescript**: Uses [@noble/secp256k1](https://github.com/paulmillr/noble-secp256k1) for curve ops and [@noble/hashes/sha3](https://github.com/paulmillr/noble-hashes) for Keccak-256 hashing.
- **BIP-340**: Directly supports Nostr x-only pubkeys (32-byte hex strings).
- **Easy to Use**: Simple `sign` and `verify` functions, minimal config needed.
## Installation
```bash
# using npm
npm install nostringer
# or using yarn
yarn add nostringer
```
## Module Formats
Nostringer supports both ESM (ECMAScript Modules) and CommonJS formats, making it compatible with virtually any JavaScript environment:
### ESM (Modern JavaScript)
```js
// In modern ESM environments or TypeScript
import { sign, verify } from "nostringer";
// Your code here
```
### CommonJS (Node.js)
```js
// In CommonJS environments
const { sign, verify } = require("nostringer");
// Your code here
```
The correct format will be automatically selected based on your environment and import style. No additional configuration is needed.
## Usage
```js
import { sign, verify } from "nostringer";
// Example: 3-member ring, with 'signerSK' as the signer's private key in hex
const ringPubKeys = [
"fa...1", // 32-byte hex: Nostr x-only pubkey #1
"65...d", // #2
"a0...9", // #3
];
const message = "Hello from ring signature land!";
const signature = sign(message, signerSK, ringPubKeys);
console.log("Generated signature:", signature);
// On the other side, to verify:
const isValid = verify(signature, message, ringPubKeys);
console.log("Is ring signature valid?", isValid);
```
## API Reference
### `sign(message: string | Uint8Array, privateKeyHex: string, publicKeysHex: string[]): RingSignature`
Signs a message using the SAG ring signature scheme.
- **message**: The message to sign (string or Uint8Array)
- **privateKeyHex**: The signer's private key (64-character hex string)
- **publicKeysHex**: Array of public keys in the ring (including the signer's key)
- **Returns**: A RingSignature object with `c0` (initial challenge) and `s` (array of responses)
### `verify(signature: RingSignature, message: string | Uint8Array, publicKeysHex: string[]): boolean`
Verifies a ring signature.
- **signature**: The ring signature object (`{ c0, s }`)
- **message**: The original message that was signed
- **publicKeysHex**: Array of public keys in the ring
- **Returns**: `true` if the signature is valid, `false` otherwise
### `RingSignature` Interface
```typescript
interface RingSignature {
c0: string; // Initial challenge (64-char hex)
s: string[]; // Array of responses (64-char hex strings)
}
```
## Security Considerations
- **Anonymity Set**: The larger the ring, the more anonymity it provides, but also increases signature size
- **No Trusted Setup**: Doesn't require any trusted setup or central authority
- **Unlinkability**: Signatures produced by the same signer cannot be linked together
- **Not Traceable**: Unlike some other ring signature schemes, this implementation doesn't include key images or linkability tags
## License
This project is licensed under the [MIT License](License).
## References
- [Linkable Spontaneous Anonymous Group Signature for Ad Hoc Groups](https://eprint.iacr.org/2004/027.pdf) - (Joseph Liu et al., 2004) – basis of LSAG.
- [Beritani, ring-signatures JS library](https://github.com/beritani/ring-signatures) – Ed25519 ring signature implementation (SAG, bLSAG, MLSAG, CLSAG).
- [Blockstream Elements rust-secp256k1-zkp library](https://github.com/BlockstreamResearch/rust-secp256k1-zkp) – Whitelist Ring Signature in libsecp256k1-zkp (C code exposed via Rust).
- [Zero to Monero 2.0 – Chapter 3, ring signature algorithms](https://www.getmonero.org/library/Zero-to-Monero-2-0-0.pdf).
- [Cronokirby Blog – On Monero's Ring Signatures](https://cronokirby.com/posts/2022/03/on-moneros-ring-signatures), explains Schnorr ring signatures in detail.
---
Started with love by [AbdelStark](https://github.com/AbdelStark) 🧡
Feel free to follow me on Nostr if you'd like, using my public key:
```text
npub1hr6v96g0phtxwys4x0tm3khawuuykz6s28uzwtj5j0zc7lunu99snw2e29
```
Or just **scan this QR code** to find me:
