Bitcoin Forum
July 01, 2025, 11:20:40 AM *
News: Pizza day contest voting
 
  Home Help Search Login Register More  
  Show Posts
Pages: [1] 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 »
1  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: Today at 04:20:58 AM
the same dude? Tongue

Dude? Nah, she's a woman.  Grin
2  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 30, 2025, 10:04:08 PM
All I mean to say is that I'm often tempted to try and figure-out the seed(s) used to generate the puzzle keys, as I think you've spent quite a lot of time doing... But every time I think about it, I have to remind myself that we just don't know enough / have enough data about where to start... Which version of which language was used, and which RNG (standard or cryptographic, Mersenne Twister or something else, etc) was used, or whether the rand was seeded once, before the loop with a static seed (like I tend to do, which I think is kind-of a common C# pattern) or again on every loop with a dynamically changing seed (the way the code you posted was), not to mention the seed's exact value itself, and who knows what other factors...

In 2015, a Puzzle BTC creator using Python + Electrum would likely have:

Generated keys via random.seed() (not PBKDF2/HMAC-SHA512).

Used WIF format (easily imported into Electrum using importprivkey) and ignored BIP-32/39.

This kept the puzzle simple, reproducible, and Electrum-friendly.

Consistent with real 2015 examples like 1FLAMEN6, which used raw SHA-256/RIPEMD160 hashes (not HD wallets) and WIF keys.

But who cares if I'm right or wrong?  Grin
3  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 30, 2025, 08:25:49 PM
How did you come to the conclusion that he used Python? Just because it's easier?


If you were a Puzzle BTC creator in 2015, you could have used Bitcoin Core, Electrum, or Armory. Electrum (written in Python) was already popular in 2015, and its wallet format is well-documented. Importing keys via the command line (electrum importprivkey) would be trivial with Python-generated WIFs.

Python’s random.seed() function ensures deterministic key generation, unlike C++’s std::rand(), which varies across implementations unless carefully controlled.

Python’s random uses a Mersenne Twister, while C++’s std::rand() depends on the compiler.

Since Electrum itself is written in Python, a Python script would integrate seamlessly.

The same code works unchanged from 2015 to 2025 (thanks to Python’s stability).You just need to know the seed.

4  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 30, 2025, 07:26:47 PM
Ok, I think I see my mistake... I just went back to the original comment, and I realize now that I had read "a single master seed" as "master seed phrase" and not "a seed used to prime a RNG" ... Meaning, I thought the idea being proposed was that the puzzles were initially created by, like, using some off-the-shelf wallet software to automatically create a new wallet, complete with a BIP-39 seed phrase and a list of associated private keys, and then changing/masking those private keys to make the puzzles' keys, which (of course) would have disassociated them all from the original BIP-39 seed phrase... And I couldn't for the life of me figure out how that would be easier than just generating them all the way you show above...

So, I am dumb, just not the way I'd feared Tongue


Personally, if I were the creator, I would use this simple python script.You could straight-up import all them keys from 1 to 160 into Electrum right from the command line. Move all the bread in two clicks, no cap. Me? I’d use a Social Security Number as the seed, toss in ‘SatoshiNakamotoPuzzle’ plus the puzzle number, and bam, we locked in. I wouldn't even lie that I used a wallet.  Cool

5  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 30, 2025, 06:38:27 PM
- #1 starts with a `1`
- #2 starts with either a `2` or `3`
- #3 starts with a `4`, `5`, `6`, or `7`
- #4 starts with any of the remaining possible hex chars
- then the pattern repeats for the next group of four keys, and so on...

is it actually possible that one seed phrase could/would actually make a series of consecutive keys that fit not just that specific pattern, but really any kind of repeating pattern at all...?? And how would you even find/make such a seed phrase...??

It is possible.

Code:
import random
import hashlib
import base58

for puzzle in range(1, 161):
      lower = 2 ** (puzzle - 1)
      upper = (2 ** puzzle) - 1
      seed = "SatoshiNakamotoPuzzle" + str(puzzle)
      random.seed(seed)
      dec = random.randint(lower, upper)
      private_key_hex = "%064x" %  dec
      private_key_bytes = bytes.fromhex(private_key_hex)
      extended_key = b'\x80' + private_key_bytes
      extended_key += b'\x01'
      checksum = hashlib.sha256(hashlib.sha256(extended_key).digest()).digest()[:4]
      wif_bytes = extended_key + checksum
      wif_compressed = base58.b58encode(wif_bytes).decode()
      print(f"Puzzle = {puzzle} {DEC} = {dec} seed = {seed} wif = {wif_compressed}")


python3 test.py
Quote
Puzzle = 1 DEC = 1 seed = SatoshiNakamotoPuzzle1 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn
Puzzle = 2 DEC = 2 seed = SatoshiNakamotoPuzzle2 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU74NMTptX4
Puzzle = 3 DEC = 5 seed = SatoshiNakamotoPuzzle3 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU75s2EPgZf
Puzzle = 4 DEC = 11 seed = SatoshiNakamotoPuzzle4 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU78rNKwdiH
Puzzle = 5 DEC = 22 seed = SatoshiNakamotoPuzzle5 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU7EL1wKGDm
Puzzle = 6 DEC = 53 seed = SatoshiNakamotoPuzzle6 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU7VmMqX3qc
Puzzle = 7 DEC = 73 seed = SatoshiNakamotoPuzzle7 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU7fj3itoEY
Puzzle = 8 DEC = 148 seed = SatoshiNakamotoPuzzle8 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU8K5DxCiQf
Puzzle = 9 DEC = 306 seed = SatoshiNakamotoPuzzle9 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU9fkdRpSex
Puzzle = 10 DEC = 634 seed = SatoshiNakamotoPuzzle10 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFUCV5z2PfC3
*****
Puzzle = 66 DEC = 38508275138367239239 seed = SatoshiNakamotoPuzzle66 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qZWCKpZnZsCqVzc1f9vt
Puzzle = 67 DEC = 146233885779705721938 seed = SatoshiNakamotoPuzzle67 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qbeiYkkDLY2iKmA6JS3q
Puzzle = 68 DEC = 204291343762893348650 seed = SatoshiNakamotoPuzzle68 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qcopt3giY39KjX9pfekV
Puzzle = 69 DEC = 588454650131287819678 seed = SatoshiNakamotoPuzzle69 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qkTtFZibZ9tNE96yFsjS
Puzzle = 70 DEC = 826730197180747088722 seed = SatoshiNakamotoPuzzle70 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qqDJuNu7s3FZKtsk9Pn7
Puzzle = 71 DEC = 1833056699595944202074 seed = SatoshiNakamotoPuzzle71 wif = KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3rBGXpLd8yP9kGu7rqRvw

