red25519

package module
v0.0.0-...-1093a31 Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2026 License: MIT Imports: 8 Imported by: 0

README

red25519

Go Reference

RedDSA (randomized, re-randomizable) signature library for Go, built on Ed25519.

Overview

red25519 extends standard Ed25519 with key blinding: a 32-byte scalar blinding factor is multiplied into both the private and public key, producing a new keypair that is unlinkable to the original yet fully functional for signing and verification. This is used in the I2P network for destination blinding (encrypted leasesets, etc.).

The API mirrors crypto/ed25519, so callers can treat it as a near drop-in replacement with additional blinding primitives.

Note: Verify is intentionally stricter than crypto/ed25519.Verify: it rejects all small-order public keys (order dividing the cofactor 8), which would allow trivial or near-trivial signature forgery. BlindPublicKey also rejects small-order input points. Normal Ed25519 keypairs are unaffected.

Features

  • Ed25519 compatible — unblinded signatures are byte-identical to crypto/ed25519
  • Key blinding — blind both public and private keys with a scalar factor
  • Composable — sequential blinding with factors bf1, bf2 equals single blinding with bf1·bf2 mod ℓ
  • Constant-time — all scalar operations use filippo.io/edwards25519 (no branching on secrets)
  • Deterministic signatures — both normal and blinded signing use deterministic nonces

Install

go get github.com/go-i2p/red25519

Usage

Standard Signing (Ed25519-Compatible)
package main

import (
    "crypto/rand"
    "fmt"
    "github.com/go-i2p/red25519"
)

func main() {
    // Generate a keypair.
    pub, priv, err := red25519.GenerateKey(rand.Reader)
    if err != nil {
        panic(err)
    }

    // Sign a message.
    msg := []byte("hello, red25519")
    sig := red25519.Sign(priv, msg)

    // Verify the signature.
    ok := red25519.Verify(pub, msg, sig)
    fmt.Println("verified:", ok) // true
}
Key Blinding
package main

import (
    "crypto/rand"
    "fmt"
    "github.com/go-i2p/red25519"
)

func main() {
    // Generate a keypair and a blinding factor.
    pub, priv, _ := red25519.GenerateKey(rand.Reader)
    bf, _ := red25519.GenerateBlindingFactor(rand.Reader)

    // Blind both keys. The blinded keypair is unlinkable to the original.
    blindedPub, _ := red25519.BlindPublicKey(pub, bf)
    blindedPriv, _ := red25519.BlindPrivateKey(priv, bf)

    // Sign with blinded private key, verify with blinded public key.
    msg := []byte("blinded message")
    sig := red25519.Sign(blindedPriv, msg)
    ok := red25519.Verify(blindedPub, msg, sig)
    fmt.Println("blinded verified:", ok) // true

    // The original public key cannot verify the blinded signature.
    ok = red25519.Verify(pub, msg, sig)
    fmt.Println("original verified:", ok) // false
}

API

Function Description
GenerateKey(rand) Generate a new Ed25519 keypair
NewKeyFromSeed(seed) Derive a private key from a 32-byte seed (deterministic)
Sign(privateKey, message) Sign a message (works with normal and blinded keys)
Verify(publicKey, message, sig) Verify a signature (rejects small-order public keys)
GenerateBlindingFactor(rand) Generate a random clamped blinding factor
BlindPublicKey(pub, blind) Derive a blinded public key: A' = b·A
BlindPrivateKey(priv, blind) Derive a blinded private key: a' = a·b mod ℓ
ComposeBlindingFactors(bf1, bf2) Compose two blinding factors: bf1·bf2 mod ℓ
PrivateKey.IsBlinded() Reports whether a key was produced by BlindPrivateKey
PrivateKey.Scalar() Returns the 32-byte private scalar (derived or stored)

Dependencies

  • filippo.io/edwards25519 — constant-time Ed25519 curve operations
  • Go standard library (crypto/sha512, crypto/rand)

License

See LICENSE.

Documentation

Overview

Package red25519 implements RedDSA (randomized, re-randomizable) signatures built on Ed25519. It extends standard Ed25519 with key blinding: a 32-byte scalar blinding factor is multiplied into both the private and public key, producing a new keypair that is unlinkable to the original yet fully functional for signing and verification.

