I am starting to get an overview about bitcoin and wanted to write a very simple programme converting private keys into public, keys, addresses etc. However, for some private keys it suddenly fails when I try to compute the compressed address for them. The reason it fails as far as I can see is that the compressed public key I am passing to the function bitcoin.pubkey_to_address() is one digit too short. As it is one of the most famous bitcoin libraries I assume that there is a fundamental error in my understanding. Thus, the question: what am I doing wrong in computing the compressed bitcoin address from a private key?
I have installed the following library: pip3 install bitcoin in the minimal example below, I am using Python 3.8.5 on Ubuntu 20.04.
import bitcoin
class WalletWithBalance:
def __init__(self, _private_key: str):
self.private_key_uncompressed_hex: str = _private_key
# public keys:
self.public_key_uncompressed_as_x_y_tuple_hex = self.get_private_key_as_x_y_tuple()
self.public_key_compressed_hex = self.get_compressed_public_key_hex()
# addresses:
self.address_compressed = bitcoin.pubkey_to_address(self.public_key_compressed_hex)
def get_public_key_as_raw_hex_str(self):
public_key_as_raw_hex_str = bitcoin.encode_pubkey(self.public_key_uncompressed_as_x_y_tuple_hex, 'hex')
return public_key_as_raw_hex_str
def get_private_key_as_x_y_tuple(self):
private_key_raw_decimal_number = bitcoin.decode_privkey(self.private_key_uncompressed_hex, 'hex')
return bitcoin.fast_multiply(bitcoin.G, private_key_raw_decimal_number)
def get_compressed_public_key_hex(self):
(public_key_x, public_key_y) = self.public_key_uncompressed_as_x_y_tuple_hex
return self.get_compressed_prefix(public_key_y) + bitcoin.encode(public_key_x, 16)
#staticmethod
def get_compressed_prefix(public_key_y):
if public_key_y % 2 == 0:
return "02"
else:
return "03"
if __name__ == "__main__":
wallet = WalletWithBalance(_private_key="0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c")
The stacktrace reads:
/usr/bin/python3 /foo/minimal_example.py
Traceback (most recent call last):
File "/foo/minimal_example.py", line 36, in <module>
wallet = WalletWithBalance(_private_key="0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c")
File "/foo/minimal_example.py", line 13, in __init__
self.address_compressed = bitcoin.pubkey_to_address(self.public_key_compressed_hex)
File "/DoeJohn/.local/lib/python3.8/site-packages/bitcoin/main.py", line 452, in pubkey_to_address
return bin_to_b58check(bin_hash160(pubkey), magicbyte)
File "/DoeJohn/.local/lib/python3.8/site-packages/bitcoin/main.py", line 334, in bin_hash160
intermed = hashlib.sha256(string).digest()
TypeError: Unicode-objects must be encoded before hashing
Related
my code has an error in line 14 saying bytes object has no attribute oid. I am not sure why is it giving me this error.
from Crypto.PublicKey import DSA
from Crypto.Signature import DSS
import random
import os
def generate_dsa_key_pair(bits=1024):
"""Generates a DSA key pair with the given number of bits."""
key = DSA.generate(bits)
return key
def sign_text(text, key):
"""Signs the given text using the given DSA private key."""
signer = DSS.new(key, 'fips-186-3')
**signature = signer.sign(text.encode())**
return signature
def create_p2ms_script(n, m):
"""Creates a P2MS script using the given number of public keys and signatures."""
# Generate N DSA key pairs
keys = [generate_dsa_key_pair() for i in range(n)]
# Select M private keys from the N keys
priv_keys = random.sample(keys, m)
# Generate M signatures using the private keys
signatures = [sign_text("help me", priv_key) for priv_key in priv_keys]
# Create scriptPubKey by concatenating the N public keys
scriptPubKey = b''.join([key.publickey().export_key(format='DER') for key in keys])
# Create scriptSig by concatenating the M signatures
scriptSig = b''.join(signatures)
return scriptPubKey, scriptSig
def save_script_to_file(script, filename):
"""Saves the given script to the specified file."""
with open(filename, 'wb') as f:
f.write(script)
def execute_p2ms_script(scriptPubKey, scriptSig):
"""Executes the given P2MS script by verifying the signatures using the public keys."""
# Split scriptPubKey into individual public keys
pub_keys = [DSA.import_key(key) for key in scriptPubKey.split(b'\x00\x02\x81\x81') if key]
# Split scriptSig into individual signatures
signatures = [sig for sig in scriptSig.split(b'\x00\x02\x81\x81') if sig]
# Check if the number of signatures matches the number of public keys
if len(signatures) != len(pub_keys):
return False
# Verify each signature using the corresponding public key
for i, sig in enumerate(signatures):
verifier = DSS.new(pub_keys[i], 'fips-186-3')
if not verifier.verify("help me ".encode(), sig):
return False
return True
if __name__ == '__main__':
n = int(input("Enter the number of public keys (N): "))
m = int(input("Enter the number of signatures (M): "))
# Create P2MS script
scriptPubKey, scriptSig = create_p2ms_script(n, m)
# Save script
I have tried to hash the object but then my code wouldn't work. I have bolded the line, can someone please explain to me?
for context, my code is meant to replicate a P2MS function.
edit: the full traceback is as follows:
Exception has occurred: AttributeError
'bytes' object has no attribute 'oid'
File ", line 14, in sign_text signature = signer.sign(text.encode())
File , line 26, in <listcomp> signatures = [sign_text("help me", priv_key) for priv_key in priv_keys]
File , line 26, in create_p2ms_script signatures = [sign_text("help me", priv_key) for priv_key in priv_keys]
File , line 66, in <module> scriptPubKey, scriptSig = create_p2ms_script(n, m)
AttributeError: 'bytes' object has no attribute 'oid'
The bytes object is what's returned by text.encode(). It's a UTF-8 encoded string and it does not have an attribute oid.
That is not what DSS.sign(msg_hash) expects. It expects a hash object (which conveniently has an attribute oid), see also PyCryptoDome doc on sign. So a Crypto.Hash object has to be created first as described here:
from Crypto.Hash import SHA256
# ... line 14
hash_object = SHA256.new(data=text.encode())
signature = signer.sign(hash_object)
In your question, you said that passing a hash object did not work. However, that's the proper way to do it. You cannot make a wish on an API and expect things to work. See the API as a contract. It specifies what you need to give and what you get in return.
Make your code run without throwing exceptions. Once you're there and you still encounter errors after passing a hash, please post another question. A thorough explanation of expected and actual behavior beyond "would not work" would also be helpful.
I am trying to implement ScroogeCoin using the fastecdsa library. I currently run into an error that happens when my create_coins function is called. The error points to the signing function (tx["signature"]) and says that it cannot convert an integer type to a byte.
import hashlib
import json
from fastecdsa import keys, curve, ecdsa
class ScroogeCoin(object):
def __init__(self):
self.private_key, self.public_key = keys.gen_keypair(curve.secp256k1)
self.address = hashlib.sha256(json.dumps(self.public_key.x).encode()).hexdigest()
self.chain = []
self.current_transactions = []
def create_coins(self, receivers: dict):
"""
Scrooge adds value to some coins
:param receivers: {account:amount, account:amount, ...}
"""
tx = {
"sender" : self.address, # address,
# coins that are created do not come from anywhere
"location": {"block": -1, "tx": -1},
"receivers" : receivers,
}
tx["hash"] = hashlib.sha256(json.dumps(tx).encode()).hexdigest()# hash of tx
tx["signature"] = ecdsa.sign(self.private_key, tx["hash"])# signed hash of tx
self.current_transactions.append(tx)
...
When this function is ran in the main function:
...
Scrooge = ScroogeCoin()
users = [User(Scrooge) for i in range(10)]
Scrooge.create_coins({users[0].address:10, users[1].address:20, users[3].address:50})
...
It produces this error:
Traceback (most recent call last):
File "D:\Scrooge_coin_assignmnet.py", line 216, in <module>
main()
File "D:\Scrooge_coin_assignmnet.py", line 197, in main
Scrooge.create_coins({users[0].address:10, users[1].address:20, users[3].address:50})
File "D:\Scrooge_coin_assignmnet.py", line 27, in create_coins
tx["signature"] = ecdsa.sign(self.private_key, tx["hash"])# signed hash of tx
File "C:\Users\d\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastecdsa\ecdsa.py", line 36, in sign
rfc6979 = RFC6979(msg, d, curve.q, hashfunc, prehashed=prehashed)
File "C:\Users\d\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastecdsa\util.py", line 25, in __init__
self.msg = msg_bytes(msg)
File "C:\Users\d\AppData\Local\Programs\Python\Python311\Lib\site-packages\fastecdsa\util.py", line 153, in msg_bytes
raise ValueError('Msg "{}" of type {} cannot be converted to bytes'.format(
ValueError: Msg "21783419755125685845542189331366569080312572314742637241373298325693730090205" of type <class 'int'> cannot be converted to bytes
I've tried to play around and change it to a byte by using encode on the tx["hash"] as well things like bytes.fromhex() but it still gives the same error. I wanted to ask others who are more skilled and see if they can see how I am messing up.
I am trying to create a public/private key pair using python.
I have created a private key using the following method:
private_key = ''.join(['%x' % random.randrange(16) for x in range(0, 64)])
using this private key I have attempted to use a ecdsa graph to generate to corresponding public key
def privateKeyToPublicKey(s):
sk = ecdsa.SigningKey.from_string(s, curve=ecdsa.SECP256k1)
vk = sk.verifying_key
return ('\04' + sk.verifying_key.to_string())
I have not been able to create the signing key (sk) due to a formatting error where my string is in the wrong format. But I am not sure how/what format the string s should be for SigningKey to work.
I get the following error when running the script:
Traceback (most recent call last):
File "address.py", line 23, in <module>
privateKeyToPublicKey(private_key)
File "address.py", line 20, in privateKeyToPublicKey
sk = ecdsa.SigningKey.from_string(s, curve=ecdsa.SECP256k1)
File "/usr/local/lib/python3.6/dist-packages/ecdsa/keys.py", line
149, in from_string
assert len(string) == curve.baselen, (len(string), curve.baselen)
AssertionError: (64, 32)
Here's a more complete code sample for clarity about the answer. Python3.
from ecdsa import SigningKey, SECP256k1
import sha3, random, binascii
private_key = ''.join(['%x' % random.randrange(16) for x in range(0, 64)])
private_key = bytes(private_key, 'utf-8')
private_key = binascii.unhexlify(private_key)
priv = SigningKey.from_string(private_key, curve=SECP256k1)
pub = priv.get_verifying_key().to_string()
keccak = sha3.keccak_256()
keccak.update(pub)
address = keccak.hexdigest()[24:]
print(address, priv.to_string().hex())
I have realised my error, the input must be in bytes, the private key in hex format. In python2 you may use:
private_key.decode('hex')
or you may use
binascii.unhexlify
in python3
I'm trying to get the names of my top 3 artists of last week with pylast (https://github.com/pylast/pylast) but I run into an error or get I get None as a result and I don't see what I'm doing wrong. pylast is a Python interface to Last.fm.
My code:
import pylast
API_KEY = ""
API_SECRET = ""
username = ""
password_hash = pylast.md5("")
network = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET, username=username, password_hash=password_hash)
user = network.get_authenticated_user();
weekly_artists = user.get_weekly_artist_charts();
# Keep the first three artists.
del weekly_artists[3:]
# Print the artist name and number of songs(weight).
for weekly_artist in weekly_artists:
artist,weight = weekly_artist
print (artist.get_name())
print (artist.get_correction())
artist.get_name() returns
None
artist.get_correction() returns
Traceback (most recent call last):
File "G:\projects\python\lastfm_weekly\lastfm-weekly.py", line 28, in <module>
print (artist.get_correction())
File "C:\Users\..\Python\Python36-32\lib\site-packages\pylast\__init__.py", line 1585, in get_correction
self._request(self.ws_prefix + ".getCorrection"), "name")
File "C:\Users\..\Python\Python36-32\lib\site-packages\pylast\__init__.py", line 1029, in _request
return _Request(self.network, method_name, params).execute(cacheable)
File "C:\Users\..\Python\Python36-32\lib\site-packages\pylast\__init__.py", line 744, in __init__
network._get_ws_auth()
AttributeError: 'str' object has no attribute '_get_ws_auth'
What am I doing wrong?
Here is a quick and dirty solution, i'm sure someone will provide something better but i just installed the package to test and it works.
network = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET)
artists = network.get_top_artists()
del artists[:3]
for i in artists:
artist, weight = i
print('Artist = {}. Weight = {}'.format(artist, weight))
I'm not really familiar with the package, I just installed it to help out with this but I do wonder what "get_name()" and "get_correction()" are as they're not in your provided code.
If they're not functions you created / are defined within your code then I'd look there for the problem.
Also, you're authenticating the user but the documentation explicitly states you don't need to unless you're writing data.
I am creating various processes that do different tasks. One of them and only one of them, has a security module that creates the PyCrypto objects.
So my program starts, creates the various processes, the process that handles messages uses the security module to decrypt and I get the following errors:
firstSymKeybin = self.cipher.decrypt(encFirstSymKeybin, '')
File "/usr/local/lib/python2.7/dist-packages/Crypto/Cipher/PKCS1_v1_5.py", line 206, in decrypt
m = self._key.decrypt(ct)
File "/usr/local/lib/python2.7/dist-packages/Crypto/PublicKey/RSA.py", line 174, in decrypt
return pubkey.pubkey.decrypt(self, ciphertext)
File "/usr/local/lib/python2.7/dist-packages/Crypto/PublicKey/pubkey.py", line 93, in decrypt
plaintext=self._decrypt(ciphertext)
File "/usr/local/lib/python2.7/dist-packages/Crypto/PublicKey/RSA.py", line 235, in _decrypt
r = getRandomRange(1, self.key.n-1, randfunc=self._randfunc)
File "/usr/local/lib/python2.7/dist-packages/Crypto/Util/number.py", line 123, in getRandomRange
value = getRandomInteger(bits, randfunc)
File "/usr/local/lib/python2.7/dist-packages/Crypto/Util/number.py", line 104, in getRandomInteger
S = randfunc(N>>3)
File "/usr/local/lib/python2.7/dist-packages/Crypto/Random/_UserFriendlyRNG.py", line 187, in read
return self._singleton.read(bytes)
File "/usr/local/lib/python2.7/dist-packages/Crypto/Random/_UserFriendlyRNG.py", line 163, in read
return _UserFriendlyRNG.read(self, bytes)
File "/usr/local/lib/python2.7/dist-packages/Crypto/Random/_UserFriendlyRNG.py", line 122, in read
self._check_pid()
File "/usr/local/lib/python2.7/dist-packages/Crypto/Random/_UserFriendlyRNG.py", line 138, in _check_pid
raise AssertionError("PID check failed. RNG must be re-initialized after fork(). Hint: Try Random.atfork()")
AssertionError: PID check failed. RNG must be re-initialized after fork(). Hint: Try Random.atfork()
Decrypting works well on interactive, when not called from a process.
My security module looks like this:
'''
Created on 25 Apr 2013
#author: max
'''
import base64, ast, binascii
from Crypto.Cipher import AES
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
import br_consts
class SecurityMod(object):
'''
classdocs
'''
def __init__(self):
'''
Constructor
'''
super(SecurityMod,self).__init__()
self.privkey = RSA.importKey(open('./privkeyBR.pem', 'r').read())
self.cipher = PKCS1_v1_5.new(self.privkey)
self.ridToKeySalt = {}
#depending on the type of message, encryption format is different
def encrypt(self, msg, rqId, rid):
##code
return encMsg
#return string of object so it can be parse by JSON
def decrypt(self, encMsg, rqId, rid):
#code
return msgObjStr
def pad_data(self,data):
if len(data) == 0:
return data
if len(data) % 16 == 0:
padding_required = 15
else:
padding_required = 15 - (len(data) % 16)
data = '%s\x80' % data
data = '%s%s' % (data, '\x00' * padding_required)
return data
def unpad_data(self,data):
if not data:
return data
data = data.rstrip('\x00')
if data[-1] == '\x80':
return data[:-1]
else:
return data
You need to call Crypto.Random.atfork() after os.fork()
I just put the __init__() in the security module before the other ones
Installing the pycryptodome package fixed this issue for me on Mac and Linux. It is a fork of the Pycrypto package.
pip install pycryptodome
Here is the link to their docs: https://pycryptodome.readthedocs.io/en/latest/index.html