Skip to main content

Try it Live

Run Secp256k1 examples in the interactive playground
This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.

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)