blindsig

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 25, 2026 License: MIT Imports: 16 Imported by: 0

README

blindsig

Tests Coverage Status Go Reference Go Report Card

Blind signature schemes in Go. A blind signature lets a signer sign a message without learning what the message is, and later, when the signature is revealed, the signer cannot link it back to the signing session that produced it.

Five schemes are provided:

Scheme Assumption Quantum-safe Signature/Token Rounds Verification
RSA (Chaum 1983) Factoring No 384 B 1 Public
Ed25519 (Schnorr) Discrete log No 64 B 3 Public
secp256k1 (Schnorr/BIP-340) Discrete log No 64 B 3 Public
BDHKE (Ed25519) Discrete log No 32 B 1 Keyed (mint)
BLNS23 (Beullens et al. 2023) Ring-SIS + Ring-LWE + NTRU Yes ~50 KB 2 Public

Installation

go get github.com/KarpelesLab/blindsig

What is a blind signature?

A blind signature protocol has three parties: a user (wants a signature), a signer (has the signing key), and a verifier (checks the signature later).

  1. The user blinds their message — transforming it so the signer cannot read it.
  2. The signer signs the blinded value — without learning anything about the original message.
  3. The user unblinds the result — recovering a valid signature on the original message.
  4. Anyone can verify the signature against the signer's public key.

The critical security property is unlinkability: even if the signer later sees the (message, signature) pair, they cannot determine which signing session produced it. This is what makes blind signatures useful for anonymous voting, e-cash, and privacy-preserving credentials.

RSA blind signatures

The RSA scheme uses Chaum's protocol (1983) with full-domain hashing (FDH). Unlinkability comes from RSA's algebraic homomorphism: the blinding factor r^e multiplied into the message cancels perfectly when divided out after signing, leaving no trace.

How it works
Client                              Server
------                              ------
m = FDH(message)
pick random r, compute r^e mod n
blinded = m · r^e mod n
                     blinded ──►
                                    blindSig = blinded^d mod n
                     ◄── blindSig
sig = blindSig · r^{-1} mod n
    = m^d mod n                     (valid RSA signature)

The signer computes blinded^d = (m · r^e)^d = m^d · r. The client removes r by multiplying by r^{-1}, leaving m^d — a standard RSA signature. The signer only ever saw m · r^e, which is uniformly random and reveals nothing about m.

Usage
package main

import (
    "fmt"
    "github.com/KarpelesLab/blindsig"
)

func main() {
    // Key generation
    key, _ := blindsig.GenerateBlindSigningKey(3072)
    pub := &key.PublicKey

    message := []byte("vote for candidate A")

    // Client: blind the message
    blinded, r, _ := blindsig.BlindMessage(message, pub)

    // Server: sign without seeing the message
    blindSig, _ := blindsig.SignBlinded(blinded, key)

    // Client: unblind to get a valid signature
    sig, _ := blindsig.UnblindSignature(blindSig, r, pub)

    // Anyone: verify
    valid := blindsig.VerifySignature(message, sig, pub)
    fmt.Println("Valid:", valid) // true
}

Schnorr blind signatures (Ed25519)

The Schnorr scheme uses the classic blind Schnorr protocol over the Ed25519 curve. Unlinkability comes from the client adding random blinding factors to both the commitment point and the challenge, which cancel out algebraically in the final signature.

How it works
Client                              Server
------                              ------
                                    pick random k
                                    R = kB
                     ◄── R
pick random α, β
R' = R + αB + βA                    (blinded commitment)
e' = H(R' || A || msg)
e = e' + β                          (blinded challenge)
                     e ──►
                                    s = k + e·a mod L
                     ◄── s
s' = s + α mod L                    (unblind response)
sig = (R', s')                       (64 bytes)

The signer saw e and R, but the signature contains e' (derived from R') and s'. Since α and β are random and unknown to the signer, they cannot link (R, e) to (R', s'). The algebraic identity holds because s'B = (k + ea + α)B = R + eA + αB = R' + e'A (since e = e' + β and R' = R + αB + βA).

Usage
package main

import (
    "fmt"
    "github.com/KarpelesLab/blindsig"
)

