Skip to content

directedbit/cryptotools

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

166 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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/directedbit/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'

BIP39 checksum

Say you lost the first of your 12 mnemonic words and you want to filter out the possible mnemonics from 2048 to 128 by verifying the checksum

from cryptotools.BTC.HD import check, WORDS

phrase = "{x} decrease enjoy credit fold prepare school midnight flower wrong false already"

for word in WORDS:
    mnemonic = phrase.format(x=word)
    if check(mnemonic):
        print(mnemonic)

Sign/Verify message

import secrets
from cryptotools.ECDSA.secp256k1 import generate_keypair, Message

private, public = generate_keypair()

message = Message(secrets.token_bytes(32))
sig1 = message.sign(private)          # ECDSA
sig2 = message.sign_schnorr(private)  # Schnorr
message.verify(sig1, public)
# True
message.verify(sig2, 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'

Configuration

By default the library communicates with the bitcoin network (for fetching transactions) via a block explorer but as an alternative you can use a bitcoin node via its RPC interface. Just set the following environmental variables:

CRYPTOTOOLS_BACKEND=rpc
CRYPTOTOOLS_RPC_HOST=localhost
CRYPTOTOOLS_RPC_PORT=8332

and optionally

CRYPTOTOOLS_RPC_USER=myuser
CRYPTOTOOLS_RPC_PW=mypassword

to switch the network to Testnet set

CRYPTOTOOLS_NETWORK=test

to run tests

python -m unittest

from the project directory

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 100.0%