k = hmac.new(k, v + b"\x00" + bx, hashfunc).digest()
The problem: implement RFC 6979 §3.2 HMAC-DRBG nonce derivation so that k depends only on (x, H(m), n) and matches published secp256k1/SHA-256 vectors. The two easy-to-get-wrong details are (1) the bits2octets truncation — a hash that is longer than q in bit length must be right-shifted to the leftmost qlen bits before reduction mod n, and a hash whose value is ≥ n must be reduced — and (2) the retry loop that re-seeds K/V whenever the candidate is k == 0 or k >= n.
Complete implementation (~/rfc6979/rfc6979.py):
import hashlib, hmac
from typing import Callable
def bits2int(b: bytes, qlen: int) -> int:
"""RFC 6979 §2.3.3: big-endian int; if input exceeds qlen bits,
keep only the leftmost qlen bits (right shift)."""
x = int.from_bytes(b, "big")
blen = len(b) * 8
return x >> (blen - qlen) if blen > qlen else x
def int2octets(x: int, rolen: int) -> bytes:
"""RFC 6979 §2.3.2: fixed rolen-octet big-endian string."""
return x.to_bytes(rolen, "big")
def bits2octets(b: bytes, order: int, qlen: int) -> bytes:
"""RFC 6979 §2.3.5: bits2int(b) mod order as rolen octets."""
rolen = (qlen + 7) // 8
return int2octets(bits2int(b, qlen) % order, rolen)
def generate_k(order: int, secexp: int, hash_func: Callable,
data: bytes, retry_gen: int = 0, extra_entropy: bytes = b"") -> int:
"""RFC 6979 §3.2 deterministic ECDSA nonce. data = raw H(m)."""
qlen = order.bit_length()
holen = hash_func().digest_size
rolen = (qlen + 7) // 8
bx = (int2octets(secexp, rolen) + bits2octets(data, order, qlen) + extra_entropy)
# Steps B/C: V = 0x01*holen, K = 0x00*holen
v = b"\x01" * holen
k = b"\x00" * holen
# Step D: K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))
k = hmac.new(k, v + b"\x00" + bx, hash_func).digest()
# Step E: V = HMAC_K(V)
v = hmac.new(k, v, hash_func).digest()
# Step F: K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1))
k = hmac.new(k, v + b"\x01" + bx, hash_func).digest()
# Step G: V = HMAC_K(V)
v = hmac.new(k, v, hash_func).digest()
# Step H: generate T = V||HMAC_K(V)||... until rolen octets; retry if
# k == 0 or k >= order (K = HMAC_K(V||0x00); V = HMAC_K(V); loop).
while True:
t = b""
while len(t) < rolen:
v = hmac.new(k, v, hash_func).digest()
t += v
candidate = bits2int(t, qlen)
if 1 <= candidate < order: # rejects k == 0 and k >= n
if retry_gen <= 0:
return candidate
retry_gen -= 1
k = hmac.new(k, v + b"\x00", hash_func).digest()
v = hmac.new(k, v, hash_func).digest()
def ecdsa_nonce(privkey: int, msg_hash: bytes, order: int,
hash_func: Callable = hashlib.sha256) -> int:
return generate_k(order, privkey, hash_func, msg_hash)
Key points that make it match the vectors exactly:
- qlen = n.bit_length(), rolen = ceil(qlen/8), holen = digest size; int2octets(x, rolen) pads the private key to rolen bytes.
- bits2octets is applied to the raw digest internally — so both the "hash longer than n in bit length" case (right-shift by blen - qlen) and the "hash value ≥ n" case (% order) are handled automatically.
- The candidate is accepted only when 1 ≤ k < n; otherwise K/V are re-seeded exactly per Step H4 and T is regenerated.
Test harness at `~/rfc6979/test_rfc6979.py` (and end-to-end signature checks) — **35/35 passed**, on both Python 3.12 and 3.14, plus byte-identical agreement with the `ecdsa` 0.19.2 reference `rfc6979.generate_k`: 1. **Published secp256k1/SHA-256 vectors** (python-ecdsa suite, all 6): `x=1, "Satoshi Nakamoto" → k = 8f8a276c...4f15d15`; `x=1, "All those moments..." → 38aa22d7...98d6b3`; `x=n-1, "Satoshi Nakamoto" → 33a19b60...aa6f90`; the `sample`/`Alan Turing` vectors, etc. 2. **RFC 6979 A.2.5 (NIST P-256)**: all 10 vectors — SHA-1/224/256/384/512 × "sample"/"test" — e.g. SHA-256 "sample" → `a6e3c57d...8aad60`. 3. **RFC 6979 A.1 basic example**: the "try-try-again" case (qlen=163, 256-bit hash) → `k = 23af4074...bdd81b`. 4. **Hash bit length > curve order**: NIST P-192 (qlen=192) with SHA-256/SHA-512 hashes, all 6 vectors match; plus a synthetic 64-byte hash for secp256k1 cross-checked against the reference (bits2int right-shifts the top 256 bits). 5. **Hash value ≥ n**: `h1 = n`, `n+0x1234`, `2^256−1` for secp256k1 — all reduce mod n identically to the reference. 6. **Retry path**: forced via `retry_gen=0..3` (skips valid k's, exercising the exact K/V update loop) — identical to reference. 7. **60 randomized cross-checks** across SECP256k1/NIST256p/NIST192p × sha1/sha256/sha512 × hash lengths 16–64 bytes; **k-range sanity** (0 < k < n) over 50 random nonces. 8. **End-to-end signatures**: P-256 "sample" (r,s) matches RFC A.2.5 exactly (`efd48b2a...`, `f7cb1c94...`); secp256k1 "Satoshi Nakamoto" r = `934b1ea1...ee3d8` (published value), s is self-consistent (recovers `z = SHA-256(msg) mod n`), verifies against the public key, and is byte-identical to the library's own deterministic signature.
{"model": "deepseek-v4-flash", "problem_class": "python-rfc6979-deterministic-ecdsa-nonce", "result": "passed", "tests": 35}The problem: implement RFC 6979 §3.2 HMAC-DRBG nonce derivation so that k depends only on (x, H(m), n) and matches published secp256k1/SHA-256 vectors. The two easy-to-get-wrong details are (1) the bits2octets truncation — a hash that is longer than q in bit length must be right-shifted to the leftmost qlen bits before reduction mod n, and a hash whose value is ≥ n must be reduced — and (2) the retry loop that re-seeds K/V whenever the candidate is k == 0 or k >= n.
Complete implementation (~/rfc6979/rfc6979.py):
import hashlib, hmac
from typing import Callable
def bits2int(b: bytes, qlen: int) -> int:
"""RFC 6979 §2.3.3: big-endian int; if input exceeds qlen bits,
keep only the leftmost qlen bits (right shift)."""
x = int.from_bytes(b, "big")
blen = len(b) * 8
return x >> (blen - qlen) if blen > qlen else x
def int2octets(x: int, rolen: int) -> bytes:
"""RFC 6979 §2.3.2: fixed rolen-octet big-endian string."""
return x.to_bytes(rolen, "big")
def bits2octets(b: bytes, order: int, qlen: int) -> bytes:
"""RFC 6979 §2.3.5: bits2int(b) mod order as rolen octets."""
rolen = (qlen + 7) // 8
return int2octets(bits2int(b, qlen) % order, rolen)
def generate_k(order: int, secexp: int, hash_func: Callable,
data: bytes, retry_gen: int = 0, extra_entropy: bytes = b"") -> int:
"""RFC 6979 §3.2 deterministic ECDSA nonce. data = raw H(m)."""
qlen = order.bit_length()
holen = hash_func().digest_size
rolen = (qlen + 7) // 8
bx = (int2octets(secexp, rolen) + bits2octets(data, order, qlen) + extra_entropy)
# Steps B/C: V = 0x01*holen, K = 0x00*holen
v = b"\x01" * holen
k = b"\x00" * holen
# Step D: K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))
k = hmac.new(k, v + b"\x00" + bx, hash_func).digest()
# Step E: V = HMAC_K(V)
v = hmac.new(k, v, hash_func).digest()
# Step F: K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1))
k = hmac.new(k, v + b"\x01" + bx, hash_func).digest()
# Step G: V = HMAC_K(V)
v = hmac.new(k, v, hash_func).digest()
# Step H: generate T = V||HMAC_K(V)||... until rolen octets; retry if
# k == 0 or k >= order (K = HMAC_K(V||0x00); V = HMAC_K(V); loop).
while True:
t = b""
while len(t) < rolen:
v = hmac.new(k, v, hash_func).digest()
t += v
candidate = bits2int(t, qlen)
if 1 <= candidate < order: # rejects k == 0 and k >= n
if retry_gen <= 0:
return candidate
retry_gen -= 1
k = hmac.new(k, v + b"\x00", hash_func).digest()
v = hmac.new(k, v, hash_func).digest()
def ecdsa_nonce(privkey: int, msg_hash: bytes, order: int,
hash_func: Callable = hashlib.sha256) -> int:
return generate_k(order, privkey, hash_func, msg_hash)
Key points that make it match the vectors exactly:
- qlen = n.bit_length(), rolen = ceil(qlen/8), holen = digest size; int2octets(x, rolen) pads the private key to rolen bytes.
- bits2octets is applied to the raw digest internally — so both the "hash longer than n in bit length" case (right-shift by blen - qlen) and the "hash value ≥ n" case (% order) are handled automatically.
- The candidate is accepted only when 1 ≤ k < n; otherwise K/V are re-seeded exactly per Step H4 and T is regenerated.
Test harness at `~/rfc6979/test_rfc6979.py` (and end-to-end signature checks) — **35/35 passed**, on both Python 3.12 and 3.14, plus byte-identical agreement with the `ecdsa` 0.19.2 reference `rfc6979.generate_k`: 1. **Published secp256k1/SHA-256 vectors** (python-ecdsa suite, all 6): `x=1, "Satoshi Nakamoto" → k = 8f8a276c...4f15d15`; `x=1, "All those moments..." → 38aa22d7...98d6b3`; `x=n-1, "Satoshi Nakamoto" → 33a19b60...aa6f90`; the `sample`/`Alan Turing` vectors, etc. 2. **RFC 6979 A.2.5 (NIST P-256)**: all 10 vectors — SHA-1/224/256/384/512 × "sample"/"test" — e.g. SHA-256 "sample" → `a6e3c57d...8aad60`. 3. **RFC 6979 A.1 basic example**: the "try-try-again" case (qlen=163, 256-bit hash) → `k = 23af4074...bdd81b`. 4. **Hash bit length > curve order**: NIST P-192 (qlen=192) with SHA-256/SHA-512 hashes, all 6 vectors match; plus a synthetic 64-byte hash for secp256k1 cross-checked against the reference (bits2int right-shifts the top 256 bits). 5. **Hash value ≥ n**: `h1 = n`, `n+0x1234`, `2^256−1` for secp256k1 — all reduce mod n identically to the reference. 6. **Retry path**: forced via `retry_gen=0..3` (skips valid k's, exercising the exact K/V update loop) — identical to reference. 7. **60 randomized cross-checks** across SECP256k1/NIST256p/NIST192p × sha1/sha256/sha512 × hash lengths 16–64 bytes; **k-range sanity** (0 < k < n) over 50 random nonces. 8. **End-to-end signatures**: P-256 "sample" (r,s) matches RFC A.2.5 exactly (`efd48b2a...`, `f7cb1c94...`); secp256k1 "Satoshi Nakamoto" r = `934b1ea1...ee3d8` (published value), s is self-consistent (recovers `z = SHA-256(msg) mod n`), verifies against the public key, and is byte-identical to the library's own deterministic signature.
{"model": "deepseek-v4-flash", "problem_class": "python-rfc6979-deterministic-ecdsa-nonce", "result": "passed", "tests": 35}