func main() {
    // Key generation
    sk, pk, _ := blindsig.GenerateEd25519Key()

    message := []byte("vote for candidate A")

    // Round 1 — Server: commit
    signerState, commitment, _ := blindsig.Ed25519SignerCommit()

    // Round 2 — Client: create blinded challenge
    clientState, challenge, _ := blindsig.Ed25519ClientChallenge(message, commitment, pk)

    // Round 3 — Server: respond
    response, _ := blindsig.Ed25519SignerRespond(signerState, challenge, sk)

    // Client: unblind
    sig, _ := blindsig.Ed25519ClientUnblind(clientState, response, pk)

    // Anyone: verify
    valid := blindsig.Ed25519Verify(message, sig, pk)
    fmt.Println("Valid:", valid) // true
}

Or using the convenience function:

sig, _ := blindsig.Ed25519BlindSign(message, sk)
valid := blindsig.Ed25519Verify(message, sig, sk.PublicKey())

secp256k1 blind signatures (BIP-340)

The secp256k1 scheme uses the same blind Schnorr protocol as Ed25519 but over the secp256k1 curve with BIP-340 conventions: x-only R encoding, even-y enforcement, and BLAKE-256 challenge hash. Signatures are compatible with the Bitcoin/Decred Schnorr ecosystem.

The sign convention is s = k - e·x (subtraction, vs Ed25519's addition), so the blinding signs are flipped: R' = R - αG - βP and s' = s - α. The client retries blinding if R'.y is odd (about 50% of the time, adding ~1 retry on average).

Note: secp256k1 blind signing takes a 32-byte hash (pre-hashed message), not the raw message.

Usage
package main

import (
    "crypto/sha256"
    "fmt"
    "github.com/KarpelesLab/blindsig"
)

func main() {
    sk, pk, _ := blindsig.GenerateSecp256k1Key()

    hash := sha256.Sum256([]byte("vote for candidate A"))

    // Round 1 — Server: commit
    signerState, commitment, _ := blindsig.Secp256k1SignerCommit()

    // Round 2 — Client: create blinded challenge
    clientState, challenge, _ := blindsig.Secp256k1ClientChallenge(hash[:], commitment, pk)

    // Round 3 — Server: respond
    response, _ := blindsig.Secp256k1SignerRespond(signerState, challenge, sk)

    // Client: unblind
    sig, _ := blindsig.Secp256k1ClientUnblind(clientState, response, pk)

    // Anyone: verify
    valid := blindsig.Secp256k1Verify(hash[:], sig, pk)
    fmt.Println("Valid:", valid) // true
}

BDHKE blind tokens (Cashu-style e-cash)

BDHKE (Blind Diffie-Hellman Key Exchange) is the blind signature scheme used in Cashu e-cash. It is the simplest scheme in the package: the mint computes a single scalar multiplication on a blinded point, and the user unblinds by subtracting a known offset. Tokens are just 32 bytes.

The tradeoff: keyed verification. Only the mint (who holds the secret key k) can verify tokens. This is natural for e-cash where the mint is always the verifier.

How it works
User                                Mint (secret k, public K=kG)
----                                ----
Y = hash_to_curve(secret)
B' = Y + rG                         (blind)
                     B' ──►
                                    C' = k·B'    (sign)
                     ◄── C'
C = C' - r·K = k·Y                  (unblind)

token = (secret, C)

Verification (mint only): C == k · hash_to_curve(secret).

Usage
sk, pk, _ := blindsig.GenerateBDHKEMintKey()

// User: blind
state, blinded, _ := blindsig.BDHKEBlind([]byte("token-secret-42"))

// Mint: sign (never sees the secret)
blindSig, _ := blindsig.BDHKESign(blinded, sk)

// User: unblind
token, _ := blindsig.BDHKEUnblind([]byte("token-secret-42"), blindSig, state, pk)

// Mint: verify (keyed — requires secret key)
valid := blindsig.BDHKEVerify(token, sk)

BLNS23 lattice-based blind signatures

The BLNS23 scheme (ePrint 2023/077) is a post-quantum blind signature based on standard lattice assumptions (Ring-SIS, Ring-LWE, and NTRU). Unlike RSA, lattice-based schemes don't have algebraic homomorphism, so the blinding mechanism is fundamentally different.

How it works

The key insight: the signer never sees any component of the final signature. Instead of algebraic unblinding, BLNS23 uses a combination of hash commitments, NTRU pre-image sampling, and zero-knowledge proofs.

Client                              Server
------                              ------
pick random short r
ρ = G(r)                            (hash, kept secret for now)
h = H(ρ, message)                   (target hash)
c = B·r + h                         (blinded commitment)
                       c ──►
                                    find short s with A·s = c
                                    (uses NTRU trapdoor)
                       ◄── s
verify A·s = c
π = NIZK proof that ∃(r, s):
    A·s = B·r + h   (linear relation)
    ||s||, ||r|| small   (shortness)

signature = (ρ, π)

Why it's blind: The signer sees only c, which is B·r + H(G(r), message). Since r is random and unknown to the signer, c looks uniformly random — it reveals nothing about the message. The final signature is (ρ, π) where ρ = G(r) (a hash the signer never saw) and π is a zero-knowledge proof (which by definition reveals nothing beyond the statement's truth). The signer cannot link c to (ρ, π) because they never learn r, and the NIZK is simulated.

