Skip to main content

Secp256k1 Test Vectors

Comprehensive test vectors from official standards to verify implementation correctness.

RFC 6979 Deterministic ECDSA

Test vectors from RFC 6979 Section A.2.5 (secp256k1 + SHA-256).

Test Case 1: “sample”

Private key:
0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721
Message: “sample” (UTF-8 encoded) Message hash (SHA-256):
0xAF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF
Expected signature:
{
  r: 0xEFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716,
  s: 0xF7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8
}
Verification:
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { sha256 } from '@noble/hashes/sha2.js';

const privateKey = hexToBytes('C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721');
const message = new TextEncoder().encode('sample');
const messageHash = sha256(message);

const signature = secp256k1.sign(messageHash, privateKey);

assert(signature.r === 0xEFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716n);
assert(signature.s === 0xF7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8n);

Test Case 2: “test”

Private key:
0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721
Message: “test” (UTF-8 encoded) Message hash (SHA-256):
0x9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08
Expected signature:
{
  r: 0xF1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367,
  s: 0x019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083
}

IETF Test Vectors

Private Key = 1

Private key:
0x0000000000000000000000000000000000000000000000000000000000000001
Public key (uncompressed):
x: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
y: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
This is the secp256k1 generator point G. Verification:
const privateKey = Bytes32();
privateKey[31] = 1;

const publicKey = Secp256k1.derivePublicKey(privateKey);

const expectedX = hexToBytes('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798');
const expectedY = hexToBytes('483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8');

assert(publicKey.slice(0, 32).every((b, i) => b === expectedX[i]));
assert(publicKey.slice(32, 64).every((b, i) => b === expectedY[i]));

Private Key = n - 1

Private key (SECP256K1_N - 1):
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140
Public key (uncompressed):
x: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
y: 0xB7C52588D95C3B9AA25B0403F1EEF75702E84BB7597AABE663B82F6F04EF2777
This is -G (negation of generator point). Verification:
const privateKey = hexToBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140');
const publicKey = Secp256k1.derivePublicKey(privateKey);

const expectedX = hexToBytes('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798');
const expectedY = hexToBytes('B7C52588D95C3B9AA25B0403F1EEF75702E84BB7597AABE663B82F6F04EF2777');

assert(publicKey.slice(0, 32).every((b, i) => b === expectedX[i]));
assert(publicKey.slice(32, 64).every((b, i) => b === expectedY[i]));

Ethereum Test Vectors

Transaction Signature

Transaction (legacy format):
{
  nonce: 9n,
  gasPrice: 20000000000n,
  gasLimit: 21000n,
  to: '0x3535353535353535353535353535353535353535',
  value: 1000000000000000000n,
  data: new Uint8Array(),
}
Private key:
0x4646464646464646464646464646464646464646464646464646464646464646
RLP-encoded transaction (for signing):
0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080808080
Transaction hash (Keccak256):
0x5c207a65c1a3d6d52f0c8b5c9e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e6e
Expected signature:
{
  v: 37,  // chainId=1, recoveryId=0: v = 1*2 + 35 + 0
  r: 0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276,
  s: 0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83
}

EIP-191 Personal Sign

Message: “Hello Ethereum” Prefixed message:
"\x19Ethereum Signed Message:\n14Hello Ethereum"
Message hash (Keccak256):
0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede
Private key:
0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
Expected signature:
{
  v: 28,
  r: 0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a1,
  s: 0x2d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee
}
Recovered address:
0x14791697260E4c9A71f18484C9f997B308e59325

Edge Cases

All-Zero Hash

Private key:
0x0000000000000000000000000000000000000000000000000000000000000001
Message hash (all zeros):
0x0000000000000000000000000000000000000000000000000000000000000000
Expected signature:
{
  r: 0x4c11c8e41c7c05b0d10c802b5ff0e4d7a4c39df8c6e7d43cfc43eb9a13e9b3da,
  s: 0x4c11c8e41c7c05b0d10c802b5ff0e4d7a4c39df8c6e7d43cfc43eb9a13e9b3da
}

All-Ones Hash

Private key:
0x0000000000000000000000000000000000000000000000000000000000000001
Message hash (all ones):
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Expected signature:
{
  r: 0x3c5bbf7a3c9be3e3e6b5f0b5a9b8f8e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5,
  s: 0x3c5bbf7a3c9be3e3e6b5f0b5a9b8f8e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5
}

