Add files via upload

This commit is contained in:
Isaacdelly 2022-11-20 21:48:14 -08:00 committed by GitHub
parent a364ff493d
commit d4859d0eca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 149 additions and 137 deletions

View File

@ -11,8 +11,11 @@ A Bitcoin wallet collider that brute forces random wallet addresses
<a href="https://www.python.org/downloads/">Python 3.6</a> or higher <a href="https://www.python.org/downloads/">Python 3.6</a> or higher
Python modules listed in the <a href="/requirements.txt">requirements.txt<a/> Python modules listed in the <a href="/requirements.txt">requirements.txt<a/>
Minimum <a href="#memory-consumption">RAM requirements</a> If you have a __Linux__ or __MacOS__ operating system, libgmp3-dev is required. If you have __Windows__ then this is not required. Install by running the command:
```
sudo apt-get install libgmp3-dev
```
# Installation # Installation
@ -36,53 +39,53 @@ This program is essentially a brute forcing algorithm. It continuously generates
# How It Works # How It Works
Private keys are generated randomly to create a 32 byte hexidecimal string using the cryptographically secure `os.urandom()` function. 32 byte hexidecimal strings are generated randomly using the `os.urandom()` function and are used as our private keys.
The private keys are converted into their respective public keys using the `starkbank-ecdsa` Python module. Then the public keys are converted into their Bitcoin wallet addresses using the `binascii` and `hashlib` standard libraries. The private keys are converted into their respective public keys using the `fastecdsa` python library. This is the fastest library to perform secp256k1 signing. If you run this on Windows then `fastecdsa` is not supported, so instead we use `starkbank-ecdsa` library to generate public keys. The public keys are converted into their Bitcoin wallet addresses using the `binascii` and `hashlib` standard libraries.
A pre-calculated database of every P2PKH Bitcoin address with a positive balance is included in this project. The generated address is searched within the database, and if it is found that the address has a balance, then the private key, public key and wallet address are saved to the text file `plutus.txt` on the user's hard drive. A pre-calculated database of every funded P2PKH Bitcoin address is included in this project. The generated address is searched within the database, and if it is found that the address has a balance, then the private key, public key and wallet address are saved to the text file `plutus.txt` on the user's hard drive.
This program also utilizes multiprocessing through the `multiprocessing.Process()` function in order to make concurrent calculations. This program also utilizes multiprocessing through the `multiprocessing.Process()` function in order to make concurrent calculations.
# Efficiency # Efficiency
It takes `0.0032457721` seconds for this progam to brute force a __single__ Bitcoin address. It takes `0.002` seconds for this progam to brute force a __single__ Bitcoin address.
However, through `multiprocessing.Process()` a concurrent process is created for every CPU your computer has. So this program can brute force addresses at a speed of `0.0032457721 ÷ cpu_count()` seconds. However, through `multiprocessing.Process()` a concurrent process is created for every CPU your computer has. So this program can brute force a single address at a speed of `0.002 ÷ cpu_count()` seconds.
# Database FAQ # Database FAQ
An offline database is used to find the balance of generated Bitcoin addresses. Visit <a href="/database/">/database</a> for information. An offline database is used to find the balance of generated Bitcoin addresses. Visit <a href="/database/">/database</a> for information.
# Parameters
This program has optional parameters to customize how it runs:
__help__: `python3 plutus.py help` <br />
Prints a short explanation of the parameters and how they work
__time__: `python3 plutus.pt time` <br />
Brute forces a single address and takes a timestamp of how long it took - used for speed testing purposes
__verbose__: 0 or 1 <br />
`python3 plutus.py verbose=1`: When set to 1, then every bitcoin address that gets bruteforced will be printed to the terminal. This has the potential to slow the program down
`python3 plutus.py verbose=0`: When set to 0, the program will not print anything to the terminal and the bruteforcing will work silently. By default verbose is set to 0
__substring__: `python3 plutus.py substring=8`:
To make the program memory efficient, the entire bitcoin address is not loaded from the database. Only the last <__substring__> characters are loaded. This significantly reduces the amount of RAM required to run the program. if you still get memory errors then try making this number smaller, by default it is set to 8. This opens us up to getting false positives (empty addresses mistaken as funded) with a probability of 1/(16^<__substring__>), however it does NOT leave us vulnerable to false negatives (funded addresses being mistaken as empty) so this is an acceptable compromise.
By default the program runs using `python3 plutus.py verbose=0 substring=8` if nothing is passed.
# Expected Output # Expected Output
Every time this program checks the balance of a generated address, it will print the result to the user. If an empty wallet is found, then the wallet address will be printed to the terminal. An example is: If a wallet with a balance is found, then all necessary information about the wallet will be saved to the text file `plutus.txt`. An example is:
>1Kz2CTvjzkZ3p2BQb5x5DX6GEoHX2jFS45
However, if a wallet with a balance is found, then all necessary information about the wallet will be saved to the text file `plutus.txt`. An example is:
>hex private key: 5A4F3F1CAB44848B2C2C515AE74E9CC487A9982C9DD695810230EA48B1DCEADD<br/> >hex private key: 5A4F3F1CAB44848B2C2C515AE74E9CC487A9982C9DD695810230EA48B1DCEADD<br/>
>WIF private key: 5JW4RCAXDbocFLK9bxqw5cbQwuSn86fpbmz2HhT9nvKMTh68hjm<br/> >WIF private key: 5JW4RCAXDbocFLK9bxqw5cbQwuSn86fpbmz2HhT9nvKMTh68hjm<br/>
>public key: 04393B30BC950F358326062FF28D194A5B28751C1FF2562C02CA4DFB2A864DE63280CC140D0D540EA1A5711D1E519C842684F42445C41CB501B7EA00361699C320<br/> >public key: 04393B30BC950F358326062FF28D194A5B28751C1FF2562C02CA4DFB2A864DE63280CC140D0D540EA1A5711D1E519C842684F42445C41CB501B7EA00361699C320<br/>
>address: 1Kz2CTvjzkZ3p2BQb5x5DX6GEoHX2jFS45<br/> >uncompressed address: 1Kz2CTvjzkZ3p2BQb5x5DX6GEoHX2jFS45<br/>
# Memory Consumption
This program uses approximately 2GB of RAM per CPU. Because this program uses multiprocessing, some data gets shared between threads making it difficult to accurately measure RAM usage.
![Imgur](https://i.imgur.com/9Cq0yf3.png)
The memory consumption stack trace was made by using <a href="https://pypi.org/project/memory-profiler/">mprof</a> to monitor this program brute force 10,000 addresses on a 4 logical processor machine with 8GB of RAM. As a result, 4 child processes were created, each consuming 2100MiB of RAM (~2GB).
# Recent Improvements & TODO # Recent Improvements & TODO
- [X] Fixed typos/formatting
- [ ] Update database
- [ ] Pickle loader
- [ ] Try to fix Memory Error
<a href="https://github.com/Isaacdelly/Plutus/issues">Create an issue</a> so I can add more stuff to improve <a href="https://github.com/Isaacdelly/Plutus/issues">Create an issue</a> so I can add more stuff to improve

221
plutus.py
View File

@ -2,82 +2,47 @@
# Made by Isaac Delly # Made by Isaac Delly
# https://github.com/Isaacdelly/Plutus # https://github.com/Isaacdelly/Plutus
import os from fastecdsa import keys, curve
import pickle from ellipticcurve.privateKey import PrivateKey
import platform
import multiprocessing
import hashlib import hashlib
import binascii import binascii
import multiprocessing import os
from ellipticcurve.privateKey import PrivateKey import sys
import time
DATABASE = r'database/MAR_23_2019/' DATABASE = r'database/11_13_2022/'
def generate_private_key(): def generate_private_key():
""" return binascii.hexlify(os.urandom(32)).decode('utf-8').upper()
Generate a random 32-byte hex integer which serves as a randomly
generated Bitcoin private key.
Average Time: 0.0000061659 seconds
"""
return binascii.hexlify(os.urandom(32)).decode('utf-8').upper()
def private_key_to_public_key(private_key): def private_key_to_public_key(private_key, fastecdsa):
""" if fastecdsa:
Accept a hex private key and convert it to its respective public key. key = keys.get_public_key(int('0x' + private_key, 0), curve.secp256k1)
Because converting a private key to a public key requires SECP256k1 ECDSA return '04' + (hex(key.x)[2:] + hex(key.y)[2:]).zfill(128)
signing, this function is the most time consuming and is a bottleneck in else:
the overall speed of the program. pk = PrivateKey().fromString(bytes.fromhex(private_key))
Average Time: 0.0031567731 seconds return '04' + pk.publicKey().toString().hex().upper()
"""
pk = PrivateKey().fromString(bytes.fromhex(private_key))
return '04' + pk.publicKey().toString().hex().upper()
def public_key_to_address(public_key): def public_key_to_address(public_key):
""" output = []
Accept a public key and convert it to its resepective P2PKH wallet address. alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
Average Time: 0.0000801390 seconds var = hashlib.new('ripemd160')
""" encoding = binascii.unhexlify(public_key.encode())
output = [] var.update(hashlib.sha256(encoding).digest())
alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' var_encoded = ('00' + var.hexdigest()).encode()
var = hashlib.new('ripemd160') digest = hashlib.sha256(binascii.unhexlify(var_encoded)).digest()
encoding = binascii.unhexlify(public_key.encode()) var_hex = '00' + var.hexdigest() + hashlib.sha256(digest).hexdigest()[0:8]
var.update(hashlib.sha256(encoding).digest()) count = [char != '0' for char in var_hex].index(True) // 2
var_encoded = ('00' + var.hexdigest()).encode() n = int(var_hex, 16)
digest = hashlib.sha256(binascii.unhexlify(var_encoded)).digest() while n > 0:
var_hex = '00' + var.hexdigest() + hashlib.sha256(digest).hexdigest()[0:8] n, remainder = divmod(n, 58)
count = [char != '0' for char in var_hex].index(True) // 2 output.append(alphabet[remainder])
n = int(var_hex, 16) for i in range(count): output.append(alphabet[0])
while n > 0: return ''.join(output[::-1])
n, remainder = divmod(n, 58)
output.append(alphabet[remainder])
for i in range(count): output.append(alphabet[0])
return ''.join(output[::-1])
def process(private_key, public_key, address, database): def private_key_to_wif(private_key):
"""
Accept an address and query the database. If the address is found in the
database, then it is assumed to have a balance and the wallet data is
written to the hard drive. If the address is not in the database, then it
is assumed to be empty and printed to the user.
Average Time: 0.0000026941 seconds
"""
if address in database[0] or \
address in database[1] or \
address in database[2] or \
address in database[3]:
with open('plutus.txt', 'a') as file:
file.write('hex private key: ' + str(private_key) + '\n' +
'WIF private key: ' + str(private_key_to_WIF(private_key)) + '\n' +
'public key: ' + str(public_key) + '\n' +
'address: ' + str(address) + '\n\n')
else:
print(str(address))
def private_key_to_WIF(private_key):
"""
Convert the hex private key into Wallet Import Format for easier wallet
importing. This function is only called if a wallet with a balance is
found. Because that event is rare, this function is not significant to the
main pipeline of the program and is not timed.
"""
digest = hashlib.sha256(binascii.unhexlify('80' + private_key)).hexdigest() digest = hashlib.sha256(binascii.unhexlify('80' + private_key)).hexdigest()
var = hashlib.sha256(binascii.unhexlify(digest)).hexdigest() var = hashlib.sha256(binascii.unhexlify(digest)).hexdigest()
var = binascii.unhexlify('80' + private_key + var[0:8]) var = binascii.unhexlify('80' + private_key + var[0:8])
@ -94,45 +59,89 @@ def private_key_to_WIF(private_key):
else: break else: break
return chars[0] * pad + result return chars[0] * pad + result
def main(database): def main(database, args):
""" while True:
Create the main pipeline by using an infinite loop to repeatedly call the private_key = generate_private_key()
functions, while utilizing multiprocessing from __main__. Because all the public_key = private_key_to_public_key(private_key, args['fastecdsa'])
functions are relatively fast, it is better to combine them all into address = public_key_to_address(public_key)
one process.
""" if args['verbose']:
while True: print(address)
private_key = generate_private_key() # 0.0000061659 seconds
public_key = private_key_to_public_key(private_key) # 0.0031567731 seconds if address[-args['substring']:] in database:
address = public_key_to_address(public_key) # 0.0000801390 seconds with open('plutus.txt', 'a') as file:
process(private_key, public_key, address, database) # 0.0000026941 seconds file.write('hex private key: ' + str(private_key) + '\n' +
# -------------------- 'WIF private key: ' + str(private_key_to_wif(private_key)) + '\n'
# 0.0032457721 seconds 'public key: ' + str(public_key) + '\n' +
'uncompressed address: ' + str(address) + '\n\n')
def print_help():
print('''Plutus homepage: https://github.com/Isaacdelly/Plutus
Plutus QA support: https://github.com/Isaacdelly/Plutus/issues
Speed test:
execute 'python3 plutus.py time', the output will be the time it takes to bruteforce a single address in seconds
By default this program runs with parameters:
python3 plutus.py verbose=0 substring=8
verbose: must be 0 or 1. If 1, then every bitcoin address that gets bruteforced will be printed to the terminal. This has the potential to slow the program down. An input of 0 will not print anything to the terminal and the bruteforcing will work silently. By default verbose is 0.
substring: to make the program memory efficient, the entire bitcoin address is not loaded from the database. Only the last <substring> characters are loaded. This significantly reduces the amount of RAM required to run the program. if you still get memory errors then try making this number smaller, by default it is set to 8. This opens us up to getting false positives (empty addresses mistaken as funded) with a probability of 1/(16^<substring>), however it does NOT leave us vulnerable to false negatives (funded addresses being mistaken as empty) so this is an acceptable compromise.''')
sys.exit(0)
def timer(args):
start = time.time()
private_key = generate_private_key()
public_key = private_key_to_public_key(private_key, args['fastecdsa'])
address = public_key_to_address(public_key)
end = time.time()
print(str(end - start))
sys.exit(0)
if __name__ == '__main__': if __name__ == '__main__':
""" args = {
Deserialize the database and read into a list of sets for easier selection 'verbose': 0,
and O(1) complexity. Initialize the multiprocessing to target the main 'substring': 8,
function with cpu_count() concurrent processes. 'fastecdsa': False
""" }
database = [set() for _ in range(4)] if platform.system() in ['Linux', 'Darwin']:
count = len(os.listdir(DATABASE)) args['fastecdsa'] = True
half = count // 2
quarter = half // 2 for arg in sys.argv[1:]:
for c, p in enumerate(os.listdir(DATABASE)): match arg.split('=')[0]:
print('\rreading database: ' + str(c + 1) + '/' + str(count), end = ' ') case 'help':
with open(DATABASE + p, 'rb') as file: print_help()
if c < half: case 'time':
if c < quarter: database[0] = database[0] | pickle.load(file) timer(args)
else: database[1] = database[1] | pickle.load(file) case 'verbose':
else: verbose = arg.split('=')[1]
if c < half + quarter: database[2] = database[2] | pickle.load(file) if verbose in ['0', '1']:
else: database[3] = database[3] | pickle.load(file) args['verbose'] = arg.split('=')[1]
print('DONE') else:
print('invalid input. verbose must be 0(false) or 1(true)')
sys.exit(-1)
case 'substring':
substring = arg.split['='][1]
if substring > 0 and substring < 35:
args['substring'] = substring
else:
print('invalid input. substring must be greater than 0 and less than 35')
sys.exit(-1)
print('reading database files...')
database = set()
for filename in os.listdir(DATABASE):
with open(DATABASE + filename) as file:
for address in file:
address = address.strip()
database.add(address[-args['substring']:])
print('DONE')
# To verify the database size, remove the # from the line below print('database size: ' + str(len(database)))
#print('database size: ' + str(sum(len(i) for i in database))); quit() print('processes spawned: ' + str(multiprocessing.cpu_count()))
for cpu in range(multiprocessing.cpu_count()):
multiprocessing.Process(target = main, args = (database, )).start()
for cpu in range(multiprocessing.cpu_count()):
multiprocessing.Process(target = main, args = (database, args)).start()

View File

@ -1,2 +1 @@
If you find a Bitcoin wallet with a balance, the wallet details will show up here: If you find a Bitcoin wallet with a balance, the wallet details will show up here:

View File

@ -1 +1,2 @@
starkbank-ecdsa==0.1.4 fastecdsa
starkbank-ecdsa