Skip to content

ECDSA vs. Schnorr Signatures a Battle

Digital signatures represent the backbone of modern cryptographic systems, serving as the building blocks for secure communication, blockchain transactions, and even verification of digital identity. We are going to examine two such digital signature algorithms, namely, the wide-use Elliptic Curve Digital Signature Algorithm (ECDSA) and the newer gaining popularity Schnorr signature scheme.

Introduction to Digital Signatures

Consider sending a letter in the physical world. You sign the letter with your hand-written signature to prove that it came from you. In the digital world, digital signatures serve the same purpose but with mathematical precision and cryptographic security. They provide three essential properties for messages:

  1. Authentication: Proving who created or sent a message
  2. Non-repudiation: The sender cannot deny sending the message
  3. Integrity: Verifying the message has not been tampered with

These attributes have made digital signatures quintessential for anything ranging from cryptocurrency transactions and authentication of software updates to the security of messaging systems.

Mathematical Grounding

Let’s briefly introduce the mathematics underlying both ECDSA and Schnorr signatures.

Mathematics Behind Elliptic Curves

The basis of both schemes consists of elliptic curves described by the equation:

y² = x³ + ax + b (mod p)

with:

  • a and b are curve parameters
  • Points on this curve form a cyclic group under point addition

Important Mathematical Properties:

  1. Point Addition: P + Q = R where P, Q and R are points on the curve
  2. Scalar Multiplication: kP = P + P +. + P (k times)
  3. Discrete Log Problem: Given P and Q = kP then it is computationally infeasible to find k

Group Properties

Both schemes are based on the following:

  • A cyclic group G of prime order n
  • A generator point G
  • A point at infinity O (neutral element)

Understanding ECDSA

Over the years, ECDSA has been the workhorse of digital signatures in blockchain and other cryptographic systems. It is an extension from the mathematical properties of elliptic curves toward producing secure signatures that are relatively small in size compared to other systems such as RSA.

Mathematical Formulation of ECDSA

  1. Key Generation:
  • Private key: Random integer d ∈ [1, n-1]
  • Public key: Q = dG
  1. Signature Generation:
    For a message m:
  • Generate random k ∈ [1, n-1]
  • Compute R = kG = (x₁, y₁)
  • r = x₁ mod n
  • s = k⁻¹(H(m) + dr) mod n
  • Signature is: (r, s)
  1. Signature Verification:
  • Compute w = s⁻¹ mod n
  • u₁ = H(m)w mod n
  • u₂ = rw mod n
  • Compute point: (x₁, y₁) = u₁G + u₂Q
  • Check r = x₁ mod n

Security Proof Sketch:

The security of ECDSA is based on the:

  1. The elliptic curve group discrete logarithm problem
  2. The random oracle model of hash function H
  3. The signature scheme security against chosen message attack

How ECDSA Works

In the signature scheme of ECDSA, the signature process is achieved as follows:

  1. Key Generation
  • Generate a random private key (a large number)
  • This private key multiplied by the generator point on the elliptic curve yields the public key
  1. Signing
  • Hash of a message
  • Generate a random number (nonce)
  • Create the signature from the combination of message hash, private key, and nonce
  • The signature would consist of two values: r and s
  1. Verification
  • Using the public key, message hash, and signature values
  • Perform elliptic curve operations to verify the signature’s validity

ECDSA’s strength lies in its widespread adoption and battle-tested implementation. Bitcoin and Ethereum both traditionally use ECDSA for transaction signatures. However, it does have some limitations:

  • Nonce Reuse: Using the same nonce for different signatures can compromise the private key
  • Signature Malleability: The signature can be slightly altered and still be valid
  • Complex Multi-signature Implementation: Combining multiple signatures is not straightforward

Understanding Schnorr Signatures

Schnorr signatures, though actually invented before it, have recently gained renewed attention due to their elegant mathematical properties and some very practical advantages. They were initially patented, which limited their widespread adoption, but are now freely available and find increasing implementation in modern systems.

Mathematical Formulation of Schnorr Signatures

  1. Key Generation:
  • Private key: Random integer x ∈ [1, n-1]
  • Public key: P = xG
  1. Signature Generation:
    For a message m:
  • Generate random k ∈ [1, n-1]
  • Calculate R = kG
  • Calculate e = H(R || P || m)
  • Calculate s = k + ex mod n
  • Signature is: (R, s)
  1. Signature Verification:
  • Compute: e = H(R || P || m)
  • Check: sG = R + eP

Security Proof:

Schnorr signatures provide stronger security guarantees through:

  1. Provable security in the algebraic group model
  2. Reduction to the discrete logarithm problem
  3. Linear construction enabling easier security proofs

The Schnorr Advantage