Why it's unforgeable: Finding short s with A·s = c requires the NTRU trapdoor. Without it, this is the Ring-SIS problem (conjectured hard even for quantum computers). The user cannot forge signatures because they cannot produce short pre-images on their own.

Usage
package main

import (
    "fmt"
    "github.com/KarpelesLab/blindsig"
)

func main() {
    // Key generation
    params := blindsig.BLNS23DefaultParams()
    sk, pk, _ := blindsig.GenerateBLNS23Key(params)

    message := []byte("vote for candidate A")

    // Round 1 — Client: create blinded commitment
    state, req, _ := blindsig.BLNS23UserBlind(message, pk)

    // Round 2 — Server: compute pre-image (never sees the message)
    resp, _ := blindsig.BLNS23SignerRespond(req, sk)

    // Client: create signature from signer's response
    sig, _ := blindsig.BLNS23UserFinalize(state, resp, pk)

    // Anyone: verify
    valid := blindsig.BLNS23Verify(message, sig, pk)
    fmt.Println("Valid:", valid) // true
}

There is also a convenience function for local use (testing, single-process scenarios):

sig, _ := blindsig.BLNS23BlindSign(message, sk, pk)
valid := blindsig.BLNS23Verify(message, sig, pk)

Comparing the three schemes

How blinding works

The three schemes achieve blindness through different mechanisms:

RSA exploits the multiplicative homomorphism of modular exponentiation. The blinding factor r^e is multiplied into the message before signing and divided out after. The signer's computation (m · r^e)^d distributes as m^d · r, so the final signature m^d contains no trace of the blinding interaction. Elegant and non-interactive, but broken by quantum computers (Shor's algorithm).

Schnorr exploits the additive homomorphism of elliptic curve points. The client adds random offsets αB and βA to the commitment point, and a corresponding shift β to the challenge. After the signer responds, the client adds α to the response. All three blinding terms cancel algebraically: s'B = R' + e'A. The signer saw (R, e) but the signature contains (R', e') — completely unlinkable. Fast (sub-millisecond) with tiny 64-byte signatures, but also not quantum-safe.

BLNS23 uses a fundamentally different approach because lattice-based cryptography has no useful homomorphism. Instead:

  1. The message is hidden inside a hash commitment. The signer sees c = B·r + H(G(r), μ), but since r is random, c is computationally indistinguishable from a random ring element. The signer learns nothing about μ.

  2. The signer's contribution (the pre-image s) never appears directly in the signature. The signature is (ρ, π) — a hash and a zero-knowledge proof. The proof demonstrates that valid r and s exist without revealing them.

  3. The zero-knowledge proof provides unlinkability. Even though the signer knows they produced some s for some c, the proof π is computationally indistinguishable from a simulated proof. The signer cannot match π to their signing session.