Ayo, there’s a pattern in the seed, but them keys and decimal numbers? Nah, they straight-up random.
But bruh, tryna find and reverse-engineer the seed? That’s impossible. You’d have an easier time brute-forcing private keys than crackin’ that seed, no cap. Grin
6  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 29, 2025, 09:38:53 AM
It’s mad hard to prove someone’s in the game. Unless they’re buggin’ and move all their BTC where there’s KYC. Like Binance or somethin’. Me? I’d probably use an atomic swap and Monero. But even if they catch a trace(where would I send it), they’d need a whole army to roll up there. A whole army. Maybe even a bunker buster. And even then, they risk startin’ WWIII with another country.  Grin

So, you got a whole strategy to move this crypto in phases, huh? Probably through Russia or China? And you cool with kissin’ 30% goodbye after all them moves?  Tongue

You think I’m out here buyin’ a house, drivin’ a Lambo, and partyin’ on a rooftop in Dubai? All at once? No. I’d be movin’ like $700-$800 a month for the next 20 years. Small moves, Ellie. Small moves. Grin
7  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 29, 2025, 08:19:52 AM
If the creator did not legally relinquish ownership (e.g., via a smart contract or binding terms), brute-forcing the key might still be considered theft.

If the owner did not clearly relinquish ownership (e.g., by publishing a signed transaction or legal agreement), taking funds may be unlawful.

Intent: If the puzzle is structured as a brute-force attack rather than a solvable riddle, it could be argued that the creator is inducing illegal activity.

This is impossible to solve, man. Not sure what's the fuss about? You want to arrest the creator of this puzzle? Aren't you? Or the participants? Come on. Lips sealed

It’s mad hard to prove someone’s in the game. Unless they’re buggin’ and move all their BTC where there’s KYC. Like Binance or somethin’. Me? I’d probably use an atomic swap and Monero. But even if they catch a trace(where would I send it), they’d need a whole army to roll up there. A whole army. Maybe even a bunker buster. And even then, they risk startin’ WWIII with another country.  Grin
8  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 28, 2025, 11:37:45 PM

If this is IDLP you don't need to store full keys, just distance from base start. This alone saves up to 20 bytes. However this works only if the max distance is ensured to never be reached.

Also I'm willing to bet an arm and leg that mmap-ing the DP file will lose big time against an actual database file, when there are lots of DPs, since they'll be spread around the disk, I wonder what your OS will have to say about that, when seeking around and pages start to swap in and out (besides all running apps stalling due to low free memory)

But seems that there's some kind of competition on these forums on who can reinvent a rounder wheel.


You're right about the distance-only storage optimization for DLP.

This implementation isn’t mine. I’m just analyzing the code. I haven't even started it in my OS. This is all theoretical. If the original author wants to optimize further, they’d need to adopt delta encoding (storing walk distances, not full keys), replace mmap with a proper DB for large-scale DP storage, and tighten memory limits to avoid thrashing.

These changes require significant redesign. Until then, it’s stuck with these limitations.  Wink

9  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 28, 2025, 08:38:29 PM
Can this be further optimized in terms of speed? Tongue


The current implementation is already highly optimized, so further gains would likely be in the 10-30% range rather than order-of-magnitude improvements. The most promising areas would be hash function optimization and fine-tuning batch sizes for specific hardware.

These batch sizes could be tuned based on:

CPU cache sizes (L1/L2/L3)

Available SIMD registers

Benchmark different sizes (256, 512, 1024)

Current DP Table Structure:

Code:
#pragma pack(push,1)
struct DPSlot{ fp_t fp; Scalar256 key; };
#pragma pack(pop)
static_assert(sizeof(DPSlot)==40);

8 bytes for the fingerprint (fp_t)

32 bytes for the scalar key (Scalar256)

Total: 40 bytes per slot


Could potentially reduce to 32-bit with:

More frequent collisions (manageable)

Secondary verification when matches occur

Savings: 4 bytes per slot (10% reduction)
10  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 28, 2025, 01:53:00 PM
hashing using XXH64_hash_t

Extremely fast Hash algorithm?   Tongue

Yeah, XXH64 gets like 10-20 GB/s.   Grin

Can you give us some code? I don't know what to ask the AI ​​where to put XXHash's?  Tongue

Mark1.cpp
Code:
#include <atomic>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <random>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#include <algorithm>

#include <omp.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xxhash.h>

#include "Int.h"
#include "Point.h"
#include "SECP256K1.h"
#include "IntGroup.h"
#include "simd_block_bloom.h"

// ─── XXHash Integration ──────────────────────────────────────────────────────
static inline uint64_t xxhash64(const void* data, size_t len, uint64_t seed = 0) {
    return XXH64(data, len, seed);
}

