A complete implementation of draft-irtf-cfrg-sigma-protocols-00 using Ristretto255.
- Schnorr Protocol: Prove knowledge of discrete logarithm
- DLEQ Protocol: Prove discrete logarithm equality (also known as Chaum-Pedersen)
- Pedersen Commitments: Prove knowledge of commitment opening
- General Framework: Build arbitrary linear relation proofs with simplified API
- Non-interactive proofs: Fiat-Shamir transformation with SHAKE128
- Both simple and general framework interfaces
- Serialization: Full encode/decode support with validation
- Secure: Built on libsodium's Ristretto255 group operations
- Spec compliant: Full implementation of IETF draft specification
#include <sodium.h>
#include "sigma.h"
// Initialize libsodium
sodium_init();
// Prove knowledge of private key
uint8_t private_key[CSIGMA_SCALAR_BYTES];
uint8_t public_key[CSIGMA_POINT_BYTES];
uint8_t proof[CSIGMA_SCHNORR_PROOF_SIZE];
crypto_core_ristretto255_scalar_random(private_key);
crypto_scalarmult_ristretto255_base(public_key, private_key);
uint8_t message[] = "Hello";
csigma_schnorr_prove(proof, private_key, public_key, message, sizeof(message));
bool valid = csigma_schnorr_verify(proof, public_key, message, sizeof(message));Prerequisites:
- C compiler (clang or gcc)
- libsodium development libraries
# Install libsodium (Ubuntu/Debian)
sudo apt-get install libsodium-dev
# Install libsodium (macOS)
brew install libsodium
# Build all executables
make
# Run all tests
make check#define CSIGMA_SCALAR_BYTES 32 // Scalar size
#define CSIGMA_POINT_BYTES 32 // Group element size
#define CSIGMA_SCHNORR_PROOF_SIZE 64 // Schnorr proof size
#define CSIGMA_DLEQ_PROOF_SIZE 96 // DLEQ proof size
#define CSIGMA_PEDERSEN_PROOF_SIZE 96 // Pedersen proof sizeProves knowledge of x where Y = x*G (G is the generator).
// Create proof
int csigma_schnorr_prove(
uint8_t proof[CSIGMA_SCHNORR_PROOF_SIZE], // Output: 64-byte proof
const uint8_t witness[CSIGMA_SCALAR_BYTES], // Secret x
const uint8_t public_key[CSIGMA_POINT_BYTES], // Public Y = x*G
const uint8_t *message, // Message to bind
size_t message_len
);
// Returns: 0 on success, -1 on error
// Verify proof
bool csigma_schnorr_verify(
const uint8_t proof[CSIGMA_SCHNORR_PROOF_SIZE],
const uint8_t public_key[CSIGMA_POINT_BYTES],
const uint8_t *message,
size_t message_len
);
// Returns: true if valid, false otherwiseProves that log_g1(h1) = log_g2(h2) without revealing the exponent.
// Create proof
int csigma_dleq_prove(
uint8_t proof[CSIGMA_DLEQ_PROOF_SIZE], // Output: 96-byte proof
const uint8_t witness[CSIGMA_SCALAR_BYTES], // Secret x where h1=g1^x, h2=g2^x
const uint8_t g1[CSIGMA_POINT_BYTES], const uint8_t h1[CSIGMA_POINT_BYTES],
const uint8_t g2[CSIGMA_POINT_BYTES], const uint8_t h2[CSIGMA_POINT_BYTES],
const uint8_t *message,
size_t message_len
);
// Returns: 0 on success, -1 on error
// Verify proof
bool csigma_dleq_verify(
const uint8_t proof[CSIGMA_DLEQ_PROOF_SIZE],
const uint8_t g1[CSIGMA_POINT_BYTES], const uint8_t h1[CSIGMA_POINT_BYTES],
const uint8_t g2[CSIGMA_POINT_BYTES], const uint8_t h2[CSIGMA_POINT_BYTES],
const uint8_t *message,
size_t message_len
);
// Returns: true if valid, false otherwise// Create commitment C = x*G + r*H
int csigma_pedersen_commit(
uint8_t commitment[CSIGMA_POINT_BYTES],
const uint8_t value[CSIGMA_SCALAR_BYTES],
const uint8_t randomness[CSIGMA_SCALAR_BYTES],
const uint8_t G[CSIGMA_POINT_BYTES],
const uint8_t H[CSIGMA_POINT_BYTES]
);
// Prove knowledge of opening
int csigma_pedersen_prove(
uint8_t proof[CSIGMA_PEDERSEN_PROOF_SIZE],
const uint8_t value[CSIGMA_SCALAR_BYTES],
const uint8_t randomness[CSIGMA_SCALAR_BYTES],
const uint8_t G[CSIGMA_POINT_BYTES],
const uint8_t H[CSIGMA_POINT_BYTES],
const uint8_t C[CSIGMA_POINT_BYTES],
const uint8_t *message,
size_t message_len
);
// Verify proof
bool csigma_pedersen_verify(
const uint8_t proof[CSIGMA_PEDERSEN_PROOF_SIZE],
const uint8_t G[CSIGMA_POINT_BYTES],
const uint8_t H[CSIGMA_POINT_BYTES],
const uint8_t C[CSIGMA_POINT_BYTES],
const uint8_t *message,
size_t message_len
);What it proves: Knowledge of a secret value (discrete logarithm) without revealing it.
Mathematical property: Proves "I know x such that Y = x*G" where G is the generator and Y is public.
Use cases:
- Digital signatures: Prove you own the private key corresponding to a public key
- Authentication: Log in to a service without transmitting your password
- Cryptocurrency wallets: Prove ownership of funds without revealing private keys
- Access control: Demonstrate you have credentials without exposing them
- Password-authenticated key exchange (PAKE): Establish secure channels based on passwords
Example scenario: Alice wants to prove she owns a Bitcoin address. She uses Schnorr to prove she knows the private key corresponding to the public address, without revealing the private key itself.
What it proves: Two discrete logarithms are equal, without revealing the common exponent.
Mathematical property: Proves "log_g1(h1) = log_g2(h2)" or equivalently "h1 = g1^x AND h2 = g2^x" for some secret x.
Use cases:
- Verifiable encryption: Prove a ciphertext encrypts a specific value without decryption
- Anonymous credentials: Show two credentials belong to the same user without revealing identity
- Mix networks: Prove correct re-encryption in privacy protocols
- Cross-chain atomic swaps: Prove same secret is used in multiple transactions
- Verifiable shuffles: Prove a list was correctly permuted without revealing the permutation
- Blind signatures: Prove consistency between blinded and unblinded values
Example scenario: A voting system needs to prove that an encrypted vote was correctly re-encrypted (same vote, different randomness) during the mixing phase, without revealing the actual vote.
| Aspect | Schnorr | DLEQ (Chaum-Pedersen) |
|---|---|---|
| Proof size | 64 bytes | 96 bytes |
| What's proven | Knowledge of one secret | Equality of two discrete logs |
| Complexity | Simpler | More complex |
| Computation | 2 exponentiations to verify | 4 exponentiations to verify |
| Primary use | Authentication, signatures | Verifiable encryption, DLEQ |
Convenient functions for common protocols (recommended for most users):
csigma_schnorr_prove()/csigma_schnorr_verify()csigma_dleq_prove()/csigma_dleq_verify()csigma_pedersen_prove()/csigma_pedersen_verify()
Build arbitrary linear relation proofs with easy-to-use helpers:
// Add elements and scalars in one step
int G = csigma_relation_add_element(&relation, generator);
int X = csigma_relation_add_element(&relation, public_key);
int x = csigma_relation_add_scalar(&relation);
// Add equation with single term (covers 80% of use cases)
csigma_relation_add_equation_simple(&relation, X, x, G);
// Prove and verify
csigma_prover_commit(&relation, witness, commitment, &state);
csigma_prover_response(&state, challenge, response);
bool valid = csigma_verify(&relation, commitment, challenge, response);For equations like C = xG + rH:
// Allocate multiple scalars/elements at once
int x = csigma_relation_allocate_scalars(&relation, 1);
int r = csigma_relation_allocate_scalars(&relation, 1);
// Add elements
int G = csigma_relation_add_element(&relation, G_value);
int H = csigma_relation_add_element(&relation, H_value);
// Multi-term equation
int scalar_indices[] = {x, r};
int element_indices[] = {G, H};
csigma_relation_add_equation(&relation, C, scalar_indices, element_indices, 2);// Serialize proof
int csigma_serialize_proof(
uint8_t *output,
const uint8_t *commitment,
size_t num_commitment_elements,
const uint8_t *response,
size_t num_response_scalars
);
// Deserialize proof (with validation)
int csigma_deserialize_proof(
uint8_t *commitment,
size_t num_commitment_elements,
uint8_t *response,
size_t num_response_scalars,
const uint8_t *data,
size_t data_len
);
// Calculate expected proof size
size_t csigma_proof_size(size_t num_commitment_elements, size_t num_response_scalars);See tests/test_framework.c for complete examples of framework usage with the simplified API.
- Elliptic Curve Group: Ristretto255 (via libsodium)
- Hash Function: SHAKE128 for Fiat-Shamir challenges
- Proof Sizes:
- Schnorr: 64 bytes (1 commitment + 1 response)
- DLEQ: 96 bytes (2 commitments + 1 response)
- Pedersen: 96 bytes (1 commitment + 2 responses)
- Security: 128-bit security level
- Namespace: All public functions prefixed with
csigma_ - Error Handling:
- Prove/create functions return 0 on success, -1 on error
- Verify functions return true if valid, false otherwise
See the following files for complete working examples:
example.c- Simple API demonstrationstests/test_framework.c- Simplified framework API usagetests/test_sigma.c- Schnorr and DLEQ teststests/test_pedersen.c- Pedersen commitment tests