Comparison table
Property RSA Ed25519 secp256k1 BDHKE BLNS23
Blinding mechanism Multiply by r^e Shift R,e by +α,+β Shift R,e by -α,-β Add rG to Y Hash commitment + NIZK
What the signer sees m·r^e e (shifted) e (shifted) Y + rG (random point) B·r + H(G(r),μ)
What's in the signature m^d (R', s') (R'.x, s') C = k·Y (32 B point) (ρ, π) — ZK proof
Unlinkability source r^e cancels α, β cancel α, β cancel rG cancels via r·K ZK proof
Verification Public Public Public Keyed (mint only) Public
Quantum-safe No No No No Yes
Rounds 1 3 3 1 2
Key size (public) 384 B 32 B 33 B 32 B ~2 KB
Signature/token size 384 B 64 B 64 B 32 B ~50 KB
Hardness assumption Factoring DLP (Ed25519) DLP (secp256k1) DLP (Ed25519) Ring-SIS + LWE + NTRU
When to use which
  • Ed25519: Best for most applications today. Tiny 64-byte signatures, fast operations, and widely deployed.

  • secp256k1: When you need compatibility with the Bitcoin/Decred ecosystem (BIP-340 Schnorr). Same security and size as Ed25519 but uses the secp256k1 curve.

  • BDHKE: For e-cash / token systems where the issuer is also the verifier. The simplest scheme, smallest tokens (32 bytes), and non-interactive. Keyed verification is the tradeoff.

  • RSA: When you need non-interactive public-verification blind signatures. Larger keys and signatures than Schnorr but no interaction required.

  • BLNS23: When you need quantum resistance. The larger signatures (~50 KB) and two-round protocol are the cost of post-quantum security. As lattice-based proof systems improve, these sizes will shrink.

Parameters

RSA
  • Minimum key size: 3072 bits (NIST SP 800-57)
  • Hash: FDH using MGF1-SHA256 expansion to modulus size
  • Signing: CRT exponentiation with timing blinding and Shamir fault detection
