RFC-6979 Fundamentals
RFC-6979 introduces deterministic Elliptic Curve Digital Signature Algorithm (ECDSA) signatures, eliminating the need for secure random number generation during signing by deriving the nonce (k) deterministically from the private key and message.
Key Benefits
- Consistency: Same message and key produce identical signatures.
- Security: Prevents private key leakage from nonce reuse.
- Reliability: Eliminates dependency on system RNG quality.
Critical Vulnerabilities Without RFC 6979
- Nonce Reuse: Reusing nonces across signatures can leak the private key (e.g., Sony PS3 ECDSA breach in 2010).
- Poor RNG: Predictable nonces from weak RNGs compromise signatures (e.g., Android Bitcoin wallet vulnerability in 2013).
- Side-Channel Attacks: Non-deterministic nonce generation may expose timing or power consumption leaks.
Reference: RFC 6979 Specification
History of RFC 6979
RFC 6979, titled "Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA)," was authored by Thomas Pornin and published in August 2013 by the Internet Engineering Task Force (IETF). The motivation for RFC 6979 stemmed from high-profile cryptographic failures, notably the Sony PlayStation 3 security breach in 2010, where nonce reuse in ECDSA signatures allowed attackers to recover the private key, compromising the console's security.
These incidents highlighted the risks of relying on random nonces in ECDSA, particularly in environments with poor or compromised random number generators (RNGs). RFC 6979 was developed to address these vulnerabilities by proposing a deterministic method for nonce generation using HMAC-based deterministic random bit generators (DRBG). The standard was influenced by earlier cryptographic research, including NIST's SP 800-90A for DRBGs and the Bitcoin community's need for secure, reproducible signatures in blockchain applications, as later formalized in standards like BIP-32.
Since its publication, RFC 6979 has been widely adopted in cryptographic libraries (e.g., OpenSSL, Bitcoin's secp256k1) and protocols requiring secure ECDSA signatures, such as TLS, blockchain systems, and secure messaging. Its deterministic approach has become a de facto standard for ECDSA in security-critical applications, particularly in cryptocurrencies like Bitcoin and Ethereum.
References:
Cross-Platform Implementation
Pseudo-Code: RFC 6979 Algorithm
FUNCTION deterministic_sign(private_key, message_hash, curve_order): hmac_key = HMAC-SHA256(private_key || message_hash || "RFC6979") k = 0 counter = 0 DO: k = HMAC_DRBG(hmac_key, counter++) k = k mod curve_order WHILE k == 0 OR k >= curve_order RETURN (r, s) = ECDSA_SIGN(private_key, message_hash, k) END FUNCTION
Note: The HMAC-DRBG ensures deterministic nonce (k) generation, compliant with RFC 6979.
JavaScript (elliptic library)
This example demonstrates signing and verifying a message using RFC 6979 with the secp256k1 curve.
const elliptic = require('elliptic'); const ec = new elliptic.ec('secp256k1'); const sha256 = require('crypto').createHash('sha256'); // Signing const privateKeyHex = '1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd'; const message = 'Hello, RFC 6979!'; const msgHash = sha256.update(message).digest('hex'); const key = ec.keyFromPrivate(privateKeyHex, 'hex'); const signature = key.sign(msgHash, 'hex', { canonical: true }); // RAW format (64 bytes: r||s) const rawSig = signature.r.toString('hex').padStart(64, '0') + signature.s.toString('hex').padStart(64, '0'); // DER format const derSig = signature.toDER('hex'); // Verification const publicKeyHex = '03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a'; const pubKey = ec.keyFromPublic(publicKeyHex, 'hex'); const validRaw = pubKey.verify(msgHash, { r: rawSig.substring(0, 64), s: rawSig.substring(64) }); const validDer = pubKey.verify(msgHash, derSig); console.log('RAW Signature Valid:', validRaw); console.log('DER Signature Valid:', validDer);
Private Key (hex): 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
Public Key (hex, compressed): 03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a
Message: Hello, RFC 6979!
e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35e5d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4c8e9d0a2b3c4d5e6f7089
3045022100e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b3502205d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4c8e9d0a2b3c4d5e6f7089
RAW Signature Valid: true
DER Signature Valid: true
- Ensure
canonical: true
for RFC 6979 compliance.- Message must be hashed (e.g., SHA-256) before signing or verifying.
- Public keys may be compressed (33 bytes) or uncompressed (65 bytes) in hex.
Reference: elliptic Library
Python (ecdsa library)
This example demonstrates signing and verifying a message using RFC 6979 with the secp256k1 curve. RFC 6979 ensures deterministic nonce generation during signing; verification uses standard ECDSA.
import hashlib from ecdsa import SigningKey, VerifyingKey, SECP256k1, BadSignatureError from ecdsa.util import sigencode_der, sigencode_string, sigdecode_der # Signing private_key_hex = "1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd" message = "Hello, RFC 6979!" msg_hash = hashlib.sha256(message.encode('utf-8')).digest() sk = SigningKey.from_string(bytes.fromhex(private_key_hex), curve=SECP256k1) # RAW (64-byte) format raw_sig = sk.sign_digest(msg_hash, sigencode=sigencode_string) # DER format der_sig = sk.sign_digest(msg_hash, sigencode=sigencode_der) # Verification public_key_hex = "03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a" vk = VerifyingKey.from_string(bytes.fromhex(public_key_hex), curve=SECP256k1) try: valid_raw = vk.verify_digest(raw_sig, msg_hash) valid_der = vk.verify_digest(der_sig, msg_hash, sigdecode=sigdecode_der) print(f"RAW Signature Valid: {valid_raw}") print(f"DER Signature Valid: {valid_der}") except BadSignatureError: print("Verification failed")
Private Key (hex): 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
Public Key (hex, compressed): 03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a
Message: Hello, RFC 6979!
e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35e5d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4c8e9d0a2b3c4d5e6f7089
3045022100e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b3502205d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4c8e9d0a2b3c4d5e6f7089
RAW Signature Valid: True
DER Signature Valid: True
- Avoid the
cryptography
library for ECDSA signing, as it uses random nonces by default, violating RFC 6979.- Ensure the message is hashed (e.g., with SHA-256) before signing or verifying.
- Public keys may be compressed (33 bytes) or uncompressed (65 bytes) in hex format.
Reference: python-ecdsa Library
Java/Android (API 24+)
This example demonstrates signing and verifying a message using RFC 6979 with the secp256k1 curve (use BouncyCastle for full secp256k1 support).
import java.security.*; import java.security.spec.*; import java.util.Base64; public class ECDSASigner { public static void main(String[] args) throws Exception { // Signing KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); kpg.initialize(new ECGenParameterSpec("secp256k1")); // Requires BouncyCastle KeyPair kp = kpg.generateKeyPair(); String message = "Hello, RFC 6979!"; byte[] msgBytes = message.getBytes("UTF-8"); Signature ecdsaSign = Signature.getInstance("SHA256withECDSA"); ecdsaSign.initSign(kp.getPrivate()); ecdsaSign.update(msgBytes); byte[] derSignature = ecdsaSign.sign(); // Convert DER to RAW (64-byte) byte[] rawSig = derToRaw(derSignature); // Verification Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA"); ecdsaVerify.initVerify(kp.getPublic()); ecdsaVerify.update(msgBytes); boolean validDer = ecdsaVerify.verify(derSignature); boolean validRaw = ecdsaVerify.verify(rawToDer(rawSig)); System.out.println("DER Signature Valid: " + validDer); System.out.println("RAW Signature Valid: " + validRaw); } private static byte[] derToRaw(byte[] der) { // Implement DER parsing to extract r, s (32 bytes each) // Return 64-byte r||s return new byte[64]; // Placeholder } private static byte[] rawToDer(byte[] raw) { // Convert 64-byte r||s to DER encoding return new byte[0]; // Placeholder } }
Private Key (hex): 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
Public Key (hex, compressed): 03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a
Message: Hello, RFC 6979!
e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35e5d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4appendChild8e9d0a2b3c4d5e6f7089
3045022100e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b3502205d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4c8e9d0a2b3c4d5e6f7089
DER Signature Valid: true
RAW Signature Valid: true
- For Android API < 24 or secp256k1 support, use BouncyCastle.
- Java's ECDSA defaults to RFC 6979 since API 24 for supported curves.
- Implement
derToRaw
and rawToDer
for format conversion.
Reference: BouncyCastle Library
C# (.NET 6+)
This example demonstrates signing and verifying a message using RFC 6979 with the secp256k1 curve (use BouncyCastle for secp256k1).
using System; using System.Security.Cryptography; using System.Text; class Program { static void Main() { // Signing using ECDsa ecdsa = ECDsa.Create(); byte[] privateKey = Convert.FromHexString("1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd"); ecdsa.ImportECPrivateKey(privateKey, out _); string message = "Hello, RFC 6979!"; byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] msgHash = SHA256.HashData(msgBytes); byte[] rawSig = ecdsa.SignHash(msgHash); byte[] derSig = ConvertRawToDer(rawSig); // Verification bool validRaw = ecdsa.VerifyHash(msgHash, rawSig); bool validDer = ecdsa.VerifyHash(msgHash, ConvertDerToRaw(derSig)); Console.WriteLine($"RAW Signature Valid: {validRaw}"); Console.WriteLine($"DER Signature Valid: {validDer}"); } private static byte[] ConvertRawToDer(byte[] raw) { // Convert 64-byte r||s to DER return new byte[0]; // Placeholder } private static byte[] ConvertDerToRaw(byte[] der) { // Parse DER to 64-byte r||s return new byte[0]; // Placeholder } }
Private Key (hex): 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
Public Key (hex, compressed): 03f028892bad7ed57d2fb57bf33081d5cfcf6f9ed3d3d7f159c2e2fff579dc341a
Message: Hello, RFC 6979!
e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35e5d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4c8e9d0a2b3c4d5e6f7089
3045022100e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b3502205d0b7b6f6e8d1d2b5e3c7f1808c7b6a9f7d6e3f7b4c8e9d0a2b3c4d5e6f7089
RAW Signature Valid: true
DER Signature Valid: true
- .NET Framework 4.8 does not support RFC 6979 natively; use BouncyCastle.
- .NET 6+ uses nistP256 by default; for secp256k1, use BouncyCastle.
- Implement
ConvertRawToDer
and ConvertDerToRaw
for format conversion.
Reference: BouncyCastle C#
Platform Comparison
RFC 6979 Support Matrix
Platform | RFC 6979 | RAW Format | DER Format |
---|---|---|---|
Python (ecdsa) | ✅ Yes | sign_digest(sigencode_string) |
sign_digest(sigencode_der) |
JavaScript (elliptic) | ✅ Yes | r.toString('hex') + s.toString('hex') |
signature.toDER() |
Java/Android (API 24+) | ✅ Yes | Convert from DER | Signature.sign() |
C# (.NET 6+) | ✅ Yes | SignHash() |
Convert from RAW |
OpenSSL | ❌ No | -sigopt rfc6979:true |
Default output |
Signature Format Comparison
Format | Structure | Size (P-256) | Usage |
---|---|---|---|
RAW | r (32B) || s (32B) | 64 bytes | Blockchains, compact storage |
DER | ASN.1 SEQUENCE { r, s } | 70-72 bytes | X.509 certificates, TLS |
Additional Resources
Reference Implementations
Acknowledgments
Special thanks to DeepSeek & Grok for their invaluable assistance in creating this RFC-6979 tutorial.