/cryptotools

Primary LanguagePythonMIT LicenseMIT

About this repo

Barebones Python 3.6+ implementation (no dependencies/standard lib only) of some common cryptographic functions for educational purposes. Feel free to fork the repo and play around with it. Performance is ..abysmal but otherwise it works fine. Please do not use this for anything serious because I am not a security expert.

Install

$ pip install git+https://github.com/mcdallas/cryptotools.git@master#egg=cryptotools

Examples

HD Wallets

from cryptotools.BTC import Xprv

>>> m = Xprv.from_mnemonic('impulse prize erode winner pupil fun off addict ...')
>>> m.encode()
'xprv9s21ZrQH143K38bNJiHY54kkjio8o6aw3bRjCbzi8KgRxNy98avUribz1wk85ToSUV2VwVuc73NJWc2YGwpMtqz7bBFUh9Q77RtJeuh2zvy'

>>> m/44/0/0/0
Xprv(path=m/44/0/0/0, key=L1WKXyMwKnp8wPwAtjwiKWunACY5RSUXAzmS6jDRRHcHnDbeRiKu)

>>> m/0./123/5.  # Use floats for hardened path, alternative is // e.g m//0/123//5
Xprv(path=m/0h/123/5h, key=L3qskbdzgNu4kwjx2QU63q59khpEHVaSbqd2Pc268Jngiha6mbfQ)

>>> M = m.to_xpub()

>>> (m/123/456).to_xpub() == M/123/456
True

>>> (m/44./0./0./0/0).address('P2PKH')  # bip44
'1BTYXdyrBh1yRCDpqyDhoQG896bnzqtaPz'

>>> (m/84./0./0./0/0).address('P2WPKH')  # bip84
'bc1qjnx8cq32z2t72tsmuwql3wz22lywlpcm3w52lk'

Sign/Verify message:

from cryptotools.ECDSA.secp256k1 import generate_keypair, Message

private, public = generate_keypair()

>>> message = Message.from_str('kinakuta')
>>> signature = message.sign(private)
>>> message.verify(signature, public)
True

Verify a transaction:

from cryptotools.BTC import Transaction

tx = Transaction.get('454e575aa1ed4427985a9732d753b37dc711675eb7c977637b1eea7f600ed214')

>>> tx
Transaction(inputs=1, outputs=2)

>>> tx.outputs
[Output(type=P2SH, value=0.0266 BTC),
 Output(type=P2WSH, value=0.00468 BTC)]

>>> tx.verify()  # this runs the bitcoin script
True

Create a transaction and submit it automatically

import os
os.environ['CRYPTOTOOLS_NETWORK'] = 'test'  # sets network to testnet (before library import)

from cryptotools.BTC import PrivateKey, send

key = PrivateKey.from_hex('mysupersecretkey')

>>> send(source='n4SbPWR6EmQMsWaQVYYFXiJgjweGKE4XnQ', to={'n2NGrooSecJaiD6ssp4YqFoj9eZ7GrCJ66': 0.46}, fee=0.01, private=key)
'907b92969cb3a16ddb45591bf2530f177b7f10cef4e62c331596a84f66c3b8c3'  # txid

Create and broadcast manually

import os
os.environ['CRYPTOTOOLS_NETWORK'] = 'test'

from cryptotools.BTC import PrivateKey, Address

private = PrivateKey.from_hex('mysupersecretkey')
address = Address('n2NGrooSecJaiD6ssp4YqFoj9eZ7GrCJ66')

>>> address.balance()
0.55

>>> send_to = {'n4SbPWR6EmQMsWaQVYYFXiJgjweGKE4XnQ': 0.1, 'n2NGrooSecJaiD6ssp4YqFoj9eZ7GrCJ66': 0.4}
>>> tx = address.send(to=send_to, fee=0.05, private=private)

>>> tx
Transaction(inputs=1, outputs=2)

>>> tx.inputs[0].is_signed()
True

>>> tx.verify()  # Make sure transaction is valid before broadcasting
True

>>> tx.broadcast()
'Transaction Submitted'

Create keys/addresses (including segwit)

from cryptotools.BTC import generate_keypair, push, script_to_address, OP
private, public = generate_keypair()

>>> private.hex()
'de4f177274d29f88a5805333e10525f5dd41634455dfadc8849b977802481ccd'

>>> private.wif(compressed=False)
'5KWCAYLo35uZ9ibPTzTUDXESTE6ne8p1eXviYMHwaoS4tpvYCAp'

>>> public.hex()
'047e30fd478b44869850352daef8f5f7a7b5233044018d465431afdc0b436c973e8df1244189d25ae73d90c90cc0f998eb9784adecaecc46e8c536d7d6845fa26e'

>>> public.to_address('P2PKH')
'19dFXDxiD4KrUTNFfcgeekFpQmUC553GzW'

# Simple <key> <OP_CHECKSIG> script
>>> script = push(public.encode(compressed=True)) + OP.CHECKSIG.byte
>>> script_to_address(script, 'P2WSH')
'bc1q8yh8l8ft3220q328hlapqhflpzy6xvkq6u36mctk8gq5pyxm3rwqv5h5dg'

# nested P2WSH into P2SH -- use with caution
>>> script_to_address(script, 'P2WSH-P2SH')
'34eBzenHJEdk5PK9ojuuBZvCRtNhvvysYZ'
from cryptotools.ECDSA.secp256k1 import CURVE, PrivateKey
private = PrivateKey.random()

>>> private.int()
8034465994996476238286561766373949549982328752707977290709076444881813294372

>>> public = private.to_public()
>>> public
PublicKey(102868560361119050321154887315228169307787313299675114268359376451780341556078, 83001804479408277471207716276761041184203185393579361784723900699449806360826)

>>> public.point in CURVE
True

>>> public.to_address('P2WPKH')
'bc1qh2egksgfejqpktc3kkdtuqqrukrpzzp9lr0phn'

Vanity address generator

from cryptotools.BTC.address import vanity

>>> private, public, address = vanity('Bob')  # Takes forever
Found address starting with Bob in 1:17:55 after 80,111 tries

RSA

from cryptotools import RSA
private, public = RSA.generate_keypair(512)

>>> txt = 'deadbeef'
>>> message = RSA.Message.from_hex(txt)
>>> message
b'\xde\xad\xbe\xef'


>>> message.encrypt(public)
>>> message
b'\x05\xe3q\x92\x1c=)\xaev\xe8\x8d\x8c\x9f\x8d\xde\x17\xdc\x95y\x1e\x90N\xf1A\x816\xb7|z\x83...'

>>> message.decrypt(private)
>>> message.hex() == txt
True

>>> message.encrypt(private)
>>> message.decrypt(public)
>>> message.hex() == txt
True

to run tests

$ python -m unittest

from the project directory