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 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) diff --git a/examples/minikey.py b/examples/minikey.py new file mode 100755 index 00000000..dd292a64 --- /dev/null +++ b/examples/minikey.py @@ -0,0 +1,35 @@ +#!/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 bitcoin.minikey import decode_minikey + +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: + secret_key = str(decode_minikey(args.minikey)) + print(secret_key) + except Exception as error: + print('%s: %s' % (error.__class__.__name__, str(error))) + exit(1)