Schnorr (Ed25519)
  • Curve: Ed25519 (twisted Edwards, birational to Curve25519)
  • Group order L: 2^252 + 27742317777372353535851937790883648493
  • Challenge hash: SHA-512(R' || A || msg) mod L
  • Signature: (R', s') = 64 bytes
BLNS23

From the paper (Table 2):

Parameter Value Description
d 512 Polynomial ring degree (X^512 + 1)
q 7933 Signature ring modulus
≈ 2^41 Proof ring modulus (q × 277199453)
σ 232 Gaussian std dev for pre-image sampling
r coefficients {-2,-1,0,1,2} User commitment randomness
NIZK κ 60 Challenge polynomial weight

References

  • D. Chaum. Blind Signatures for Untraceable Payments. CRYPTO 1982.
  • D. Pointcheval, J. Stern. Security Arguments for Digital Signatures and Blind Signatures. J. Cryptology, 2000. — Blind Schnorr security analysis.
  • W. Beullens, V. Lyubashevsky, N. K. Nguyen, G. Seiler. Lattice-Based Blind Signatures: Short, Efficient, and Round-Optimal. ePrint 2023/077.
  • V. Lyubashevsky, N. K. Nguyen, M. Plançon. Lattice-Based Zero-Knowledge Proofs and Applications. CRYPTO 2022. ePrint 2022/284.

License

See LICENSE for details.

Documentation

Overview

Package blindsig implements blind signature schemes.

Blind signatures allow a signer to sign a message without learning its content. Three schemes are provided:

  • RSA blind signatures (Chaum, 1983) using full-domain hashing. Non-interactive (1 round). 384-byte signatures. Not quantum-safe.

  • Schnorr blind signatures over Ed25519. Interactive (3 rounds). 64-byte signatures. Not quantum-safe, but efficient and compact.

  • Schnorr blind signatures over secp256k1 (BIP-340 compatible). Interactive (3 rounds). 64-byte signatures with x-only R. Not quantum-safe. Compatible with Bitcoin/Decred Schnorr.

  • BLNS23 lattice-based blind signatures (Beullens-Lyubashevsky-Nguyen-Seiler, ePrint 2023/077) using NTRU pre-image sampling and NIZK proofs. Interactive (2 rounds). ~50 KB signatures. Quantum-resistant.

  • BDHKE (Blind Diffie-Hellman Key Exchange) over Ed25519. Non-interactive (1 round). 32-byte tokens. Keyed verification (only the mint/signer can verify). Used in Cashu e-cash.

All schemes provide blindness: the signer cannot learn the message. RSA, Ed25519, secp256k1, and BLNS23 provide public verification and full unlinkability. BDHKE provides keyed verification (mint-only).

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidSignature = errors.New("invalid blind signature")

ErrInvalidSignature is returned when a blind signature fails verification.

Functions

func BDHKEBlind added in v0.1.1

func BDHKEBlind(secret []byte) (*BDHKEBlindingState, *BDHKEBlindedMessage, error)

BDHKEBlind blinds a secret for sending to the mint. Computes Y = hash_to_curve(secret), B' = Y + rG. Returns the blinded message and the blinding state needed for unblinding.

func BDHKEVerify added in v0.1.1

func BDHKEVerify(token *BDHKEToken, sk *BDHKEMintPrivateKey) bool

BDHKEVerify verifies a token against the mint's secret key. Checks that C == k·hash_to_curve(secret). This is keyed verification — only the mint (with secret key k) can verify.

func BLNS23UserBlind

func BLNS23UserBlind(message []byte, pk *BLNS23PublicKey) (*BLNS23ClientState, *BLNS23BlindRequest, error)

BLNS23UserBlind creates a blinded commitment for the message (Round 1). Returns the client state (kept secret) and the blind request to send to the signer.

func BLNS23Verify

func BLNS23Verify(message []byte, sig *BLNS23Signature, pk *BLNS23PublicKey) bool

BLNS23Verify verifies a BLNS23 blind signature on the given message.

func BlindMessage

func BlindMessage(message []byte, pubKey *rsa.PublicKey) (blinded *big.Int, blindingFactor *big.Int, err error)

BlindMessage blinds a message so it can be sent to a signer without revealing its content. It returns the blinded value and the blinding factor needed to later unblind the signature.

func Ed25519SignerRespond

func Ed25519SignerRespond(state *Ed25519SignerState, challenge []byte, sk *Ed25519PrivateKey) ([]byte, error)

Ed25519SignerRespond computes the signer's response (Round 3). s = k + e·a mod L.

func Ed25519Verify

func Ed25519Verify(message []byte, sig *Ed25519Signature, pk *Ed25519PublicKey) bool

Ed25519Verify verifies a blind signature on the given message.

func GenerateBDHKEMintKey added in v0.1.1

func GenerateBDHKEMintKey() (*BDHKEMintPrivateKey, *BDHKEMintPublicKey, error)

GenerateBDHKEMintKey generates a mint key pair for BDHKE.

func GenerateBLNS23Key

func GenerateBLNS23Key(params *BLNS23Params) (*BLNS23PrivateKey, *BLNS23PublicKey, error)

GenerateBLNS23Key generates a BLNS23 key pair.

func GenerateBlindSigningKey

func GenerateBlindSigningKey(bits int) (*rsa.PrivateKey, error)

GenerateBlindSigningKey generates an RSA private key suitable for blind signing. bits must be at least 3072.

func GenerateEd25519Key

func GenerateEd25519Key() (*Ed25519PrivateKey, *Ed25519PublicKey, error)

GenerateEd25519Key generates a key pair for blind signatures over Ed25519.

func GenerateSecp256k1Key

func GenerateSecp256k1Key() (*Secp256k1PrivateKey, *Secp256k1PublicKey, error)

GenerateSecp256k1Key generates a key pair for blind signatures over secp256k1.

func Secp256k1SignerRespond

func Secp256k1SignerRespond(state *Secp256k1SignerState, challenge []byte, sk *Secp256k1PrivateKey) ([]byte, error)

Secp256k1SignerRespond computes the signer's response (Round 3). BIP-340 convention: s = k - e·x (negating k if R.y is odd).

func Secp256k1Verify

func Secp256k1Verify(hash []byte, sig *Secp256k1Signature, pk *Secp256k1PublicKey) bool

Secp256k1Verify verifies a blind secp256k1 signature. BIP-340 verification: R = s·G + e·P, check R.x == sig.R and R.y is even.

func SignBlinded

func SignBlinded(blinded *big.Int, privKey *rsa.PrivateKey) (*big.Int, error)

SignBlinded signs a blinded message using the signer's private key. The signer never sees the original message.

Internally this uses CRT exponentiation with random timing blinding and Shamir fault detection.

func UnblindSignature

func UnblindSignature(blindSig *big.Int, blindingFactor *big.Int, pubKey *rsa.PublicKey) (*big.Int, error)

UnblindSignature removes the blinding factor from a blind signature, yielding a valid signature on the original message.

func VerifySignature

func VerifySignature(message []byte, signature *big.Int, pubKey *rsa.PublicKey) bool

VerifySignature verifies that signature is a valid RSA blind signature on message under the given public key.

Types

type BDHKEBlindSignature added in v0.1.1

type BDHKEBlindSignature struct {
	// contains filtered or unexported fields
}

BDHKEBlindSignature is the mint's blind signature C' = k·B'.

func BDHKESign added in v0.1.1

BDHKESign creates a blind signature on a blinded message. The mint computes C' = k·B' without learning the secret.

func ParseBDHKEBlindSignature added in v0.1.1

func ParseBDHKEBlindSignature(data []byte) (*BDHKEBlindSignature, error)

ParseBDHKEBlindSignature parses a 32-byte compressed blind signature.

func (*BDHKEBlindSignature) Bytes added in v0.1.1

func (s *BDHKEBlindSignature) Bytes() []byte

Bytes serializes a blind signature to 32 bytes (compressed point).

type BDHKEBlindedMessage added in v0.1.1

type BDHKEBlindedMessage struct {
	// contains filtered or unexported fields
}

BDHKEBlindedMessage is a blinded token B' = Y + rG sent to the mint.

func ParseBDHKEBlindedMessage added in v0.1.1

func ParseBDHKEBlindedMessage(data []byte) (*BDHKEBlindedMessage, error)

ParseBDHKEBlindedMessage parses a 32-byte compressed blinded message.

func (*BDHKEBlindedMessage) Bytes added in v0.1.1

func (b *BDHKEBlindedMessage) Bytes() []byte

Bytes serializes a blinded message to 32 bytes (compressed point).

type BDHKEBlindingState added in v0.1.1

type BDHKEBlindingState struct {
	// contains filtered or unexported fields
}

BDHKEBlindingState holds the client's blinding factor for unblinding.

type BDHKEMintPrivateKey added in v0.1.1

type BDHKEMintPrivateKey struct {
	// contains filtered or unexported fields
}

BDHKEMintPrivateKey is the mint's secret key k.

func (*BDHKEMintPrivateKey) PublicKey added in v0.1.1

func (sk *BDHKEMintPrivateKey) PublicKey() *BDHKEMintPublicKey

PublicKey returns the mint's public key.

type BDHKEMintPublicKey added in v0.1.1

type BDHKEMintPublicKey struct {
	// contains filtered or unexported fields
}

BDHKEMintPublicKey is the mint's public key K = kG.

func ParseBDHKEMintPublicKey added in v0.1.1

func ParseBDHKEMintPublicKey(data []byte) (*BDHKEMintPublicKey, error)

ParseBDHKEMintPublicKey parses a 32-byte compressed public key.

func (*BDHKEMintPublicKey) Bytes added in v0.1.1

func (pk *BDHKEMintPublicKey) Bytes() []byte

Bytes returns the 32-byte compressed public key.

type BDHKEToken added in v0.1.1

type BDHKEToken struct {
	Secret []byte   // x — the user's secret
	Cx, Cy *big.Int // C = k·Y where Y = hash_to_curve(x)
}

BDHKEToken is an unblinded token (x, C) where C = k·hash_to_curve(x).

func BDHKEUnblind added in v0.1.1

func BDHKEUnblind(secret []byte, blindSig *BDHKEBlindSignature, state *BDHKEBlindingState, mintPub *BDHKEMintPublicKey) (*BDHKEToken, error)

BDHKEUnblind removes the blinding to produce a valid token. C = C' - r·K where K is the mint's public key.

func (*BDHKEToken) Bytes added in v0.1.1

func (t *BDHKEToken) Bytes() []byte

Bytes serializes a token: 32-byte C point || secret.

type BLNS23BlindRequest

type BLNS23BlindRequest struct {
	C ring.BigPoly // c = B·r + H(G(r), μ)
}

BLNS23BlindRequest is the user's blinded commitment (Round 1 message).

type BLNS23BlindResponse

type BLNS23BlindResponse struct {
	S ring.BigPolyVec // short s with A·s = c
}

BLNS23BlindResponse is the signer's pre-image (Round 2 message).

func BLNS23SignerRespond

func BLNS23SignerRespond(req *BLNS23BlindRequest, sk *BLNS23PrivateKey) (*BLNS23BlindResponse, error)

BLNS23SignerRespond computes the signer's response (Round 2). The signer finds a short pre-image s with A·s = c using the NTRU trapdoor.

type BLNS23ClientState

type BLNS23ClientState struct {
	R       ring.BigPolyVec // random commitment vector (secret)
	Rho     []byte          // G(r) hash
	HTarget ring.BigPoly    // H(ρ, μ) — the hash target
	Message []byte          // original message
}

BLNS23ClientState holds the user's ephemeral state between protocol rounds.

type BLNS23Params

type BLNS23Params struct {
	SigRing     *ring.BigRing // R_q: d=512, q=7933
	ProofRing   *ring.BigRing // R_{q̂}: d=512, q̂=q·q2
	Q           *big.Int
	Q2          *big.Int
	QHat        *big.Int
	Sigma       float64         // pre-image Gaussian std dev
	BetaRSq     *big.Int        // L2² norm bound on r
	BetaSSq     *big.Int        // L2² norm bound on s
	ProofParams *tworing.Params // NIZK parameters
}

BLNS23Params holds the pre-computed scheme parameters.

func BLNS23DefaultParams

func BLNS23DefaultParams() *BLNS23Params

BLNS23DefaultParams returns the default parameters from the paper (Table 2).

type BLNS23PrivateKey

type BLNS23PrivateKey struct {
	Params *BLNS23Params
	F      ring.BigPoly    // NTRU secret f
	G      ring.BigPoly    // NTRU secret g
	H      ring.BigPoly    // public h = f^{-1}·g mod q
	FInv   ring.BigPoly    // f^{-1} mod q
	FF     ring.BigPoly    // F from SolveNTRU: fG - gF = q
	GG     ring.BigPoly    // G from SolveNTRU
	B      ring.BigPolyVec // random blinding vector (same as in public key)
}

BLNS23PrivateKey is the signer's secret key.

func (*BLNS23PrivateKey) PublicKey

func (sk *BLNS23PrivateKey) PublicKey() *BLNS23PublicKey

PublicKey returns the public key corresponding to this private key.

type BLNS23PublicKey

type BLNS23PublicKey struct {
	Params *BLNS23Params
	A      ring.BigPolyVec // [h, 1] — NTRU public key structure
	B      ring.BigPolyVec // random blinding vector (2 polynomials)
}

BLNS23PublicKey is the signer's public key.

type BLNS23Signature

type BLNS23Signature struct {
	Rho   []byte         // G(r) — 32 bytes
	Proof *tworing.Proof // NIZK proof π₂
}

BLNS23Signature is the blind signature on a message.

func BLNS23BlindSign

func BLNS23BlindSign(message []byte, sk *BLNS23PrivateKey, pk *BLNS23PublicKey) (*BLNS23Signature, error)

BLNS23BlindSign runs the full blind signing protocol locally (convenience).

func BLNS23UserFinalize

func BLNS23UserFinalize(state *BLNS23ClientState, resp *BLNS23BlindResponse, pk *BLNS23PublicKey) (*BLNS23Signature, error)

BLNS23UserFinalize creates the blind signature from the signer's response (Round 3).

type Ed25519ClientState

type Ed25519ClientState struct {
	// contains filtered or unexported fields
}

Ed25519ClientState holds the client's state between protocol rounds.

func Ed25519ClientChallenge

func Ed25519ClientChallenge(message, commitment []byte, pk *Ed25519PublicKey) (*Ed25519ClientState, []byte, error)

Ed25519ClientChallenge creates a blinded challenge from the signer's commitment (Round 2). Computes R' = R + αB + βA, e' = H(R' || A || msg), sends e = e' + β.