The API mirrors crypto/ed25519, so callers can treat it as a near drop-in replacement with additional BlindPublicKey, BlindPrivateKey, and BlindingFactor primitives (see blind.go).

Verify is intentionally stricter than crypto/ed25519.Verify: it rejects all small-order public keys (points whose order divides the cofactor 8), which would allow trivial or near-trivial signature forgery. Normal Ed25519 keypairs are unaffected.

This package does not implement VerifyWithOptions for Ed25519ctx or Ed25519ph (context strings and pre-hashing). These modes are outside the scope of I2P RedDSA and are not needed for destination blinding. Callers requiring Ed25519ctx/Ed25519ph should use crypto/ed25519.VerifyWithOptions directly.

Index

Constants

View Source
const (
	// PublicKeySize is the size, in bytes, of public keys.
	PublicKeySize = 32
	// PrivateKeySize is the size, in bytes, of private keys.
	PrivateKeySize = 64
	// SignatureSize is the size, in bytes, of signatures.
	SignatureSize = 64
	// SeedSize is the size, in bytes, of private key seeds,
	// which are used to derive the full private key.
	SeedSize = 32
)
View Source
const (
	// BlindingFactorSize is the size, in bytes, of blinding factors.
	BlindingFactorSize = 32
)

Variables

This section is empty.

Functions

func GenerateKey

func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error)

GenerateKey generates a public/private key pair using entropy from rand. If rand is nil, crypto/rand.Reader will be used.

func Sign

func Sign(privateKey PrivateKey, message []byte) []byte

Sign signs the message with privateKey and returns a 64-byte signature. It works with both normal keys (from GenerateKey/NewKeyFromSeed) and blinded keys (from BlindPrivateKey). For blinded keys, the pre-computed scalar is used directly and the nonce is derived from the embedded domain-separated prefix.

func Verify

func Verify(publicKey PublicKey, message []byte, sig []byte) bool

Verify reports whether sig is a valid signature of message by publicKey. It returns false for malformed inputs.

Unlike crypto/ed25519.Verify, this function rejects all small-order public keys (points whose order divides the cofactor 8), not just the identity. Small-order points allow trivial or near-trivial forgery because k·A collapses to the identity (or a predictable torsion point) for all challenges k. Normal Ed25519 keypairs are unaffected.

Types

type BlindingFactor

type BlindingFactor []byte

BlindingFactor is a 32-byte clamped Ed25519 scalar used to blind (re-randomize) keypairs. Blinding produces a new keypair that is unlinkable to the original yet algebraically related, enabling destination blinding in I2P encrypted leasesets.

func ComposeBlindingFactors

func ComposeBlindingFactors(bf1, bf2 BlindingFactor) (BlindingFactor, error)

ComposeBlindingFactors computes the scalar product of two blinding factors: result = bf1 · bf2 mod ℓ. When used with BlindPublicKey, the composed factor produces the same blinded public key as sequential blinding with bf1 then bf2:

composed, _ := ComposeBlindingFactors(bf1, bf2)
BlindPublicKey(pub, composed) == BlindPublicKey(BlindPublicKey(pub, bf1), bf2)

For private key blinding, the composed factor yields the same scalar and public key but a different deterministic nonce prefix, so signatures will differ from those produced by sequential blinding. Both are valid.

The returned BlindingFactor is a canonical scalar (reduced mod ℓ), not a clamped byte string. When passed to BlindPublicKey or BlindPrivateKey, it takes the canonical decoding path in scalarFromBlind (SetCanonicalBytes), bypassing clamping. This is correct because the Multiply output is already a valid scalar.

func GenerateBlindingFactor

func GenerateBlindingFactor(rand io.Reader) (BlindingFactor, error)

GenerateBlindingFactor generates a random blinding factor using entropy from rand. If rand is nil, crypto/rand.Reader will be used. The output is clamped per Ed25519 convention: low 3 bits cleared, bit 254 set, bit 255 cleared.

type PrivateKey

type PrivateKey []byte

PrivateKey is the type of Ed25519 private keys. It has the same layout as crypto/ed25519: 32-byte seed followed by 32-byte public key (64 bytes total).

Blinded private keys use an extended 96-byte format: scalar(32) || nonce_prefix(32) || pubkey(32). These must only be produced by BlindPrivateKey; manually constructing a 96-byte PrivateKey with invalid scalar bytes will cause Sign to panic.

func BlindPrivateKey