Schnorr signatures work similarly to ECDSA but with some key improvements:

  1. Simpler Mathematics
  • Linear relationship between components makes it easier to prove security
  • More straightforward implementation reduces the risk of bugs
  1. Native Multi-signature Support
  • Multiple signatures can be aggregated into a single signature
  • This saves space and improves privacy in blockchain applications
  1. Batch Verification
  • More signatures are verified much faster individually
  • Particularly useful for verification on blockchain nodes that have to verify plenty of signatures

Recently, the Bitcoin network adopted the Schnorr signature with the Taproot upgrade for efficient and private smart contract execution.

Detailed Comparison

FeatureECDSASchnorr Signatures
Mathematical Properties
Security BasisDiscrete Log ProblemDiscrete Log Problem
Signature Size64 bytes (r,s)64 bytes (R,s)
LinearityNon-linear signature equationLinear signature equation
Provable SecurityComplex security proofSimpler security proof
Implementation Characteristics
Nonce RequirementsStrict (reuse breaks security)Strict (reuse breaks security)
Implementation ComplexityMore complexSimpler
Side-Channel Attack ResistanceRequires careful implementationBetter inherent resistance
Advanced Features
Multi-signature SupportComplex to implementNative support
Signature AggregationLimitedEfficient
Batch VerificationLess efficientMore efficient
Practical Aspects
Adoption LevelWidely adoptedGrowing adoption
StandardizationWell-standardizedNewer standards emerging
CompatibilityExtensive ecosystemLimited but growing
Performance
Verification SpeedBase performance~15% faster
Signature Generation SpeedBase performanceSimilar
Memory RequirementsBase requirementSlightly lower
Security Properties
MalleabilityMalleableNon-malleable
Key Prefixing RequirementsRequired for securityNot required
Replay Attack ProtectionRequires additional measuresBuilt-in protection

Key Differences and Practical Implications

Looking into the differences between ECDSA and Schnorr signatures, a few key factors come into play:

ECDSA vs Schnorr

Security

  • Schnorr signatures provide provable security based on standard cryptographic assumptions
  • ECDSA has more complicated security proofs, depending on stronger assumptions
  • For practical applications today, both are secure

Efficiency

  • Schnorr signatures are typically smaller in size
  • Verification of Schnorr signatures can be batched more efficiently
  • ECDSA requires more complex calculations for signature verification

Implementation

  • ECDSA is more widely supported in existing systems
  • Schnorr signatures are easier to implement correctly
  • Schnorr’s linear nature makes it more suitable for advanced cryptographic protocols

Implementation Examples

ECDSA Implementation

Here’s a practical implementation of ECDSA using Python’s fastecdsa library:

from fastecdsa import keys, curve, ecdsa
from hashlib import sha256

def generate_ecdsa_keypair():
    """Generate ECDSA key pair."""
    private_key = keys.gen_private_key(curve.secp256k1)
    public_key = keys.get_public_key(private_key, curve.secp256k1)
    return private_key, public_key

def sign_ecdsa(message: str, private_key: int) -> tuple:
    """
    Sign a message using ECDSA.

    Args:
signature The message to be signed
        private_key - The private key for signing
        #       Returns:
        tuple -      (r,s) signature parts
    """"""
    message_hash = int(sha256(message.encode('utf-8')).hexdigest(), 16)
r, s = ecdsa.sign(message_hash, private_key, curve=curve.secp256k1)
return r, s
def verify_ecdsa(message: str, signature: tuple, public_key) -> bool:
    """"
    Verify an ECDSA signature.

    Args:
        message: The original message
        signature: (r, s) signature components
public_key: The public key for verification

    Returns:
        bool: True if signature is valid, False otherwise
    """
    message_hash = int(sha256(message.encode('utf-8')).hexdigest(), 16)
    r, s = signature
    return ecdsa.verify(signature, message_hash, public_key, curve=curve.secp256k1)

# Example usage
def ecdsa_example():
    # Generate keypair
    private_key, public_key = generate_ecdsa_keypair()

    # Sign a message
    message = "Hello, Blockchain!"
    signature = sign_ecdsa(message, private_key)

    # Verify the signature
    is_valid = verify_ecdsa(message, signature, public_key)
    return is_valid

Schnorr Implementation

Here is an example implementation of Schnorr signatures in Python. For ease of understanding, this is implemented in a simplified manner and not for production use:

from hashlib import sha256
import random
from fastecdsa.curve import secp256k1
from fastecdsa.point import Point

class SchnorrSignature:
    def __init__(self):
        self.curve = secp256k1
        self.G = self.curve.G
        self.n = self.curve.q

    def generate_keypair(self):
"""Generate Schnorr key pair."""
        private_key = random.randrange(1, self.n)
        public_key = private_key * self.G
        return private_key, public_key

    def hash_point(self, point: Point) -> int:
        """Hash an elliptic curve point."""
return int(sha256(f"{point.x},{point.y}".encode()).hexdigest(), 16)

    def hash_message(self, R: Point, P: Point, message: str) -> int:
        """Hash the commitment, public key and message."""
        message_bytes = message.encode('utf-8')
        hash_input = f"{R.x},{R.y},{P.x},{P.y},{message_bytes.hex()}".encode()
return int(sha256(hash_input).hexdigest(), 16) % self.n

    def sign(self, message: str, private_key: int, public_key: Point) -> tuple:
        """
        Create a Schnorr signature.

        Args:
message: Message to sign
            private_key: Private key
            public_key: Public key (P = x*G)

        Returns:
tuple: (R, s) signature components
        """
        # Generate random nonce
        k = random.randrange(1, self.n)
        R = k * self.G
# Calculate challenge
        e = self.hash_message(R, public_key, message)

        # Calculate signature
        s = (k + e * private_key) % self.n
return (R, s)

    def verify(self, message: str, signature: tuple, public_key: Point) -> bool:
        """
        Verify a Schnorr signature.

        Args:
message: Original message
            signature: (R, s) signature components
            public_key: Public key for verification

        Returns:
bool: True if signature is valid
        """
        R, s = signature

        # Compute challenge
        e = self.hash_message(R, public_key, message)
# Verify signature equation: sG = R + eP
        left_side = s * self.G
        right_side = R + e * public_key
return left_side == right_side

# Example usage
def schnorr_example():
    schnorr = SchnorrSignature()

    # Generate keypair
    private_key, public_key = schnorr.generate_keypair()

    # Sign a message
    message = "Hello, Blockchain!"
    signature = schnorr.sign(message, private_key, public_key)
# Verify the signature
    is_valid = schnorr.verify(message, signature, public_key)
    return is_valid

Batch Verification Example

Below is an example of how Schnorr signatures enable efficient batch verification:

def batch_verify_schnorr(messages: list, signatures: list, public_keys: list) -> bool:
    """
    Batch verify multiple Schnorr signatures.
    
    Args:
        messages: List of messages
        signatures: List of signatures (R, s)
        public_keys: List of public keys
        
    Returns:
        bool: True if all signatures are valid
    """
    schnorr = SchnorrSignature()
    n = len(messages)
    
    # Generate random weights
    weights = [random.randrange(1, schnorr.n) for _ in range(n)]
    
    # Combine signatures
    combined_s = sum(w * s for w, (_, s) in zip(weights, signatures)) % schnorr.n
    combined_R = sum(w * R for w, (R, _) in zip(weights, signatures))
    
    # Combine public keys and challenges
    combined_P = Point(0, 0, curve=secp256k1)  # Identity point
    for i in range(n):
        e = schnorr.hash_message(signatures[i][0], public_keys[i], messages[i])
        combined_P = combined_P + (weights[i] * e * public_keys[i])
    
    # Verify batch
    return combined_s * schnorr.G == combined_R + combined_P

Implementation Considerations

While implementing these signature schemes in production, consider the following:

  1. Secure Random Number Generation
  • Use cryptographically secure random number generators
  • Never reuse nonces (k values)
  • Implement deterministic nonce generation RFC 6979

2. Side Channel Attack Prevention

  • Use constant time operations
  • Avoid conditionals based on secret values
  • Use secure memory handling

3. Error Handling

  • Always validate inputs
  • Handle edge cases well (infinity points, zero values)
  • Instead use appropriate error handling.

4. Performance Optimization

  • Whenever possible, use optimized libraries for curve operations.
  • When appropriate, apply batching verification.
  • If highly performant on a large scale, use hardware acceleration.

Which One Should You Use?

Use one or the other according to your specific requirements:

ECDSA if you require:

  • wide compatibility with most of the current systems
  • You are working with established blockchain platforms that require ECDSA
  • Your implementation needs to be immediately compatible with existing tools

Choose Schnorr signatures if:

  • You’re building a new system with multi-signature requirements
  • Efficiency and signature size are critical concerns
  • You need advanced features like signature aggregation
  • You’re implementing new blockchain or cryptographic protocols

Looking to the Future

The cryptographic community increasingly favors Schnorr signatures for new implementations due to the following reasons:

  • Greater traction within large blockchain networks
  • A general trend toward better privacy and efficiency in cryptographic schemes
  • The requirement for more flexible signature schemes in advanced protocols

However, ECDSA will still be relevant due to its extensive deployment and continued support by major systems.

Conclusion

Both of them are powerful cryptographic tools that have their merits. While ECDSA has been the backbone of many cryptographic systems, Schnorr signatures are the future, with their elegant design and advanced features. Moving forward, we are likely to see increased adoption of Schnorr signatures, especially in new systems and protocols that can benefit from the advantages they bring.

In this case, the choice between them should be guided by your specific needs: compatibility with existing systems, efficiency requirements, and the need for advanced features like signature aggregation. Understanding both algorithms and their trade-offs enables you to make informed decisions in your cryptographic implementations.

Remember, cryptographic security is a factor not only in the choice of algorithm, but also in its proper implementation and correct key management, along with system design as a whole. Follow best practices and, when possible, take the advice of security experts for production deployment.