static inline uint64_t IntLow64(const Int& n){ return n.bits64[0]; }

static inline uint64_t pointToFp(const Point& p) {
    uint64_t x = IntLow64(p.x);
    uint8_t parity = !p.y.IsEven();
    return xxhash64(&x, sizeof(x), parity);
}


// ─── Misc ─────────────────────────────────────────────────────────────────────
static std::string humanBytes(size_t bytes){
    constexpr const char* u[]{"B","Kb","Mb","Gb","Tb"};
    double v = double(bytes); int s = 0;
    while(v >= 1024.0 && s < 4){ v /= 1024.0; ++s; }
    std::ostringstream o;
    o << std::fixed << std::setprecision((v<10)?2:(v<100)?1:0) << v << u[s];
    return o.str();
}
static inline void intCopy(Int& d,const Int& s){ d.Set(&const_cast<Int&>(s)); }
static inline int bitlen(const Int& n){ return const_cast<Int&>(n).GetBitLength(); }
static inline bool intGE(const Int& a,const Int& b){
    return const_cast<Int&>(a).IsGreaterOrEqual(&const_cast<Int&>(b));
}

// ─── Scalar256 ────────────────────────────────────────────────────────────────
struct Scalar256{ uint64_t w[4]; };
static inline void intToScalar(const Int& n, Scalar256& s){
    s.w[0]=n.bits64[0]; s.w[1]=n.bits64[1];
    s.w[2]=n.bits64[2]; s.w[3]=n.bits64[3];
}
static inline void scalarToInt(const Scalar256& s, Int& n){
    n.SetInt32(0);
    for(int i=3;i>=0;--i){ n.ShiftL(64); Int tmp(s.w[i]); n.Add(&tmp); }
}

using fp_t = uint64_t;

// ─── SSD DP storage ────────────────────────────────────────────────────────────
#pragma pack(push,1)
struct DPSlot{ fp_t fp; Scalar256 key; };
#pragma pack(pop)
static_assert(sizeof(DPSlot)==40);

struct DPStorage{
    size_t                        cap=0, mapBytes=0;
    int                           fd=-1;
    DPSlot*                       slots=nullptr;
    std::unique_ptr<std::atomic<uint8_t>[]> st_used, st_lock;
    std::atomic<size_t>           size{0};          
    std::atomic<size_t>           dirty{0};
    std::atomic<size_t>           flush_counter{0};  
    std::atomic<bool>             enable_flush{true};

    void   init(const std::string& path,size_t c);
    void   flushIfNeeded(size_t slotIdx) noexcept;
    void   fullSync() noexcept;
    void   close();
};
static DPStorage dp;

static constexpr size_t FLUSH_STEP = 1ull<<24;  