Malleability Tests

Low-s Enforcement

Original signature:
const signature = {
  r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n,
  s: 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0n, // > n/2
  v: 27
};
Normalized (low-s):
const normalized = {
  r: 0xefd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716n,
  s: 0x0000000000000000000000000000000000000000000000000000000000000001n, // n - s
  v: 28  // Recovery ID flipped
};
Verification:
// High-s should be rejected
assert(Secp256k1.verify(signature, hash, publicKey) === false);

// Low-s should verify
assert(Secp256k1.verify(normalized, hash, publicKey) === true);

Invalid Input Tests

Invalid Private Keys

// Zero key
const zeroKey = Bytes32();
expect(() => Secp256k1.sign(hash, zeroKey)).toThrow('InvalidPrivateKeyError');

// Key = n (curve order)
const nKey = hexToBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141');
expect(() => Secp256k1.sign(hash, nKey)).toThrow('InvalidPrivateKeyError');

// Key > n
const tooBigKey = Bytes32().fill(0xff);
expect(() => Secp256k1.sign(hash, tooBigKey)).toThrow('InvalidPrivateKeyError');

// Wrong length
const shortKey = Bytes16();
expect(() => Secp256k1.sign(hash, shortKey)).toThrow('InvalidPrivateKeyError');

Invalid Signatures

// r = 0
const zeroR = { r: Bytes32(), s: validS, v: 27 };
assert(Secp256k1.verify(zeroR, hash, publicKey) === false);

// s = 0
const zeroS = { r: validR, s: Bytes32(), v: 27 };
assert(Secp256k1.verify(zeroS, hash, publicKey) === false);

// r >= n
const bigR = { r: Bytes32().fill(0xff), s: validS, v: 27 };
assert(Secp256k1.verify(bigR, hash, publicKey) === false);

// s >= n
const bigS = { r: validR, s: Bytes32().fill(0xff), v: 27 };
assert(Secp256k1.verify(bigS, hash, publicKey) === false);

Invalid Public Keys

// Point not on curve
const invalidPoint = Bytes64().fill(0x01);
expect(() => Secp256k1.verify(sig, hash, invalidPoint)).toThrow('InvalidPublicKeyError');

// Wrong length
const shortKey = Bytes32();
expect(() => Secp256k1.verify(sig, hash, shortKey)).toThrow('InvalidPublicKeyError');

// Point at infinity
const infinity = Bytes64();
expect(() => Secp256k1.verify(sig, hash, infinity)).toThrow('InvalidPublicKeyError');

Cross-Implementation Verification

Noble vs OpenSSL

Test that our TypeScript implementation (using @noble/curves) matches OpenSSL results:
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { execSync } from 'child_process';

const privateKey = crypto.getRandomValues(Bytes32());
const messageHash = crypto.getRandomValues(Bytes32());

// Sign with @noble
const nobleSig = secp256k1.sign(messageHash, privateKey);

// Verify with OpenSSL
const publicKey = secp256k1.getPublicKey(privateKey);
const result = execSync(`openssl dgst -verify pubkey.pem -signature sig.der message.bin`);

assert(result.toString().includes('Verified OK'));

Ethereum Clients

Test vectors used by Go-Ethereum, Nethermind, etc: Geth test vector:
{
  "privateKey": "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8ff4e1e7a7e5e8c5b",
  "message": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "expectedR": "0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276",
  "expectedS": "0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83",
  "expectedV": 27
}

Performance Benchmarks

Expected performance ranges for reference implementations:
// Signing (1000 iterations)
// @noble/curves: 500-800ms (1.25-2ms per sig)
// libsecp256k1: 200-400ms (0.5-1ms per sig)
// OpenSSL: 300-500ms (0.75-1.25ms per sig)

// Verification (1000 iterations)
// @noble/curves: 800-1200ms (2-3ms per verify)
// libsecp256k1: 400-600ms (1-1.5ms per verify)
// OpenSSL: 500-800ms (1.25-2ms per verify)

// Public key derivation (1000 iterations)
// @noble/curves: 400-600ms (1-1.5ms per key)
// libsecp256k1: 150-300ms (0.38-0.75ms per key)