Пишем свой LBC

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
7,535
Реакции
36
Только ленивый не задумывался о том, как бы сбрутить приватный ключ к жирным кошелькам вроде этого https://www.blockchain.com/btc/address/35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP
Существует проект с названием Large Bitcoin Collider https://lbc.cryptoguru.org/ Суть их идеи проста: все кошельки с положительным балансом известны, нужно найти коллизии приватных ключей.


(c) Википедия


Количество всех возможных ключей 2^160 или 115792089237316195423570985008687907852837564279074904382605163141518161494400 Много, правда?
Но смотреть можно по-другому, каждый биткойн-адрес имеет примерно 2^96 закрытых ключей, соответствующих ему. Стакан наполовину полон!


По причине отсутствия личного дата-центра, разворачиваться буду на домашнем пк. Из вложений - был докуплен SHDD на 500 гигабайт за 3500 рублей чтобы хранить на нём >300 гигабайт блоков.




Ставим bitcoind, запускаем синхронизацию full node. Четыре дня и 25%. Долго, однако.


Поискав в сети, был найден сайт https://getbitcoinblockchain.com, но на нем раздача шла ещё медленнее. Снова фейл.


Вбиваем на удачу на рутрекере и, о чудо, добрый русский человек на раздаче почти круглосуточно https://rutracker.org/forum/viewtopic.php?t=5520053. За ночь раздача скачалась, отлично!




Запускаем rpc-сервер bitcoind -rpcpassword=pass -rpcuser=user -server -datadir=/path/to/blockchain, долго ждём рескана блоков. Пока приступаем к написанию нашего "коллайдера"


Код:
// 1BTC
func genLegacy() (string, string) {
    priv, err := btcec.NewPrivateKey(btcec.S256())
    if err != nil {
        log.Fatal(err)
    }
    byteHash := btcutil.Hash160(priv.PubKey().SerializeCompressed())
    addr, err := btcutil.NewAddressPubKeyHash(byteHash, &chaincfg.MainNetParams)
    if err != nil {
        log.Fatal(err)
    }
    wif, _ := btcutil.NewWIF(priv, &chaincfg.MainNetParams, false)
    return wif.String(), addr.String()
}


Изначально, в аккаунт добавлялись просто адреса для мониторинга, это было ошибочное решение, т.к. в один момент сторадж был удалён, а через некоторое время прошли 4 транзакции по этим адресам. Мелкие, но всё же. Теперь приватные ключи нужно импортировать в наш wallet.


Код:
func importPriv(privKey string) {
    wif, _ := btcutil.DecodeWIF(privKey)
    if err := client.ImportPrivKeyRescan(wif, "", true); err != nil {
        log.Fatal(err)
    }
}


И rpc-клиент


Код:
func newClient() *rpcclient.Client {
    connCfg := &rpcclient.ConnConfig{
        Host:         "localhost:8332",
        User:         "user",
        Pass:         "pass",
        HTTPPostMode: true,
        DisableTLS:   true,
    }
    client, err := rpcclient.New(connCfg, nil)
    if err != nil {
        log.Fatal(err)
    }
    return client
}




Скорость оставляла желать лучшего, а импортировав примерно ~300k ключей скорость упала до 8-12 ключей в секунду на импорте.




Код:
$ stat wallet.dat


  Файл: wallet.dat


  Размер: 339472384     Блоков: 663040     Блок В/В: 4096   обычный файл


Поскольку 24/7 наблюдать нереально, нужно мониторить и делать автослив.
Проверка и автослив положительного баланса


Код:
func autoSendBalance() {
    for {
        balance, err := client.GetBalance("*")
        if err != nil {
            log.Println(err)
            continue
        }
        if balance.ToBTC() > 0 {
            myAddress, err := btcutil.DecodeAddress(TO, &chaincfg.MainNetParams)


            //SendFrom requires to the wallet to be unlocked
            if err := client.WalletPassphrase(secret, 60); err != nil {
                panic(err)
            }
            _, err = client.SendFrom("*", myAddress, balance)
            if err != nil {
                log.Println(err)
                continue
            }
        }
        time.Sleep(time.Second * 1)
    }
}