type Ed25519PrivateKey

type Ed25519PrivateKey struct {
	// contains filtered or unexported fields
}

Ed25519PrivateKey is a private key for blind signatures over Ed25519.

func (*Ed25519PrivateKey) PublicKey

func (sk *Ed25519PrivateKey) PublicKey() *Ed25519PublicKey

PublicKey returns the public key for this private key.

type Ed25519PublicKey

type Ed25519PublicKey struct {
	// contains filtered or unexported fields
}

Ed25519PublicKey is a public key for blind signatures over Ed25519.

func ParseEd25519PublicKey

func ParseEd25519PublicKey(data []byte) (*Ed25519PublicKey, error)

ParseEd25519PublicKey parses a 32-byte compressed public key.

func (*Ed25519PublicKey) Bytes

func (pk *Ed25519PublicKey) Bytes() []byte

Bytes returns the 32-byte compressed encoding of the public key.

type Ed25519Signature

type Ed25519Signature struct {
	Rx, Ry *big.Int
	S      *big.Int
}

Ed25519Signature is a blind signature (64 bytes: R' || s').

func Ed25519BlindSign

func Ed25519BlindSign(message []byte, sk *Ed25519PrivateKey) (*Ed25519Signature, error)

Ed25519BlindSign runs the full blind protocol locally (convenience).

