> ## Documentation Index
> Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Signing

> ECDSA signing with secp256k1 using deterministic RFC 6979 nonces

<Card title="Try it Live" icon="play" href="https://playground.tevm.sh?example=crypto/secp256k1.ts">
  Run Secp256k1 examples in the interactive playground
</Card>

<Warning>
  **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.
</Warning>

## Examples

* [Sign Message](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/sign-message.ts) - Sign Keccak256 hash with ECDSA
* [Sign Transaction](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/sign-transaction.ts) - Sign Ethereum transaction
* [Compact Signature](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/compact-signature.ts) - 65-byte compact format
* [Signature Bytes](https://github.com/evmts/voltaire/blob/main/playground/src/examples/crypto/secp256k1/signature-bytes.ts) - Serialize signatures

# Secp256k1 Signing

Sign message hashes with secp256k1 using deterministic ECDSA (RFC 6979). Ethereum uses secp256k1 signatures for all transaction authorization and authentication.

## Overview

ECDSA (Elliptic Curve Digital Signature Algorithm) signing computes a signature (r, s, v) from:

* **Message hash** (32 bytes) - Keccak256 of transaction or message
* **Private key** (32 bytes) - Secret scalar (0 \< key \< curve order)

The signature proves knowledge of the private key without revealing it. RFC 6979 deterministic nonce generation ensures identical signatures for same message+key pairs, preventing catastrophic nonce reuse.

## API

### `sign(messageHash, privateKey)`

Sign a 32-byte message hash with deterministic ECDSA.

**Parameters:**

* `messageHash` (`HashType`) - 32-byte hash to sign
* `privateKey` (`Uint8Array`) - 32-byte private key (0 \< key \< n)

**Returns:** `BrandedSignature`

```typescript theme={null}
{
  r: Uint8Array,  // 32 bytes
  s: Uint8Array,  // 32 bytes (low-s enforced)
  v: number       // 27 or 28 (Ethereum format)
}
```

**Throws:**

* `InvalidPrivateKeyError` - Key wrong length, zero, or >= curve order
* `Secp256k1Error` - Signing operation failed

**Example:**

```typescript theme={null}
import * as Secp256k1 from '@tevm/voltaire/Secp256k1';
import { Keccak256 } from '@tevm/voltaire/Keccak256';

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

const message = Keccak256.hashString('Hello, Ethereum!');
const signature = Secp256k1.sign(message, privateKey);

console.log(signature.v); // 27 or 28
console.log(signature.r.length); // 32
console.log(signature.s.length); // 32
```

## Algorithm Details

### ECDSA Signature Generation

1. **Hash message**: `e = hash(message)` (typically Keccak256)
2. **Generate nonce** (RFC 6979): `k = HMAC_DRBG(private_key, message_hash)` (deterministic)
3. **Calculate point**: `R = k * G` (scalar multiplication of generator)
4. **Compute r**: `r = R.x mod n` (x-coordinate of R)
5. **Compute s**: `s = k^-1 * (e + r * private_key) mod n`
6. **Normalize s**: If `s > n/2`, set `s = n - s` (low-s malleability fix)
7. **Calculate v**: Recovery ID (0 or 1) + 27 for Ethereum compatibility

### RFC 6979 Deterministic Nonces

**Why deterministic?** Random nonce generation is dangerous:

* **Nonce reuse** with different messages leaks the private key
* **Weak randomness** (bad RNG) enables key recovery attacks
* **Implementation bugs** in random generation are common

RFC 6979 derives nonces deterministically:

```
k = HMAC_DRBG(key: private_key, data: message_hash, hash: SHA-256)
```

Benefits:

* **No RNG required** - Eliminates entropy source vulnerabilities
* **Reproducible** - Same message + key = same signature (testable)
* **Secure** - Nonce is cryptographically derived from secrets
* **Standard** - RFC 6979 widely adopted (Bitcoin, Ethereum, etc.)

**Critical:** Never implement custom nonce generation. Use RFC 6979.

## Signature Components

### r (32 bytes)

The x-coordinate of the ephemeral public key `R = k * G`:

* Must be in range `[1, n-1]` where n is curve order
* Derived from deterministic nonce k
* Acts as a commitment to the nonce

### s (32 bytes)

The proof that binds the signature to the private key:

* Computed as `s = k^-1 * (e + r * private_key) mod n`
* Must be in range `[1, n-1]`
* **Low-s enforced**: If `s > n/2`, signature uses `n - s` instead
* Low-s prevents signature malleability (BIP 62, EIP-2)

### v (recovery ID)

Ethereum-specific value for public key recovery:

* **Standard**: 0 or 1 (which of two possible y-coordinates)
* **Ethereum**: 27 or 28 (v = recovery\_id + 27)
* **EIP-155 (replay protection)**: v = chain\_id \* 2 + 35 + recovery\_id
* Enables `ecRecover` precompile to extract public key from signature

## Security Considerations

### Critical Warnings

⚠️ **NEVER reuse nonces**: Reusing k with different messages leaks the private key. RFC 6979 prevents this - do not override.

⚠️ **Validate private keys**: Keys must be 32 bytes and satisfy `0 < key < n`. Zero keys and keys >= n are invalid.

⚠️ **Use cryptographically secure random**: For private key generation, use `crypto.getRandomValues()` or similar CSPRNG. Never use `Math.random()`.

⚠️ **Protect private keys**: Store keys in secure hardware (HSM, Secure Enclave) when possible. Never log or transmit unencrypted keys.

⚠️ **Hash before signing**: Sign hashes, not raw messages. Ethereum signs Keccak256 hashes of RLP-encoded transactions.

### Low-s Malleability

ECDSA signatures have an inherent malleability: if (r, s) is valid, so is (r, n - s). Both verify correctly but produce different signature bytes.

**Problem:** Malleability enables:

* Transaction replay with modified signature
* Smart contract exploit via signature verification bypass
* Blockchain state inconsistency

**Solution:** Enforce low-s (s ≤ n/2):

```typescript theme={null}
if (s > CURVE_ORDER / 2n) {
  s = CURVE_ORDER - s;
  v ^= 1; // Flip recovery ID
}
```

Ethereum requires low-s (BIP 62, EIP-2). Our implementation automatically normalizes.

### Side-Channel Resistance

Timing attacks can leak private keys through:

* **Non-constant-time modular arithmetic** - Branch timing leaks bit values
* **Cache timing** - Memory access patterns reveal secrets
* **Power analysis** - CPU power consumption correlates with operations

**Mitigations:**

* TypeScript: Uses `@noble/curves` (constant-time operations)
* Zig: ⚠️ **NOT constant-time** - Custom implementation unaudited
* Production: Use audited libraries or hardware wallets

## Test Vectors

### RFC 6979 Deterministic Signatures

```typescript theme={null}
// Private key = 1
const privateKey = Bytes32();
privateKey[31] = 1;

// Message hash (SHA-256 of "hello world")
const messageHash = Hash(
  sha256(new TextEncoder().encode("hello world"))
);

// Sign twice
const sig1 = Secp256k1.sign(messageHash, privateKey);
const sig2 = Secp256k1.sign(messageHash, privateKey);

// Deterministic: identical signatures
assert(sig1.r.every((byte, i) => byte === sig2.r[i]));
assert(sig1.s.every((byte, i) => byte === sig2.s[i]));
assert(sig1.v === sig2.v);
```

### Different Messages

```typescript theme={null}
const privateKey = Bytes32();
privateKey[31] = 1;

const msg1 = Keccak256.hashString("message 1");
const msg2 = Keccak256.hashString("message 2");

const sig1 = Secp256k1.sign(msg1, privateKey);
const sig2 = Secp256k1.sign(msg2, privateKey);

// Different messages = different signatures
assert(!sig1.r.every((byte, i) => byte === sig2.r[i]));
```

### Edge Cases

```typescript theme={null}
// Minimum valid private key
const minKey = Bytes32();
minKey[31] = 1;
const sig1 = Secp256k1.sign(messageHash, minKey); // Valid

// Maximum valid private key (n - 1)
const maxKey = new Uint8Array([
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
  0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
  0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x40,
]);
const sig2 = Secp256k1.sign(messageHash, maxKey); // Valid

// Zero key (invalid)
const zeroKey = Bytes32();
expect(() => Secp256k1.sign(messageHash, zeroKey)).toThrow();

// Key >= n (invalid)
const invalidKey = new Uint8Array([
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
  0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
  0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41,
]);
expect(() => Secp256k1.sign(messageHash, invalidKey)).toThrow();
```

## Implementation Notes

### TypeScript

Uses `@noble/curves/secp256k1`:

* **Audit status:** Multiple security audits, production-ready
* **RFC 6979:** Built-in deterministic nonces
* **Low-s:** Automatic normalization
* **Constant-time:** Side-channel resistant
* **Size:** \~20KB minified (tree-shakeable)

### Zig

Custom implementation in `src/crypto/secp256k1.zig`:

* ⚠️ **UNAUDITED** - Not security reviewed
* ⚠️ **NOT constant-time** - Vulnerable to timing attacks
* ⚠️ **Educational only** - Do not use in production
* Implements basic ECDSA with RFC 6979

For production Zig/FFI use, wrap TypeScript implementation via WASM.

## Related

* [Verification](/crypto/secp256k1/verification) - Verify secp256k1 signatures
* [Key Derivation](/crypto/secp256k1/key-derivation) - Private → public key
* [Recovery](/crypto/secp256k1/recovery) - Public key recovery (ecRecover)
* [Security](/crypto/secp256k1/security) - Side-channel attacks, malleability
* [Usage Patterns](/crypto/secp256k1/usage-patterns) - Transaction signing examples
