kTimesG's 80-bit key challenge
October 16, 2024. kTimesG announced a side challenge in the Bitcointalk thread of the b1000 puzzle. The deal: on November 1 at midnight UTC, a P2PKH address with 0.005 BTC and "an 80-bits secure private key". Plus the range to search. The winner only had to say which method broke it. And there was a line for everyone about to argue: "If your first instinct is to yell 'this is not a 80-bits secure private key', please note this competition is NOT for you."
kTimesG's 80-bit key challenge
bitcoin
80 unknown bits in a 511-bit key. The public key only ever showed up in the mempool. Taken 39 minutes in.
- Author
- kTimesG
- Prize recorded
- 0.005 BTC
- Still unsolved
- none
Census
- solved
- 1100%
- public keys
- 1100%
- private keys
- 1100%
Access
- Load
getCollection("80-bit") - Show80-bit in the playground
How the 80-bit challenge works
80 bits in the middle of a 511-bit number
The range post went up at 00:01:53 UTC. It's not a range from 1 to 2^80:
minKey = 0x659756abf6c17ca70e0000000000000000000140be6ddd93e441f8d4b4a85653b20b4cdcc5c748207a0daa16191d07a425d8080c276f9412472e0429e61bc355
maxKey = 0x659756abf6c17ca70fffffffffffffffffffff40be6ddd93e441f8d4b4a85653b20b4cdcc5c748207a0daa16191d07a425d8080c276f9412472e0429e61bc355
Both numbers are 511 bits long. They differ only in 80 contiguous bits, starting at bit 361. A number that big still works as a private key. It just wraps around the curve order n.
The trick is to move the unknown bits to the bottom. Subtract minKey·G from the public key. Multiply by the inverse of 2^361 mod n. What's left is an ordinary key between 1 and 2^80, and Kangaroo eats that in minutes. The answer, 0x2d56cbf370cbeef9e80a, goes back the same way. minKey | (k << 361), then mod n, gives b40e7d34…6be5fe56.
So yes, 80-bit secure. Some people in the thread still spent the day arguing it wasn't.
The public key only lived in the mempool
The address got two payments of 0.0025 BTC on October 28 and never spent. No spend, no public key, nothing to search with. The author promised to push a spend 20 to 28 hours after the range post. And the hint said where the hard part was: "The challenge involves correctly extracting pubkey from the raw TX, otherwise it's a no brainer."
So the real game was a race. Pull the public key out of the pending spend. Crack it. Replace the author's transaction with a higher fee, all before a block confirms it.
The author pushed right after a block, and the next one took 57 minutes. Plenty of time. albert0bsd posted the transformed public key minutes after the spend showed up. Still, nobody replaced it for 39 minutes. kTimesG wasn't impressed: "What if this was the real 80 bit puzzle being emptied, you'll never have 40 minutes." Etar's script had the key in 158 seconds and then crashed on the fee. Ouch.
The replacement landed in block 868480 at 00:37:21 UTC on November 2. 0.00495 BTC went to 14q4SoQwENXXzsVT3GMwDrDUGiW5QZeiDg. The author's own spend never confirmed, so the chain shows only the replacement.
What the kTimesG record carries
export const kTimesG80Bit = bitcoinPuzzle({
id: "80-bit",
address: p2pkh("1ECDLP8osCZHBB1LH5PVAUfFegeMgFb52q", "90b881be…8ba92a12"),
sourceUrl: RANGE,
startedAt: "2024-11-01 00:01:53",
status: Status.Solved,
pubkey: compressed("03a61fc84b6429f07fc0edf25265ef7a0ced3cd9a0edea85e9f58b50b5d73f66e7"),
key: hex("b40e7d34265ab9533a64622bd1a188fb8abb8829af545169abad49b46be5fe56"),
prize: 0.005,
hints: [/* the 80 contiguous bits, the range with its answer, the raw TX */],
solvedAt: "2024-11-02 00:37:21",
solveTime: 88528,
transactions: [funding(/* … */), increase(/* … */), claim(/* … */)],
solver: party(undefined, { addresses: ["14q4SoQwENXXzsVT3GMwDrDUGiW5QZeiDg"] }),
});
A 256-bit key and no bits
The key is the reduced one, the 256-bit number that actually signs for the address. It has no bits, on purpose. bits has to match the key's own length, and 80 is the width of the range, not the key. The range and the 511-bit form sit in the hints, where the author put them.
The range hint also carries the answer. Before anyone had the public key, the range post committed to the solve steps with a SHA-256, a6c39217…f0651db0. The steps came out after the claim. The record quotes their last lines: shift 361, the 80-bit key and the % that turns it into b40e7d34….
Started at the range, not at the funding
startedAt is the range post, not the funding four days earlier. The author did post the address on October 31, to show it wasn't a hoax. But without the range there was nothing to search. And without the pending spend, nothing to search with. So solveTime is 88,528 seconds, about 24.6 hours, and almost all of it was waiting for the spend.
A solver with an address and no name
opchecksig wrote later that the prize address is theirs. Kangaroo on an RTX 4070, 28 minutes. Nobody signed anything with that address though. So the record keeps the address and skips the name.
A singleton id, no name segment
80-bit is the whole identifier, like genesis, gsmg and the other singletons. It used to be ktimesg/80-bit, the author's handle as the collection key for a collection that only ever held one challenge. kTimesG keeps the author page. The old pages redirect here.
The kTimesG challenge record
bitcoinp2pkh solved
Record
bitcoinPuzzle({
address: p2pkh("1ECDLP8osCZH…eMgFb52q", "90b881be7044…8ba92a12"),
status: Status.Solved,
key: hex("b40e7d34265a…6be5fe56"),
})- Prize
- 0.005 BTC
- Key material
- hex
- Verification
- verified
- Solved
- 2024-11-02 · 1d 35m
- Trail
- Explorerblockstream.info
- Sourcebitcointalk.org/index.php?topic=1306983.msg64691846
- Try80-bit in the playground
- Address
- 1ECDLP8osCZHBB1LH5PVAUfFegeMgFb52q
hash160 90b881be7044a596b0ac843d84ff31278ba92a12
Key
- public key · compressed
- 03a61fc84b6429f07fc0edf25265ef7a0ced3cd9a0edea85e9f58b50b5d73f66e7
- private key (hex)
- b40e7d34265ab9533a64622bd1a188fb8abb8829af545169abad49b46be5fe56
Transactions
- funding2024-10-280.0025 BTCe4a2751bf7…43568b
- increase2024-10-280.0025 BTCa7d8b7f7a8…c5b2f9
- claim2024-11-020.00495 BTCdc8309f8b5…162f36
Hints
- 2024-10-16
officialThe Hamming length of the range will therefore be 80 contiguous bits, but they may start anywhere.
- 2024-11-01
officialminKey = 0x659756abf6c17ca70e0000000000000000000140be6ddd93e441f8d4b4a85653b20b4cdcc5c748207a0daa16191d07a425d8080c276f9412472e0429e61bc355 maxKey = 0x659756abf6c17ca70fffffffffffffffffffff40be6ddd93e441f8d4b4a85653b20b4cdcc5c748207a0daa16191d07a425d8080c276f9412472e0429e61bc355
Sourcebitcointalk.org/index.php?topic=1306983.msg64691846
Published answer
assert shift == 361; elem = shift_inv * (public_key - min_elem); assert idlp_key == 0x2d56cbf370cbeef9e80a; private_key = min_key | (idlp_key << shift); assert private_key % secp256k1.N == 0xb40e7d34265ab9533a64622bd1a188fb8abb8829af545169abad49b46be5fe56
2024-11-02bitcointalk.org/index.php?topic=1306983.msg64695204
- 2024-11-01
officialThe challenge involves correctly extracting pubkey from the raw TX, otherwise it's a no brainer.