func Ed25519ClientUnblind

func Ed25519ClientUnblind(state *Ed25519ClientState, response []byte, pk *Ed25519PublicKey) (*Ed25519Signature, error)

Ed25519ClientUnblind removes the blinding to produce the final signature. s' = s + α mod L. Signature = (R', s').

func ParseEd25519Signature

func ParseEd25519Signature(data []byte) (*Ed25519Signature, error)

ParseEd25519Signature parses a 64-byte signature.

func (*Ed25519Signature) Bytes

func (sig *Ed25519Signature) Bytes() []byte

Bytes returns the 64-byte encoding: R' (32 bytes) || s' (32 bytes).

type Ed25519SignerState

type Ed25519SignerState struct {
	// contains filtered or unexported fields
}

Ed25519SignerState holds the signer's ephemeral state for one signing session.

func Ed25519SignerCommit

func Ed25519SignerCommit() (*Ed25519SignerState, []byte, error)

Ed25519SignerCommit starts a blind signing session (Round 1). The signer picks a random nonce k and returns the commitment R = kB (32 bytes).

type Secp256k1ClientState

type Secp256k1ClientState struct {
	// contains filtered or unexported fields
}

Secp256k1ClientState holds the client's state between protocol rounds.

func Secp256k1ClientChallenge