func BlindPrivateKey(priv PrivateKey, blind BlindingFactor) (PrivateKey, error)

BlindPrivateKey derives a blinded private key by multiplying the private scalar a by the blinding factor b: a' = a · b mod ℓ. The resulting key can be used with Sign to produce signatures verifiable against the corresponding blinded public key (from BlindPublicKey).

Invariant: BlindPublicKey(priv.Public(), bf) == BlindPrivateKey(priv, bf).Public()

The blinded key uses an extended 96-byte internal format so that Sign can detect it and use the pre-computed scalar directly (no SHA-512 re-expansion). The nonce prefix for deterministic signing is derived as SHA-512(0xFF || blind || original_prefix) for domain separation.

func NewKeyFromSeed

func NewKeyFromSeed(seed []byte) PrivateKey

NewKeyFromSeed calculates a private key from a seed. Panics if len(seed) is not SeedSize. This function is provided for interoperability with RFC 8032; for most uses, GenerateKey should be preferred.

func (PrivateKey) Equal

func (priv PrivateKey) Equal(x crypto.PrivateKey) bool

Equal reports whether priv and x have the same value. x must be of type PrivateKey; if not, Equal returns false. The comparison is constant-time.

func (PrivateKey) IsBlinded

func (priv PrivateKey) IsBlinded() bool

IsBlinded reports whether priv is a blinded key (produced by BlindPrivateKey). Blinded keys use a 96-byte internal format and have different semantics for PrivateKey.Seed and PrivateKey.Scalar.

func (PrivateKey) Public

func (priv PrivateKey) Public() crypto.PublicKey

Public returns the PublicKey corresponding to priv. Works for both normal (64-byte) and blinded (96-byte) private keys. The returned value has underlying type PublicKey.

func (PrivateKey) Scalar

func (priv PrivateKey) Scalar() []byte

Scalar returns the private scalar as a 32-byte canonical encoding. For blinded keys (produced by BlindPrivateKey), this is the stored blinded scalar a' = a·b mod ℓ. For normal keys, this is derived by expanding the seed via SHA-512 and clamping, matching the Ed25519 key derivation procedure.

The returned scalar is the secret signing exponent. Handle it with the same care as the private key itself.

func (PrivateKey) Seed

func (priv PrivateKey) Seed() []byte

Seed returns the private key seed (the first 32 bytes). For normal keys, this can be used with NewKeyFromSeed to regenerate the key.

WARNING: For blinded keys (produced by BlindPrivateKey), the first 32 bytes contain the raw scalar, not a seed. Calling NewKeyFromSeed with a blinded key's Seed will NOT recreate the blinded key — it will produce an unrelated normal key. Use PrivateKey.IsBlinded to check, and PrivateKey.Scalar to retrieve the blinded scalar explicitly.

func (PrivateKey) Sign

func (priv PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)

Sign signs the given message with priv, implementing crypto.Signer. Ed25519 performs two passes over messages to be signed and therefore cannot handle pre-hashed messages. Thus opts.HashFunc() must return zero to indicate the message hasn't been hashed. This can be achieved by passing crypto.Hash(0) as the value for opts.

type PublicKey

type PublicKey []byte

PublicKey is the type of Ed25519 public keys (32-byte compressed point).

func BlindPublicKey

func BlindPublicKey(pub PublicKey, blind BlindingFactor) (PublicKey, error)

BlindPublicKey derives a blinded public key by multiplying the public key point A by the blinding factor scalar b: A' = b · A. The result is unlinkable to the original public key without knowledge of the blinding factor.

BlindPublicKey rejects pure-torsion (small-order) input points but accepts mixed-order points (points with both a prime-order and a torsion component). For a mixed-order public key, the invariant

BlindPublicKey(pub, bf) == BlindPrivateKey(priv, bf).Public()

may not hold because BlindPrivateKey always produces a pure prime-order public key (via ScalarBaseMult). This is not a practical concern because all legitimate Ed25519 public keys (from GenerateKey, NewKeyFromSeed, or standard crypto/ed25519) are pure prime-order points.

func (PublicKey) Equal

func (pub PublicKey) Equal(x crypto.PublicKey) bool

Equal reports whether pub and x have the same value. x must be of type PublicKey; if not, Equal returns false. The comparison is constant-time.

Jump to

Keyboard shortcuts

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