// ─── DPStorage impl ───────────────────────────────────────────────────────────
void DPStorage::init(const std::string& path,size_t c){
    cap=c; mapBytes=cap*sizeof(DPSlot);

    int flags = O_RDWR | O_CREAT;
#ifdef O_DIRECT
    flags |= O_DIRECT;        
#endif
#ifdef O_SYNC
    flags |= O_SYNC;          
#endif
    fd = ::open(path.c_str(),flags,0644);
    if(fd<0){
        perror("open(dp)"); throw std::runtime_error("open(dp)");
    }
    if(posix_fallocate(fd,0,mapBytes)){
        perror("fallocate(dp)"); throw std::runtime_error("fallocate(dp)");
    }
    void* p = mmap(nullptr,mapBytes,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(p==MAP_FAILED){
        perror("mmap(dp)"); throw std::runtime_error("mmap(dp)");
    }
    slots = static_cast<DPSlot*>(p);

    st_used = std::make_unique<std::atomic<uint8_t>[]>(cap);
    st_lock = std::make_unique<std::atomic<uint8_t>[]>(cap);
#pragma omp parallel for schedule(static)
    for(size_t i=0;i<cap;++i){
        st_used[i].store(0,std::memory_order_relaxed);
        st_lock[i].store(0,std::memory_order_relaxed);
    }
    madvise(slots,mapBytes,MADV_RANDOM);
}

void DPStorage::flushIfNeeded(size_t slotIdx) noexcept{
    if(!enable_flush.load(std::memory_order_relaxed)) return;
    if(flush_counter.fetch_add(1,std::memory_order_relaxed) % FLUSH_STEP == 0){
        size_t start = (slotIdx / FLUSH_STEP) * FLUSH_STEP;
        size_t end   = std::min(start + FLUSH_STEP, cap);
        size_t len   = (end - start) * sizeof(DPSlot);
        msync(reinterpret_cast<char*>(slots) + start*sizeof(DPSlot), len, MS_ASYNC);
    }
}
void DPStorage::fullSync() noexcept{
    msync(slots,mapBytes,MS_SYNC);
}
void DPStorage::close(){
    if(slots) munmap(slots,mapBytes);
    if(fd>=0) ::close(fd);
}

// ─── Curve ────────────────────────────────────────────────────────────────────
static const char *P_HEX="FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F";
static const char *N_HEX="FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141";
static Int P_PRIME, ORDER_N; static Secp256K1 secp;

static inline Point addP(const Point& a,const Point& b){
    if(const_cast<Int&>(a.x).IsZero() && const_cast<Int&>(a.y).IsZero()) return b;
    if(const_cast<Int&>(b.x).IsZero() && const_cast<Int&>(b.y).IsZero()) return a;
    return secp.AddDirect(const_cast<Point&>(a),const_cast<Point&>(b));
}
static inline Point mulP(const Int& k){ Int t(k); return secp.ComputePublicKey(&t); }

// ─── Bloom + DP API ───────────────────────────────────────────────────────────
static simd_bloom::SimdBlockFilterFixed<>* bloom=nullptr;
static std::atomic<uint64_t> dpDone{0};

inline bool sameScalar(const Scalar256& a,const Scalar256& b){
    return std::memcmp(&a,&b,sizeof(Scalar256))==0;
}

// ─── Dual-hash DP ────────────────────────────────────────────────────────────
bool dp_insert_unique(fp_t fp,const Int& idx){
    Int t(idx); t.Mod(&ORDER_N);
    Scalar256 key; intToScalar(t,key);

    size_t mask = dp.cap - 1;
    size_t h1   = fp & mask;
    size_t h2   = ((fp << 1) | 1) & mask;
    if(h2 == 0) h2 = 1;

    size_t h = h1;
    for(size_t i=0;i<dp.cap;++i){
        if(!dp.st_used[h].load(std::memory_order_acquire)){
            uint8_t exp=0;
            if(dp.st_lock[h].compare_exchange_strong(exp,1,std::memory_order_acq_rel)){
                if(!dp.st_used[h].load(std::memory_order_acquire)){
                    dp.slots[h].fp  = fp;
                    dp.slots[h].key = key;
                    dp.st_used[h].store(1,std::memory_order_release);
                    dp.st_lock[h].store(0,std::memory_order_release);

                    dp.size.fetch_add(1,std::memory_order_relaxed);
                    dp.flushIfNeeded(h);
                    dpDone.fetch_add(1,std::memory_order_relaxed);
                    return true;
                }
                dp.st_lock[h].store(0,std::memory_order_release);
            }
        }else if(dp.slots[h].fp==fp && sameScalar(dp.slots[h].key,key)){
            return false;
        }
        h = (h + h2) & mask;
    }
    return false;            
}
bool dp_find(fp_t fp,Int& out){
    size_t mask = dp.cap - 1;
    size_t h1   = fp & mask;
    size_t h2   = ((fp << 1) | 1) & mask;
    if(h2 == 0) h2 = 1;

    size_t h = h1;
    for(size_t i=0;i<dp.cap;++i){
        if(!dp.st_used[h].load(std::memory_order_acquire))
            return false;
        if(dp.slots[h].fp == fp){
            scalarToInt(dp.slots[h].key,out);
            return true;
        }
        h = (h + h2) & mask;
    }
    return false;
}

// ─── Binary DP I/O ────────────────────────────────────────────────────────────
#pragma pack(push,1)
struct DpItem{ fp_t fp; uint8_t priv[32]; };
#pragma pack(pop)

void saveDPBinary(const std::string& fn) {
    std::ofstream f(fn, std::ios::binary | std::ios::trunc);
    if (!f) { std::cerr << "[ERR] open " << fn << "\n"; return; }
    uint64_t cnt = 0;
    for (size_t h = 0; h < dp.cap; ++h) {
        if (!dp.st_used[h].load(std::memory_order_acquire)) continue;
        DpItem it;
        it.fp = dp.slots[h].fp;
        Int p; scalarToInt(dp.slots[h].key, p); p.Get32Bytes(it.priv);
        f.write(reinterpret_cast<char*>(&it), sizeof(it)); ++cnt;
    }
    std::cout << "Saved " << cnt << " traps to " << fn
              << " (" << humanBytes(f.tellp()) << ")\n";
}

bool loadDPBinary(const std::string& fn){
    std::ifstream f(fn,std::ios::binary|std::ios::ate);
    if(!f){ std::cerr<<"[ERR] open "<<fn<<"\n"; return false; }
    if(f.tellg()%sizeof(DpItem)){ std::cerr<<"[ERR] bad size\n"; return false; }
    f.seekg(0);
    DpItem it; uint64_t n = 0;
    while(f.read(reinterpret_cast<char*>(&it),sizeof(it))){
        Int p; p.Set32Bytes(it.priv);
        if(dp_insert_unique(it.fp,p)) bloom->Add(uint32_t(it.fp));
        if((++n & 0xFFFFF) == 0) std::cout<<"\rLoaded "<<n<<std::flush;
    }
    std::cout<<"\rLoaded "<<n<<" traps (done)\n";
    return true;
}

// ─── range split ──────────────────────────────────────────────────────────────
struct RangeSeg{ Int start,length; };
static std::vector<RangeSeg>
splitRange(const Int& A,const Int& total,unsigned parts){
    std::vector<RangeSeg> seg(parts);
    Int chunk(total); Int div((uint64_t)parts); chunk.Div(&div,nullptr);
    Int lenLast(total);
    if(parts>1){ Int t(chunk); Int m((uint64_t)(parts-1)); t.Mult(&m); lenLast.Sub(&t); }
    for(unsigned i=0;i<parts;++i){
        seg[i].start = A;
        if(i){
            Int off(chunk); Int k((uint64_t)i); off.Mult(&k); seg[i].start.Add(&off);
        }
        seg[i].length = (i==parts-1)?lenLast:chunk;
    }
    return seg;
}

// ─── batch-EC-add ─────────────────────────────────────────────────────────────
template<unsigned N>
static inline void batchAdd(Point* base,Point* plus){
    std::array<Int,N> dX;
    for(unsigned i=0;i<N;++i) dX[i].ModSub(&plus[i].x,&base[i].x);
    static thread_local IntGroup grp(N); grp.Set(dX.data()); grp.ModInv();

    for(unsigned i=0;i<N;++i){
        Int dY; dY.ModSub(&plus[i].y,&base[i].y);
        Int k ; k .ModMulK1(&dY,&dX[i]);
        Int k2; k2.ModSquareK1(&k);
        Int xn(base[i].x); xn.ModNeg(); xn.ModAdd(&k2); xn.ModSub(&plus[i].x);
        Int dx(base[i].x); dx.ModSub(&xn); dx.ModMulK1(&k);
        base[i].x = xn;
        base[i].y.ModNeg();
        base[i].y.ModAdd(&dx);
    }
}

// ─── jump-table ───────────────────────────────────────────────────────────────
static std::vector<Point> jumps;
static void buildJumpTable(unsigned k){
    jumps.resize(k);
#pragma omp parallel for schedule(static)
    for(unsigned i=0;i<k;++i){
        Int e((uint64_t)1); e.ShiftL(int(i+1));
        jumps[i] = mulP(e);
    }
}

// ─── globals ──────────────────────────────────────────────────────────────────
static std::atomic<uint64_t> hops{0}, restarts{0};
static std::atomic<bool>     solved{false};
static Int  privFound;
static std::atomic<unsigned> found_tid{0};
static std::atomic<uint64_t> winner_wraps{0};
static std::once_flag        record_flag;

// ─── wrap helper ──────────────────────────────────────────────────────────────
static inline void addWrapCnt(Int& v,const Int& d,const Int& len,uint64_t& wraps){
    v.Add(&const_cast<Int&>(d));
    if(intGE(v,len)){ v.Sub(&const_cast<Int&>(len)); ++wraps; }
}

// ─── Traps (Phase-1) ─────────────────────────────────────────────────────────
static constexpr unsigned K_DP = 512;
static void buildDP_segment(const RangeSeg& seg,uint64_t target,
                            unsigned k,unsigned bits,uint64_t seed){
    const uint64_t mask = (1ULL<<bits)-1;
    std::mt19937_64 rng(seed);
    std::uniform_int_distribution<uint64_t> rd;

    std::array<Int,   K_DP> dist;
    std::array<uint64_t,K_DP> wraps{};
    std::array<Point, K_DP> cur, stepPts;

    const size_t BATCH_SIZE = 256;
    std::vector<std::pair<fp_t,Int>> batch;
    batch.reserve(BATCH_SIZE);

    auto rndMod=[&](Int& o){
        o.SetInt32(0); int parts=(bitlen(seg.length)+63)/64;
        for(int p=0;p<parts;++p){
            Int t((uint64_t)rd(rng)); t.ShiftL(p*64); o.Add(&t);
        }
        o.Mod(&const_cast<Int&>(seg.length));
    };
    for(unsigned i=0;i<K_DP;++i){
        rndMod(dist[i]); Int a(seg.start); a.Add(&dist[i]); cur[i] = mulP(a);
    }

    uint64_t made = 0;
    while(made < target){
        for(unsigned i=0;i<K_DP;++i){
            uint64_t h = xxhash64(&cur[i].x.bits64[0], 8) % k;
            Int step((uint64_t)1); step.ShiftL(int(h+1));

            if((IntLow64(cur[i].x) & mask) == 0){
                fp_t fp = pointToFp(cur[i]);

                Int scalar(seg.length);
                Int w((uint64_t)wraps[i]); scalar.Mult(&w);
                scalar.Add(&const_cast<Int&>(dist[i]));
                scalar.Add(&const_cast<Int&>(seg.start));
                scalar.Mod(&ORDER_N);

                batch.emplace_back(fp,scalar);
                if(batch.size() >= BATCH_SIZE || made + batch.size() >= target){
#pragma omp critical(dp_insert)
                    {
                        for(auto& it: batch){
                            if(dp_insert_unique(it.first,it.second)){
                                bloom->Add(uint32_t(it.first));
                                ++made;
                                if(made==target) break;
                            }
                        }
                        batch.clear();
                    }
                }
            }

            stepPts[i] = jumps[h];
            addWrapCnt(dist[i],step,seg.length,wraps[i]);
        }
        batchAdd<K_DP>(cur.data(),stepPts.data());
    }
    if(!batch.empty()){
#pragma omp critical(dp_insert)
        {
            for(auto& it: batch){
                if(dp_insert_unique(it.first,it.second)){
                    bloom->Add(uint32_t(it.first));
                    ++made;
                }
            }
            batch.clear();
        }
    }
}

// ─── Phase-2: wild kangaroos ─────────────────────────────────────────────────
static constexpr unsigned K   = 512;
static constexpr unsigned CACHE_LIMIT = 1024;

struct PendingCheck{ fp_t fp; unsigned idx; };

static void worker(uint32_t tid,const RangeSeg& seg,const Point& pub,
                   unsigned k,unsigned bits){
    struct LoopDet{
        uint64_t next,cnt,sig;
        void reset(uint64_t s){ next=1024; cnt=0; sig=s; }
    };

    const uint64_t mask = (1ULL<<bits)-1;
    std::mt19937_64 rng(xxhash64(&tid, sizeof(tid), 0xDEADBEEF));
    std::uniform_int_distribution<uint64_t> rd;

    std::array<Int,   K> dist;
    std::array<uint64_t,K> wraps{};
    std::array<Point, K> cur, stepPts;
    std::array<LoopDet,K> loop;

    auto rndMod=[&](Int& o){
        o.SetInt32(0); int parts=(bitlen(seg.length)+63)/64;
        for(int p=0;p<parts;++p){
            Int t((uint64_t)rd(rng)); t.ShiftL(p*64); o.Add(&t);
        }
        o.Mod(&const_cast<Int&>(seg.length));
    };
    for(unsigned i=0;i<K;++i){
        rndMod(dist[i]);
        cur[i]  = addP(pub,mulP(dist[i]));
        loop[i].reset(pointToFp(cur[i]));
    }

    madvise(dp.slots,dp.mapBytes,MADV_SEQUENTIAL);

    uint64_t local=0; const uint64_t FLUSH = 1ULL<<18;
    std::vector<PendingCheck> cache; cache.reserve(CACHE_LIMIT);

    while(!solved.load()){
        for(unsigned i=0;i<K;++i){
            if(solved.load()) return;

            uint64_t x64 = IntLow64(cur[i].x);
            uint64_t h   = xxhash64(&x64, sizeof(x64)) % k;

            auto& ld = loop[i];
            if(++ld.cnt == ld.next){
                uint64_t sig = pointToFp(cur[i]);
                if(sig == ld.sig){
                    rndMod(dist[i]);
                    cur[i]  = addP(pub,mulP(dist[i]));
                    wraps[i]=0; ld.reset(sig);
                    restarts.fetch_add(1,std::memory_order_relaxed);
                    continue;
                }
                ld.sig = sig; ld.next <<= 1;
            }

            stepPts[i] = jumps[h];
            Int step((uint64_t)1); step.ShiftL(int(h+1));
            addWrapCnt(dist[i],step,seg.length,wraps[i]);
            ++local;
        }
        batchAdd<K>(cur.data(),stepPts.data());
        if(local >= FLUSH){ hops.fetch_add(local); local = 0; }

        for(unsigned i=0;i<K;++i){
            if(solved.load()) return;
            if((IntLow64(cur[i].x)&mask)!=0) continue;

            fp_t fp = pointToFp(cur[i]);
            cache.push_back({fp,i});

            if(cache.size() >= CACHE_LIMIT){
#pragma omp critical(dp_query)
                {
                    for(auto& item: cache){
                        if(!bloom->Find(uint32_t(item.fp))) continue;
                        Int trap;
                        if(!dp_find(item.fp,trap)) continue;

                        Int dw(seg.length);
                        Int w((uint64_t)wraps[item.idx]); dw.Mult(&w);
                        dw.Add(&const_cast<Int&>(dist[item.idx]));
                        dw.Mod(&ORDER_N);

                        Int priv; intCopy(priv,trap); priv.Sub(&dw); priv.Mod(&ORDER_N);
                        Point tst = mulP(priv);
                        if(tst.x.IsEqual(&const_cast<Int&>(pub.x)) &&
                           tst.y.IsEqual(&const_cast<Int&>(pub.y)))
                        {
                            std::call_once(record_flag,[]{});
                            intCopy(privFound,priv);
                            found_tid.store(tid);
                            winner_wraps.store(wraps[item.idx]);
                            solved.store(true);
                        }
                    }
                }
                cache.clear();
                if(solved.load()) return;
            }
        }
    }
    if(local) hops.fetch_add(local);
}

// ─── main ─────────────────────────────────────────────────────────────────────
int main(int argc,char** argv)
{
    /* init curve */
    P_PRIME.SetBase16((char*)P_HEX); ORDER_N.SetBase16((char*)N_HEX); secp.Init();

    /* ── CLI ── */
    Int A,B; uint64_t traps=0; unsigned bits=12; size_t ramGB=8;
    Point pub; unsigned k_user=0; bool saveDP=false, loadDP=false;
    std::string dpFile;
    for(int i=1;i<argc;++i){
        std::string a=argv[i];
        if(a=="--range"){ std::string s=argv[++i]; auto p=s.find(':');
            A.SetBase10((char*)s.substr(0,p).c_str());
            B.SetBase10((char*)s.substr(p+1).c_str());
        }else if(a=="--dp_point") traps=std::strtoull(argv[++i],nullptr,10);
        else if(a=="--dp_bits")  bits=std::stoul(argv[++i]);
        else if(a=="--ram"||a=="--ram-limit") ramGB=std::stoull(argv[++i]);
        else if(a=="--k") k_user=std::stoul(argv[++i]);
        else if(a=="--pubkey"){
            std::string h=argv[++i]; if(h.rfind("0x",0)==0) h.erase(0,2);
            char pc=h[1]; Int x; x.SetBase16((char*)h.substr(2).c_str());
            pub.x=x; pub.y=secp.GetY(x,pc=='2');
        }else if(a=="--save-dp"||a=="-s") saveDP=true;
        else if(a=="--load-dp"){ loadDP=true; dpFile=argv[++i]; }
        else{ std::cerr<<"Unknown "<<a<<'\n'; return 1; }
    }
    if(A.IsZero()||B.IsZero()){ std::cerr<<"--range missing\n"; return 1; }

    /* ── params ── */
    Int range(B); range.Sub(&A);
    unsigned Lbits=range.GetBitLength();
    if(!traps){
        traps=(Lbits>=52)?(1ULL<<(Lbits/2))
             : uint64_t(std::ceil(range.ToDouble()/std::sqrt(range.ToDouble())));
    }
    unsigned k = k_user? k_user : std::max(1u,Lbits/2);

    /* нoвыe пapaмeтpы */
    constexpr double MAX_LOAD     = 0.50;
    constexpr double bloomFactor  = 10.0;  

    size_t cap0 = size_t(std::ceil(double(traps) / MAX_LOAD));
    size_t cap  = 1;
    while(cap < cap0) cap <<= 1;

    dp.init("dp_table.bin",cap);

    size_t bloomBytes=size_t(traps*bloomFactor);
    std::cout<<"\n=========== Phase-0: Data summary ==========\n";
    std::cout<<"DP table (SSD): "<<humanBytes(cap*sizeof(DPSlot))
             <<"  ( "<<traps<<" / "<<cap<<" slots, load "
             <<std::fixed<<std::setprecision(2)
             <<double(traps)/cap*100<<"% )\n";
    std::cout<<"Bloom    (RAM): "<<humanBytes(bloomBytes)<<'\n';

    bloom=new simd_bloom::SimdBlockFilterFixed<>(bloomBytes);

    unsigned th=std::max(1u,std::thread::hardware_concurrency());
    auto segs=splitRange(A,range,th);
    uint64_t per=(traps+th-1)/th;
    buildJumpTable(k);

    // ─── Phase-1 ────────────────────────────────────────────────────────────
    dp.enable_flush.store(false);            
    std::cout<<"\n========== Phase-1: Building traps =========\n";
    if(loadDP){
        if(!loadDPBinary(dpFile)) return 1;
    }else{
        std::thread progress([&]{
            while(dpDone.load()<traps){
                std::cout<<"\rUnique traps: "<<dpDone<<'/'<<traps<<std::flush;
                std::this_thread::sleep_for(std::chrono::milliseconds(250));
            }
            std::cout<<"\rUnique traps: "<<traps<<"/"<<traps<<" (done)\n";
        });
#pragma omp parallel for schedule(static)
        for(unsigned t=0;t<th;++t)
            buildDP_segment(segs[t],per,k,bits,
                            xxhash64(&t, sizeof(t), 0xABCDEF12345678ULL));
        progress.join();
        if(saveDP) saveDPBinary("DP.bin");
    }
    dp.fullSync();                        
    dp.enable_flush.store(true);

    // ─── Phase-2 ────────────────────────────────────────────────────────────
    std::cout<<"\n=========== Phase-2: Kangaroos =============\n";
    auto t0=std::chrono::steady_clock::now();
    std::thread pool([&]{
#pragma omp parallel for num_threads(th) schedule(static)
        for(unsigned id=0;id<th;++id) worker(id,segs[id],pub,k,bits);
    });

    uint64_t lastHops=0;
    auto lastStat=t0;
    while(true){
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        if(solved.load()) break;
        auto nowTick=std::chrono::steady_clock::now();
        if(nowTick-lastStat<std::chrono::seconds(5)) continue;

        double dt=std::chrono::duration<double>(nowTick-lastStat).count();
        lastStat=nowTick;
        uint64_t now=hops.load(); uint64_t delta=now-lastHops; lastHops=now;
        double sp=delta/dt; const char* u=" H/s";
        if(sp>1e6){ sp/=1e6; u=" MH/s";}
        else if(sp>1e3){ sp/=1e3; u=" kH/s";}
        uint64_t sec=std::chrono::duration_cast<std::chrono::seconds>(nowTick-t0).count();

        std::cout<<"\rSpeed: "<<std::fixed<<std::setprecision(2)<<sp<<u
                 <<" | Hops: "<<now
                 <<" | Restart wild: "<<restarts.load()
                 <<" | Time: "<<sec/3600<<':'<<std::setw(2)<<std::setfill('0')
                 <<(sec/60)%60<<':'<<std::setw(2)<<sec%60<<std::flush;
    }
    pool.join();

    // ─── Phase-3 ────────────────────────────────────────────────────────────
    auto msTot=std::chrono::duration_cast<std::chrono::milliseconds>(
                   std::chrono::steady_clock::now()-t0).count();
    uint64_t h=msTot/3'600'000,m=(msTot/60'000)%60,s=(msTot/1'000)%60,ms=msTot%1'000;

    std::cout<<"\n\n============= Phase-3: Result ==============\n";
    std::cout<<"Private key : 0x"<<std::setw(64)<<std::setfill('0')
             <<privFound.GetBase16()<<"\n";
    std::cout<<"Found by thr: "<<found_tid.load()<<"\n";
    std::cout<<"Wild wraps  : "<<winner_wraps.load()
             <<(winner_wraps.load()?"  [wrapped]\n":"  [no wrap]\n");
    std::cout<<"Wild restart: "<<restarts.load()<<"\n";
    std::cout<<"Total time  : "<<std::setw(2)<<h<<':'<<std::setw(2)<<m<<':'
             <<std::setw(2)<<s<<'.'<<std::setw(3)<<ms<<"\n";

    { std::ofstream("FOUND.txt")<<"0x"<<std::setw(64)<<std::setfill('0')
                                <<privFound.GetBase16()<<"\n"; }
    std::cout<<"Private key : saved to FOUND.txt\n";

    delete bloom; dp.close();
    return 0;
}

Makefile
Code:
# Compiler
CXX = g++

# Compiler flags
CXXFLAGS = -m64 -std=c++17 -Ofast -march=native -mtune=native \
           -Wall -Wextra -Wno-write-strings -Wno-unused-variable \
           -Wno-deprecated-copy -Wno-unused-parameter -Wno-sign-compare \
           -Wno-strict-aliasing -Wno-unused-but-set-variable \
           -funroll-loops -ftree-vectorize -fstrict-aliasing \
           -fno-semantic-interposition -fvect-cost-model=unlimited \
           -fno-trapping-math -fipa-ra -flto -fassociative-math \
           -fopenmp -mavx2 -mbmi2 -madx -fwrapv \
           -fomit-frame-pointer -fpredictive-commoning -fgcse-sm -fgcse-las \
           -fmodulo-sched -fmodulo-sched-allow-regmoves -funsafe-math-optimizations

# Linker flags
LDFLAGS = -lxxhash

# Source files
SRCS = Mark1.cpp Int.cpp SECP256K1.cpp Point.cpp Random.cpp IntMod.cpp IntGroup.cpp Timer.cpp

# Object files
OBJS = $(SRCS:.cpp=.o)

# Target executable
TARGET = Mark1

# Link the object files to create the executable and then delete .o files
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) $(LDFLAGS)
rm -f $(OBJS) && chmod +x $(TARGET)

# Compile each source file into an object file
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@

# Clean up build files
clean:
@echo "Cleaning..."
rm -f $(OBJS) $(TARGET)

# Phony targets
.PHONY: all clean

Code:
sudo apt-get install libxxhash-dev


P.S.
Next up is an even tighter package dp_table.bin. After that, it’s all GPU code, no extra fluff for max speed.
11  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 28, 2025, 12:52:34 PM
hashing using XXH64_hash_t 

Extremely fast Hash algorithm?   Tongue

Yeah, XXH64 gets like 10-20 GB/s.   Grin
12  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 28, 2025, 12:23:03 PM
And I have solved a huge problem with solving speed SSD dp storing. Right now all are OK. Solving speed is the same as RAM storing DP.
ChatGPT helped to do it, it take me a blocks of code with an errors (not errors, problems with dp table size and hashing).


You can go even further here. See here how the database packaging is done and hashing using XXH64_hash_t  in bsgs.cpp

https://github.com/AlexanderKud/BSGS_NoMachine


 Wink
13  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 27, 2025, 08:09:05 AM
Why don’t we have these discussions about legality in any of these puzzle topics? I thought this was a Bitcoin forum. Sticky posts explaining that brute-forcing could be considered theft under certain conditions would help. This is the first time I’ve heard this explained here.  Tongue

There’s tons of threads floating around that cover this stuff. It’s like walking into a gym and someone explaining ‘Hey, don’t drop the weights on your feet’. It’s just one of those unspoken rules. But yeah, maybe it wouldn’t hurt to have a quick reminder up top for the new folks.  Grin
14  Bitcoin / Bitcoin Discussion / Re: Bitcoin puzzle transaction ~32 BTC prize to who solves it on: June 26, 2025, 08:52:35 PM
I have to admit, I'm not very good at this.  Tongue


You can always try fishing.It’s one of the best ways to calm your mind and heal your nerves. The discipline, patience, and connection to nature that fishing teaches can steer people away from crime, drug use, and self-destructive cycles. Of course, always fish responsibly. Check local regulations and get a license if needed. But once you do, you might just find that fishing does more than put food on the table; it brings peace to your soul.  Grin
15  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 25, 2025, 12:53:40 PM
I don’t understand why people jump into other people’s script discussions and immediately favor their own solution as the best. Isn’t the point of having separate topics and scripts to allow everyone their own space?  Tongue

Because they’re bored and ain’t got nothin’ better to do with their life. Or maybe they keep their scripts private, so they just talk trash about others instead.  Grin
16  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 25, 2025, 12:32:59 PM

That's why I don't see the point of accelerating the DP lookup via RAM or bloom filters. They don't really help much, maybe just for educational purposes on very low-end puzzles.

The only issue is that finding DPs will be slow for high puzzles, so the database grows pretty slow. But for example, with a DP of 32, puzzles up to 192 bits are manageable with at most 2**64 entries. For Puzzle 135 and a DP of 32, only around 2**35 entries of storage are needed, which is 32 billion entries. This can fit on a single SSD.

You're talking about another hypothetical script that has nothing to do with this one. But okay. Maybe the author of this script will go in that direction. Then we'll talk about those details. Wink
17  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 25, 2025, 11:01:14 AM
New version is ready: https://github.com/Dookoo2/Mark1
And i believe that I can do my own challenge - 100 bit - 1 hour.

I know you can.  Grin
18  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 25, 2025, 10:44:46 AM
I have no idea what you're both talking about. But if the script does what it promises, I don't care about the explanation.  Grin

The proof is as always in the pudding.

NoMachine, KtimesG, I know that in present time it has no any chances for solving 135, but next version will be with GPU integration + distributed DP gen + distributed worker node + oracle node. I know how to do it and I have enough c++ skills for doing it.
GPU code is the most difficult for me, because i have no experience with CUDA. And AI is useless for it (and for senior/staff level c++ coding also).

If you have time to code, go for it. I know how much effort it takes to pull this off... But the real question is whether to sit around and do it without any compensation. Maybe it’s better to go fishing?  Wink
19  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 25, 2025, 10:09:56 AM
WTF are you talking about?