func Secp256k1ClientChallenge(hash, commitment []byte, pk *Secp256k1PublicKey) (*Secp256k1ClientState, []byte, error)

Secp256k1ClientChallenge creates a blinded challenge from the signer's commitment (Round 2).

BIP-340 convention: s = k - e·x, verification R = s·G + e·P. Blinding: R' = R - αG - βP, e' = H(R'.x || msg), e = e' + β, s' = s - α. The client retries blinding if R'.y is odd (BIP-340 requires even y).

type Secp256k1PrivateKey

type Secp256k1PrivateKey struct {
	// contains filtered or unexported fields
}

Secp256k1PrivateKey is a private key for blind signatures over secp256k1.

func (*Secp256k1PrivateKey) PublicKey

func (sk *Secp256k1PrivateKey) PublicKey() *Secp256k1PublicKey

PublicKey returns the public key for this private key.

type Secp256k1PublicKey

type Secp256k1PublicKey struct {
	// contains filtered or unexported fields
}

Secp256k1PublicKey is a public key for blind signatures over secp256k1.

func ParseSecp256k1PublicKey

func ParseSecp256k1PublicKey(data []byte) (*Secp256k1PublicKey, error)

ParseSecp256k1PublicKey parses a 33-byte compressed public key.

func (*Secp256k1PublicKey) Bytes

func (pk *Secp256k1PublicKey) Bytes() []byte

Bytes returns the 33-byte compressed encoding of the public key.

type Secp256k1Signature

type Secp256k1Signature struct {
	R secp256k1.FieldVal   // R'.x (x-coordinate only)
	S secp256k1.ModNScalar // s'
}

Secp256k1Signature is a blind signature (64 bytes: R'.x || s').

func ParseSecp256k1Signature

func ParseSecp256k1Signature(data []byte) (*Secp256k1Signature, error)

ParseSecp256k1Signature parses a 64-byte signature.

func Secp256k1BlindSign

func Secp256k1BlindSign(hash []byte, sk *Secp256k1PrivateKey) (*Secp256k1Signature, error)

Secp256k1BlindSign runs the full blind protocol locally (convenience). hash must be 32 bytes (pre-hashed message).

func Secp256k1ClientUnblind

func Secp256k1ClientUnblind(state *Secp256k1ClientState, response []byte, pk *Secp256k1PublicKey) (*Secp256k1Signature, error)

Secp256k1ClientUnblind removes the blinding to produce the final signature. s' = s - α. Signature = (R'.x, s').

func (*Secp256k1Signature) Bytes

func (sig *Secp256k1Signature) Bytes() []byte

Bytes returns the 64-byte encoding: R'.x (32 bytes) || s' (32 bytes).

type Secp256k1SignerState

type Secp256k1SignerState struct {
	R secp256k1.JacobianPoint // commitment R = kG
	// contains filtered or unexported fields
}

Secp256k1SignerState holds the signer's ephemeral state for one signing session.

func Secp256k1SignerCommit

func Secp256k1SignerCommit() (*Secp256k1SignerState, []byte, error)

Secp256k1SignerCommit starts a blind signing session (Round 1). The signer picks a random nonce k and returns the commitment R = kG (33 bytes compressed).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL