- Category: Crypto
- Score: 469/500
- Solves: 2/428
Another boring crypto challenge about signatures.
This challenge signs 6 signatures using ECDSA on Secp256k1. The nonce
This challenge is based on jonasnick/ecdsaPredictableNonce.
Need to use two basic facts:
$a \oplus b = a + b - 2 (a \land b)$ - For two n bits integer
$a,b$ ,$a \land b = \sum_{i=0}^{n-1} 2^i a_i b_i$ , where$a_i$ and$b_i$ is their n-th bit.
So
In Secp256k1,
But it forgot an important fact:
Details are in the solution script
After recovering Congrats! This is your flag:
in the plaintext, we can take the first block and xor it with the first block of ciphertext, they decrypt it with the key. This is how AES-CTR works.
from fastecdsa.curve import secp256k1 as CURVE
from Crypto.Cipher import AES
from hashlib import sha256
from operator import xor
import ast
with open("output.txt") as f:
sigs = []
for _ in range(6):
z, r, s = map(int, f.readline().strip().split())
sigs.append((z, r, s))
msgct = ast.literal_eval(f.readline())
def recover_d(sigs):
P = PolynomialRing(Zmod(CURVE.q), "d", 256)
ds = P.gens()
dd = sum([2 ** i * di for i, di in enumerate(ds)])
polys = []
for z, r, s in sigs:
d_and_z = sum([2 ** i * ((z & (1 << i)) >> i) * di for i, di in enumerate(ds)])
# fact: (a xor b) = a + b - 2 * (a and b)
k = dd + z - 2 * d_and_z
polys.append((s * k) - (z + r * dd))
M, v = Sequence(polys).coefficient_matrix()
print(v.T)
M = M.T.dense_matrix()
a, b = M.dimensions()
B = block_matrix(
ZZ, [[matrix.identity(b) * CURVE.q, matrix.zero(b, a)], [M, matrix.identity(a)]]
)
B[:, :b] *= 2 ^ 64
print("LLL", B.dimensions())
for row in B.LLL():
if row[:b] == 0 and row[-1] == 1 and all(0 <= x <= 1 for x in row[b:-1]):
dbits = row[b:-1]
d = int("".join(map(str, dbits[::-1])), 2)
return d
d = recover_d(sigs)
print(f"{d = }")
key = sha256(str(d).encode()).digest()[:16]
nonce = AES.new(key, AES.MODE_ECB).decrypt(
bytes(map(xor, b"Congrats! This is your flag: "[:16], msgct[:16]))
)[:8]
cipher = AES.new(key, AES.MODE_CTR, nonce=nonce)
print(cipher.decrypt(msgct))