Прошло время, количество импортированных ключей перевалило за 3 миллиона, rpc-сервер еле ворочался, а денег на личный дата-центр по-прежнему как не было, так и нет.


Меняем концепцию, больше никаких импортов в wallet. Сторим приватный ключ и соответствующий сжатый адрес в kv-хранилище. Я выбрал библиотеку boltdb (https://github.com/etcd-io/bbolt) потому что давно с ней работаю и она себя отлично зарекомендовала. Хранилище


Код:
func saveToStorage(addr, priv string, db *bolt.DB) error {
    db.Update(func(tx *bolt.Tx) error {
    _, err := tx.CreateBucketIfNotExists([]byte("Bitcoin"))
    if err != nil {
        return err
    }
    return nil
    })
    db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte("Bitcoin"))
        err := b.Put([]byte(addr), []byte(priv))
        return err
    })
}




Воркер будет таким


Код:
func genWorker() {
    var priv, addr string
    for {
        switch format {
        case "legacy":
            priv, addr = genLegacy()
        /*
        case "p2sh":
            priv, addr = genP2SH()
        case "segwit":
            priv, addr = genSegWit()
        */
        default:
            panic("format not defined")
        }
        saveToStorage(addr, priv)
    }
}




Теперь нужно находить приватный ключ по адресу, если, конечно же, он имеется в нашей базе.


Код:
func getPrivKey(addr string, db *bolt.DB) []byte {
    var priv []byte
    db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte("Bitcoin"))
        priv = b.Get([]byte(addr))
    })
    return priv
}




Ну а теперь, пока `genWorker` работает в отдельной goroutine, будем мониторить транзакции. Как только будет проведена новая, проверим нет ли в нашем хранилище приватного ключа, которому соответствует txOut. И если соответствует, сделаем перевод заветных биточков на нужный кош.


Код:
func txTrack() error {
    bc, err := client.GetBlockCount()
    if err != nil {
        return err
    }


    chHash, err := client.GetBlockHash(bc)
    if err != nil {
        return err
    }


    block, err := client.GetBlock(chHash)
    if err != nil {
        return err
    }


    hashes, err := block.TxHashes()
    if err != nil {
        return err
    }
    for _, hash := range hashes {
        parseRawTx(hash.String())
    }
    return nil
}






Собственно, осталось только добавить параметры коммандной строки для удобства, мне достаточно трёх:


- только генерировать новые пары


- генерировать, проверять баланс, отправлять на мой кош


- слить две базы в одну


1 и 3 нужно для того, чтобы в дороге, например, можно было нагружать ноут чем-нибудь полезным,а дома уже объединять с основной. Хотя полезность, конечно, сомнительна.


Код:
func joinDB(src, dst string) error {
    srcDb, err := bolt.Open(src, 0600, nil)
    if err != nil {
        return err
    }
    defer srcDb.Close()
    dstDb, err := bolt.Open(dst, 0600, nil)
    if err != nil {
        return err
    }
    defer dstDb.Close()
    srcDb.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte("Bitcoin"))
        c := b.Cursor()
        for k, v := c.First(); k != nil; k, v = c.Next() {
            dstDb.Update(func(tx *bolt.Tx) error {
                b := tx.Bucket([]byte("Bitcoin"))
                err := b.Put(k, v)
                return err
            })
        }
        return nil
    })
    return nil
}


Ну и всё, собственно. Запускаем демон bitcoind с ресканом и построением индексов по транзам, запускаем нашу лотерею и, желательно, забываем о ней. Возможно, через какое-то время будет очень приятный сюрприз. В любом случае, затрат никаких, кроме электроэнергии, да и чем чёрт не шутит.


P.S. Предугадывая главный вопрос, отвечаю сразу - нет, больше коллизий не найдено. Пока не найдено
yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
Но количество приватный ключей уже измеряется не миллионами, а гигабайтами, так что всё может случится.


Автор: corax
 

Members, viewing this thread

Сейчас на форуме нет ни одного пользователя.