From 266a9fcd86f037474a6e8e564a6468bc4f2ec423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Strobel?= Date: Sat, 1 Jul 2017 07:53:08 +0200 Subject: [PATCH 1/7] Add minikey decoding. --- bitcoin/base58.py | 27 ++++++++++++++++++++++++++ bitcoin/tests/test_base58.py | 30 +++++++++++++++++++++++++++++ examples/minikey.py | 37 ++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100755 examples/minikey.py diff --git a/bitcoin/base58.py b/bitcoin/base58.py index 4253240e..4d625d6c 100644 --- a/bitcoin/base58.py +++ b/bitcoin/base58.py @@ -23,6 +23,7 @@ _bord = lambda x: x import binascii +from hashlib import sha256 import bitcoin.core @@ -91,6 +92,30 @@ def decode(s): else: break return b'\x00' * pad + res +class InvalidMinikeyError(Base58Error): + """Raised for invalid minikeys""" + pass + +def decode_minikey(minikey): + """Decode minikey in str or bytes to standard base58 bytes + + Minikeys are an old key format, for details see + https://en.bitcoin.it/wiki/Mini_private_key_format. + """ + if isinstance(minikey, str): + minikey = minikey.encode('ascii') + length = len(minikey) + if length not in [22, 30]: + raise InvalidMinikeyError('Minikey length %d is not 22 or 30' % length) + h = sha256(minikey) + h_cs = h.copy() + h_cs.update(b'?') + checksum = _bord(h_cs.digest()[0]) + if checksum != 0: + raise InvalidMinikeyError('Minikey checksum %s is not 0' % checksum) + versioned = b'\x80' + h.digest() + checked = versioned + sha256(sha256(versioned).digest()).digest()[:4] + return encode(checked) class Base58ChecksumError(Base58Error): """Raised on Base58 checksum errors""" @@ -151,6 +176,8 @@ def __repr__(self): 'InvalidBase58Error', 'encode', 'decode', + 'InvalidMinikeyError', + 'decode_minikey', 'Base58ChecksumError', 'CBase58Data', ) diff --git a/bitcoin/tests/test_base58.py b/bitcoin/tests/test_base58.py index a57d1fe1..2ff279dd 100644 --- a/bitcoin/tests/test_base58.py +++ b/bitcoin/tests/test_base58.py @@ -36,6 +36,36 @@ def test_encode_decode(self): self.assertEqual(act_base58, exp_base58) self.assertEqual(act_bin, exp_bin) +class Test_minikey(unittest.TestCase): + + valid_minikeys = [ + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy', '5JPy8Zg7z4P7RSLsiqcqyeAF1935zjNUdMxcDeVrtU1oarrgnB7'), + ('SVY4eSFCF4tMtMohEkpXkoN9FHxDV7', '5JSyovgwfVcuFZBAp8LAta2tMsmscxXv3FvzvJWeKBfycLAmjuZ') + ] + invalid_minikeys = [ + ('', 'Minikey length 0 is not 22 or 30'), + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrR', 'Minikey length 29 is not 22 or 30'), + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRyz', 'Minikey length 31 is not 22 or 30'), + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRz', 'Minikey checksum 213 is not 0'), + ('S6c56bnXQiBjk9mqSYE7yk', 'Minikey checksum 46 is not 0') + ] + + def test_decode_minikey_bytes(self): + for minikey, exp_base58_key in self.valid_minikeys: + base58_key2 = decode_minikey(minikey.encode('ascii')) + self.assertEqual(base58_key2, exp_base58_key) + + def test_decode_minikey_str(self): + for minikey, exp_base58_key in self.valid_minikeys: + base58_key = decode_minikey(minikey) + self.assertEqual(base58_key, exp_base58_key) + + def test_invalid(self): + for minikey, msg in self.invalid_minikeys: + with self.assertRaises(InvalidMinikeyError) as cm: + decode_minikey(minikey) + self.assertEqual(str(cm.exception), msg) + class Test_CBase58Data(unittest.TestCase): def test_from_data(self): b = CBase58Data.from_bytes(b"b\xe9\x07\xb1\\\xbf'\xd5BS\x99\xeb\xf6\xf0\xfbP\xeb\xb8\x8f\x18", 0) diff --git a/examples/minikey.py b/examples/minikey.py new file mode 100755 index 00000000..300fb189 --- /dev/null +++ b/examples/minikey.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2013-2015 The python-bitcoinlib developers +# +# This file is part of python-bitcoinlib. +# +# It is subject to the license terms in the LICENSE file found in the top-level +# directory of this distribution. +# +# No part of python-bitcoinlib, including this file, may be copied, modified, +# propagated, or distributed except according to the terms contained in the +# LICENSE file. + +from __future__ import absolute_import, division, print_function, unicode_literals + +from hashlib import sha256 +from bitcoin import base58 + +def parser(): + import argparse + parser = argparse.ArgumentParser( + description='Decode a minikey to base58 format.', + epilog='Security warning: arguments may be visible to other users on the same host.') + parser.add_argument( + 'minikey', + help='the minikey') + return parser + +if __name__ == '__main__': + args = parser().parse_args() + try: + base58_key = base58.decode_minikey(args.minikey) + except Exception as error: + print('%s: %s' % (error.__class__.__name__, str(error))) + exit(1) + else: + print(base58_key) From bafd3dca7c4986ae6f3b2f48510f3c56a9dd5839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Strobel?= Date: Sat, 1 Jul 2017 08:07:44 +0200 Subject: [PATCH 2/7] Add a valid 22 char minikey to test vector. --- bitcoin/tests/test_base58.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bitcoin/tests/test_base58.py b/bitcoin/tests/test_base58.py index 2ff279dd..8b33ca9f 100644 --- a/bitcoin/tests/test_base58.py +++ b/bitcoin/tests/test_base58.py @@ -40,7 +40,8 @@ class Test_minikey(unittest.TestCase): valid_minikeys = [ ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy', '5JPy8Zg7z4P7RSLsiqcqyeAF1935zjNUdMxcDeVrtU1oarrgnB7'), - ('SVY4eSFCF4tMtMohEkpXkoN9FHxDV7', '5JSyovgwfVcuFZBAp8LAta2tMsmscxXv3FvzvJWeKBfycLAmjuZ') + ('SVY4eSFCF4tMtMohEkpXkoN9FHxDV7', '5JSyovgwfVcuFZBAp8LAta2tMsmscxXv3FvzvJWeKBfycLAmjuZ'), + ('S6c56bnXQiBjk9mqSYEa30', '5KM4V1haDBMEcgzPuAWdHSBAVAEJNp4he2meirV3JNvZz9aWBNH') ] invalid_minikeys = [ ('', 'Minikey length 0 is not 22 or 30'), From 88b5866e1c844aae0ea537360d261f0bd8178c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Strobel?= Date: Sat, 1 Jul 2017 08:09:59 +0200 Subject: [PATCH 3/7] Remove unneeded import of sha256. --- examples/minikey.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/minikey.py b/examples/minikey.py index 300fb189..12ce7f0e 100755 --- a/examples/minikey.py +++ b/examples/minikey.py @@ -13,7 +13,6 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from hashlib import sha256 from bitcoin import base58 def parser(): From 6fd0e6611825048cc138f710d16839c8fd650334 Mon Sep 17 00:00:00 2001 From: juestr Date: Sun, 30 Jul 2017 01:39:33 +0200 Subject: [PATCH 4/7] Revert base58 files. --- bitcoin/base58.py | 27 --------------------------- bitcoin/tests/test_base58.py | 31 ------------------------------- 2 files changed, 58 deletions(-) diff --git a/bitcoin/base58.py b/bitcoin/base58.py index 4d625d6c..4253240e 100644 --- a/bitcoin/base58.py +++ b/bitcoin/base58.py @@ -23,7 +23,6 @@ _bord = lambda x: x import binascii -from hashlib import sha256 import bitcoin.core @@ -92,30 +91,6 @@ def decode(s): else: break return b'\x00' * pad + res -class InvalidMinikeyError(Base58Error): - """Raised for invalid minikeys""" - pass - -def decode_minikey(minikey): - """Decode minikey in str or bytes to standard base58 bytes - - Minikeys are an old key format, for details see - https://en.bitcoin.it/wiki/Mini_private_key_format. - """ - if isinstance(minikey, str): - minikey = minikey.encode('ascii') - length = len(minikey) - if length not in [22, 30]: - raise InvalidMinikeyError('Minikey length %d is not 22 or 30' % length) - h = sha256(minikey) - h_cs = h.copy() - h_cs.update(b'?') - checksum = _bord(h_cs.digest()[0]) - if checksum != 0: - raise InvalidMinikeyError('Minikey checksum %s is not 0' % checksum) - versioned = b'\x80' + h.digest() - checked = versioned + sha256(sha256(versioned).digest()).digest()[:4] - return encode(checked) class Base58ChecksumError(Base58Error): """Raised on Base58 checksum errors""" @@ -176,8 +151,6 @@ def __repr__(self): 'InvalidBase58Error', 'encode', 'decode', - 'InvalidMinikeyError', - 'decode_minikey', 'Base58ChecksumError', 'CBase58Data', ) diff --git a/bitcoin/tests/test_base58.py b/bitcoin/tests/test_base58.py index 8b33ca9f..a57d1fe1 100644 --- a/bitcoin/tests/test_base58.py +++ b/bitcoin/tests/test_base58.py @@ -36,37 +36,6 @@ def test_encode_decode(self): self.assertEqual(act_base58, exp_base58) self.assertEqual(act_bin, exp_bin) -class Test_minikey(unittest.TestCase): - - valid_minikeys = [ - ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy', '5JPy8Zg7z4P7RSLsiqcqyeAF1935zjNUdMxcDeVrtU1oarrgnB7'), - ('SVY4eSFCF4tMtMohEkpXkoN9FHxDV7', '5JSyovgwfVcuFZBAp8LAta2tMsmscxXv3FvzvJWeKBfycLAmjuZ'), - ('S6c56bnXQiBjk9mqSYEa30', '5KM4V1haDBMEcgzPuAWdHSBAVAEJNp4he2meirV3JNvZz9aWBNH') - ] - invalid_minikeys = [ - ('', 'Minikey length 0 is not 22 or 30'), - ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrR', 'Minikey length 29 is not 22 or 30'), - ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRyz', 'Minikey length 31 is not 22 or 30'), - ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRz', 'Minikey checksum 213 is not 0'), - ('S6c56bnXQiBjk9mqSYE7yk', 'Minikey checksum 46 is not 0') - ] - - def test_decode_minikey_bytes(self): - for minikey, exp_base58_key in self.valid_minikeys: - base58_key2 = decode_minikey(minikey.encode('ascii')) - self.assertEqual(base58_key2, exp_base58_key) - - def test_decode_minikey_str(self): - for minikey, exp_base58_key in self.valid_minikeys: - base58_key = decode_minikey(minikey) - self.assertEqual(base58_key, exp_base58_key) - - def test_invalid(self): - for minikey, msg in self.invalid_minikeys: - with self.assertRaises(InvalidMinikeyError) as cm: - decode_minikey(minikey) - self.assertEqual(str(cm.exception), msg) - class Test_CBase58Data(unittest.TestCase): def test_from_data(self): b = CBase58Data.from_bytes(b"b\xe9\x07\xb1\\\xbf'\xd5BS\x99\xeb\xf6\xf0\xfbP\xeb\xb8\x8f\x18", 0) From e9713ad2d23193c452bb3cc8d658d3da9a7594d8 Mon Sep 17 00:00:00 2001 From: juestr Date: Sun, 30 Jul 2017 01:52:40 +0200 Subject: [PATCH 5/7] Add minikey module, using CBitcoinSecret for private keys. --- bitcoin/minikey.py | 50 +++++++++++++++++++++++++++++++++++ bitcoin/tests/test_minikey.py | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 bitcoin/minikey.py create mode 100644 bitcoin/tests/test_minikey.py diff --git a/bitcoin/minikey.py b/bitcoin/minikey.py new file mode 100644 index 00000000..33cc15ad --- /dev/null +++ b/bitcoin/minikey.py @@ -0,0 +1,50 @@ +# Copyright (C) 2013-2014 The python-bitcoinlib developers +# +# This file is part of python-bitcoinlib. +# +# It is subject to the license terms in the LICENSE file found in the top-level +# directory of this distribution. +# +# No part of python-bitcoinlib, including this file, may be copied, modified, +# propagated, or distributed except according to the terms contained in the +# LICENSE file. + +""" +Minikey Handling + +Minikeys are an old key format, for details see +https://en.bitcoin.it/wiki/Mini_private_key_format. +""" + +from __future__ import absolute_import, division, print_function, unicode_literals + +import sys +_bord = (lambda x: x) if sys.version > '3' else ord + +from hashlib import sha256 + +from bitcoin.wallet import CBitcoinSecret + +class InvalidMinikeyError(Exception): + """Raised for invalid minikeys""" + pass + +def decode_minikey(minikey): + """Decode minikey from str or bytes to a CBitcoinSecret""" + if isinstance(minikey, str): + minikey = minikey.encode('ascii') + length = len(minikey) + if length not in [22, 30]: + raise InvalidMinikeyError('Minikey length %d is not 22 or 30' % length) + h0 = sha256(minikey) + h1 = h0.copy() + h1.update(b'?') + checksum = _bord(h1.digest()[0]) + if checksum != 0: + raise InvalidMinikeyError('Minikey checksum %s is not 0' % checksum) + return CBitcoinSecret.from_secret_bytes(h0.digest(), False) + +__all__ = ( + 'InvalidMinikeyError', + 'decode_minikey' +) diff --git a/bitcoin/tests/test_minikey.py b/bitcoin/tests/test_minikey.py new file mode 100644 index 00000000..db152fce --- /dev/null +++ b/bitcoin/tests/test_minikey.py @@ -0,0 +1,50 @@ +# Copyright (C) 2013-2014 The python-bitcoinlib developers +# +# This file is part of python-bitcoinlib. +# +# It is subject to the license terms in the LICENSE file found in the top-level +# directory of this distribution. +# +# No part of python-bitcoinlib, including this file, may be copied, modified, +# propagated, or distributed except according to the terms contained in the +# LICENSE file. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import unittest + +from bitcoin.minikey import * +from bitcoin.wallet import CBitcoinSecret + +class Test_minikey(unittest.TestCase): + + valid_minikeys = [ + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy', '5JPy8Zg7z4P7RSLsiqcqyeAF1935zjNUdMxcDeVrtU1oarrgnB7'), + ('SVY4eSFCF4tMtMohEkpXkoN9FHxDV7', '5JSyovgwfVcuFZBAp8LAta2tMsmscxXv3FvzvJWeKBfycLAmjuZ'), + ('S6c56bnXQiBjk9mqSYEa30', '5KM4V1haDBMEcgzPuAWdHSBAVAEJNp4he2meirV3JNvZz9aWBNH') + ] + invalid_minikeys = [ + ('', 'Minikey length 0 is not 22 or 30'), + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrR', 'Minikey length 29 is not 22 or 30'), + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRyz', 'Minikey length 31 is not 22 or 30'), + ('S6c56bnXQiBjk9mqSYE7ykVQ7NzrRz', 'Minikey checksum 213 is not 0'), + ('S6c56bnXQiBjk9mqSYE7yk', 'Minikey checksum 46 is not 0') + ] + + def test_decode_minikey_bytes(self): + for minikey, exp_base58_key in self.valid_minikeys: + secret_key = decode_minikey(minikey.encode('ascii')) + self.assertIsInstance(secret_key, CBitcoinSecret) + self.assertEqual(str(secret_key), exp_base58_key) + + def test_decode_minikey_str(self): + for minikey, exp_base58_key in self.valid_minikeys: + secret_key = decode_minikey(minikey) + self.assertIsInstance(secret_key, CBitcoinSecret) + self.assertEqual(str(secret_key), exp_base58_key) + + def test_invalid(self): + for minikey, msg in self.invalid_minikeys: + with self.assertRaises(InvalidMinikeyError) as cm: + decode_minikey(minikey) + self.assertEqual(str(cm.exception), msg) From db7b8f9a3a5a760e78cdc4bc2657eea96113b430 Mon Sep 17 00:00:00 2001 From: juestr Date: Sun, 30 Jul 2017 02:26:01 +0200 Subject: [PATCH 6/7] Fix minikey cli tool in examples. --- examples/minikey.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/minikey.py b/examples/minikey.py index 12ce7f0e..dd292a64 100755 --- a/examples/minikey.py +++ b/examples/minikey.py @@ -13,7 +13,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from bitcoin import base58 +from bitcoin.minikey import decode_minikey def parser(): import argparse @@ -28,9 +28,8 @@ def parser(): if __name__ == '__main__': args = parser().parse_args() try: - base58_key = base58.decode_minikey(args.minikey) + secret_key = str(decode_minikey(args.minikey)) + print(secret_key) except Exception as error: print('%s: %s' % (error.__class__.__name__, str(error))) exit(1) - else: - print(base58_key) From 0e86ba256cb7e3e3fd573fd7ee4bae6dc1cc8122 Mon Sep 17 00:00:00 2001 From: juestr Date: Sun, 30 Jul 2017 02:33:54 +0200 Subject: [PATCH 7/7] Add bitcoin.minikey line to README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 915e81e0..dcdf2b6c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ library. Non-consensus critical modules include the following: bitcoin.bloom - Bloom filters (incomplete) bitcoin.net - Network communication (in flux) bitcoin.messages - Network messages (in flux) + bitcoin.minikey - Minikey decoding bitcoin.rpc - Bitcoin Core RPC interface support bitcoin.wallet - Wallet-related code, currently Bitcoin address and private key support