I have no idea what you're talking about.

https://raw.githubusercontent.com/Dookoo2/Mark1/refs/heads/main/Mark1.cpp

This code use a Memory-Mapped DP Table , which allows for:

Persistent storage of DPs,
Efficient concurrent access,
Disk-based extension of capacity beyond available RAM,
Fast lookups via hashing and atomic operations,
Optional loading/saving of DP sets for reuse.


This structure is then stored in a memory-mapped file (dp_table.bin) via mmap, allowing the program to treat a file on disk as if it were directly in memory.

Thus, the statement "DPs do not need instant lookup" is incorrect in this context because:

This implementation requires fast lookups for real-time collision detection.
Using memory-mapped files achieves both performance and persistence.


Regardless of everything said - this script is still useless if someone wants to solve puzzle 135.
It is purely for experimentation and learning. No guarantees are given.
20  Bitcoin / Development & Technical Discussion / Re: Mark1 - pollard rho implementation (38 minutes for 80 bits solving on CPU) on: June 25, 2025, 02:48:52 AM
I think the next point to address regarding the Kangaroo algorithm is to avoid loading the DP into RAM. I think that is a strong limiting point for Kangaroo, especially when it comes to high ranges.

Has anyone thought of any interesting ideas to solve this?.

It ain’t worth makin’, but you could totally build a Memory-Mapped DP Table System that mixes a Bloom filter (in RAM) with a memory-mapped DP table (on disk). Gotta reserve, like, 2-3x the expected DP table size to keep things runnin’ smooth. And for those massive tables over 1TB? Yeah, you’ll need to split ’em across multiple files.Just like I did in my BSGS. But here’s the catch: If someone’s crazy enough to try solvin’ Puzzle 135, they’re gonna need thousands upon thousands of terabytes of storage. Grin
Pages: [1] 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 »
Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!