コード例 #1
0
 def test_from_random(self):
     secret = int_to_be_bytes(39823453, 32)
     values = [secret, int_to_be_bytes(0, 32), int_to_be_bytes(CURVE_ORDER, 32)]
     def source(size):
         assert size == 32
         return values.pop()
     p = PrivateKey.from_random(source=source)
     assert p.to_bytes() == secret
     assert p.coin() is Bitcoin
     assert p.is_compressed()
コード例 #2
0
 def test_mult_bad(self):
     p1 = PrivateKey.from_random()
     with pytest.raises(ValueError):
         p1.multiply(bytes(32))
     with pytest.raises(ValueError):
         p1.multiply(int_to_be_bytes(CURVE_ORDER))
     with pytest.raises(ValueError):
         p1.multiply(int_to_be_bytes(CURVE_ORDER + 1))
     with pytest.raises(ValueError):
         p1.add(bytes(33))
     with pytest.raises(ValueError):
         p1.add(b'1' * 31)
     with pytest.raises(TypeError):
         p1.add('')
コード例 #3
0
 def test_add_bad(self):
     p1 = PrivateKey.from_random()
     with pytest.raises(ValueError):
         p1.add(int_to_be_bytes(CURVE_ORDER - p1.to_int()))
     with pytest.raises(ValueError):
         p1.add(int_to_be_bytes(CURVE_ORDER))
     with pytest.raises(ValueError):
         p1.add(int_to_be_bytes(CURVE_ORDER + 1))
     with pytest.raises(ValueError):
         p1.add(bytes(33))
     with pytest.raises(ValueError):
         p1.add(b'1' * 31)
     with pytest.raises(TypeError):
         p1.add('')
コード例 #4
0
 def test_combine_keys_bad_intermediate(self):
     priv = PrivateKey.from_random()
     priv2 = PrivateKey(
         int_to_be_bytes(CURVE_ORDER - priv.to_int(), size=32))
     # But combining with bad intermediate result but good final is fine
     P = PublicKey.combine_keys(
         [priv.public_key, priv2.public_key, priv.public_key])
     assert P == priv.public_key
コード例 #5
0
 def test_from_arbitrary_bytes(self):
     p = PrivateKey.from_arbitrary_bytes(b'BitcoinSV')
     assert p._secret == bytes(23) + b'BitcoinSV'
     p = PrivateKey.from_arbitrary_bytes(double_sha256(b'a') + double_sha256(b'b'))
     assert p.to_hex() == '6d2611b247da5cfe25f82673fb52d1739635bedfd3f1c2aa0bc5b10c044f18e9'
     with pytest.raises(ValueError):
         PrivateKey.from_arbitrary_bytes(bytes(46))
     with pytest.raises(ValueError):
         PrivateKey.from_arbitrary_bytes(int_to_be_bytes(CURVE_ORDER) * 10)
コード例 #6
0
    def test_add_bad(self):
        priv = PrivateKey.from_random()
        P = priv.public_key

        value = int_to_be_bytes(CURVE_ORDER - priv.to_int())
        with pytest.raises(ValueError):
            P.add(value)
        with pytest.raises(ValueError):
            P.add(bytes(33))
        with pytest.raises(ValueError):
            P.add(b'1' * 31)
        with pytest.raises(TypeError):
            P.add('')
コード例 #7
0
 def test_combine_keys_bad(self):
     priv = PrivateKey.from_random()
     priv2 = PrivateKey(
         int_to_be_bytes(CURVE_ORDER - priv.to_int(), size=32))
     with pytest.raises(ValueError):
         PublicKey.combine_keys([priv.public_key, priv2.public_key])
コード例 #8
0
class TestPrivateKey:
    @pytest.mark.parametrize("bad_key", (
        1,
        'g',
    ))
    def test_bad_type(self, bad_key):
        with pytest.raises(TypeError):
            PrivateKey(bad_key)

    @pytest.mark.parametrize("bad_key", (
        int_to_be_bytes(CURVE_ORDER),
        int_to_be_bytes(CURVE_ORDER + 1),
        b'',
        bytes(30) + bytes([1]),
        bytes(32),
    ))
    def test_bad_value(self, bad_key):
        with pytest.raises(ValueError):
            PrivateKey(bad_key)

    @pytest.mark.parametrize("good_key", (
        one,
        bytes(31) + bytes([1]),
        int_to_be_bytes(CURVE_ORDER - 1),
    ))
    def test_good(self, good_key):
        p = PrivateKey(good_key)

    def test_constructor(self):
        secret = os.urandom(32)
        p1 = PrivateKey(secret, True, Bitcoin)
        p2 = PrivateKey(secret, network=BitcoinTestnet, compressed=False)
        assert p1.to_bytes() is secret and p2.to_bytes() is secret
        assert p1 == p2
        assert p1.network() is Bitcoin and p2.network() is BitcoinTestnet
        assert p1.is_compressed()
        assert not p2.is_compressed()
        p3 = PrivateKey(secret)
        assert p3.network() is Bitcoin
        assert p3.is_compressed()

    def test_from_arbitrary_bytes(self):
        p = PrivateKey.from_arbitrary_bytes(b'BitcoinSV')
        assert p._secret == bytes(23) + b'BitcoinSV'
        p = PrivateKey.from_arbitrary_bytes(
            double_sha256(b'a') + double_sha256(b'b'))
        assert p.to_hex(
        ) == '6d2611b247da5cfe25f82673fb52d1739635bedfd3f1c2aa0bc5b10c044f18e9'
        with pytest.raises(ValueError):
            PrivateKey.from_arbitrary_bytes(bytes(46))
        with pytest.raises(ValueError):
            PrivateKey.from_arbitrary_bytes(int_to_be_bytes(CURVE_ORDER) * 10)

    def test_eq(self):
        secret = os.urandom(32)
        p1 = PrivateKey(secret)
        p2 = PrivateKey(bytes(secret))
        assert p1 is not p2
        assert p1 == p2
        p2 = PrivateKey(os.urandom(32))
        assert p1 != p2
        # Non-PrivateKeys
        assert p1 != 0
        assert p1 != 'foo'

    def test_hashable(self):
        secret = os.urandom(32)
        p1 = PrivateKey(secret, True, Bitcoin)
        p2 = PrivateKey(secret, False, BitcoinTestnet)
        assert len({p1, p2}) == 1

    def test_public_key(self):
        secret = os.urandom(32)
        for network in (Bitcoin, BitcoinTestnet):
            for compressed in (False, True):
                p = PrivateKey(secret, compressed, network)
                P = p.public_key
                assert P.network() is network
                assert P.is_compressed() is compressed

    def test_public_key_bad(self):
        # Force coverage with a fake condition
        priv = PrivateKey.from_random()
        priv._secret = bytes(32)
        with pytest.raises(RuntimeError):
            priv.public_key

    def test_to_int(self):
        secret = os.urandom(32)
        p = PrivateKey(secret)
        assert p.to_int() == int.from_bytes(secret, 'big')

    def test_to_bytes(self):
        secret = os.urandom(32)
        p = PrivateKey(secret)
        assert p.to_bytes() == secret

    @pytest.mark.parametrize("value", [
        1,
        283758232,
        1 << 31,
    ])
    def test_from_int(self, value):
        p = PrivateKey.from_int(value)
        assert p.to_int() == value
        assert p.network() is Bitcoin
        assert p.is_compressed()

    @pytest.mark.parametrize("value", [0, 0x0, 00])
    def test_from_int_bad(self, value):
        with pytest.raises(ValueError):
            PrivateKey.from_int(value)

    @pytest.mark.parametrize("value", [-1, 1 << 256])
    def test_from_int_overflow(self, value):
        with pytest.raises(OverflowError):
            PrivateKey.from_int(value)

    def test_to_hex(self):
        secret = os.urandom(32)
        p = PrivateKey(secret)
        assert p.to_hex() == secret.hex()

    @pytest.mark.parametrize("value", [
        1,
        283758232,
        1 << 31,
    ])
    def test_from_hex(self, value):
        hex_str = hex(value)[2:]  # Drop 0x
        if len(hex_str) < 64:
            hex_str = '0' * (64 - len(hex_str)) + hex_str
        p = PrivateKey.from_hex(hex_str)
        assert p.to_int() == value
        assert p.network() is Bitcoin
        assert p.is_compressed()

    @pytest.mark.parametrize("hex_str",
                             ['', '00', '2345', '  ' + '11' * 30 + '  '])
    def test_from_hex_bad(self, hex_str):
        with pytest.raises(ValueError):
            PrivateKey.from_hex(hex_str)

    def test_from_text(self):
        priv = PrivateKey.from_random()
        # Hex
        priv2 = PrivateKey.from_text(priv.to_hex())
        assert priv2 == priv
        # Minikey
        assert (PrivateKey.from_text('SZEfg4eYxCJoqzumUqP34g') ==
                PrivateKey.from_minikey('SZEfg4eYxCJoqzumUqP34g'))
        # WIF
        assert (PrivateKey.from_text(
            'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617') ==
                PrivateKey.from_WIF(
                    'KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617'))

    @pytest.mark.parametrize("text", [
        '01' * 31, 'SZEfg4eYxCJoqzumUqP34',
        'wdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617'
    ])
    def test_from_text_bad(self, text):
        with pytest.raises(ValueError):
            PrivateKey.from_text(text)

    def test_from_random(self):
        secret = int_to_be_bytes(39823453, 32)
        values = [
            secret,
            int_to_be_bytes(0, 32),
            int_to_be_bytes(CURVE_ORDER, 32)
        ]

        def source(size):
            assert size == 32
            return values.pop()

        p = PrivateKey.from_random(source=source)
        assert p.to_bytes() == secret
        assert p.network() is Bitcoin
        assert p.is_compressed()

    @pytest.mark.parametrize(
        "minikey",
        ['SZEfg4eYxCJoqzumUqP34g', 'S6c56bnXQiBjk9mqSYE7ykVQ7NzrRy'])
    def test_from_minikey(self, minikey):
        p = PrivateKey.from_minikey(minikey)
        assert p._secret == sha256(minikey.encode())
        assert p.network() is Bitcoin
        assert not p.is_compressed()

    @pytest.mark.parametrize(
        "minikey",
        ['SZEfg4eYxCJoqzumUqP34h', 'S6c56bnXQiBjk9mqSYE7ykVQ7NzrRz'])
    def test_from_minikey_bad(self, minikey):
        with pytest.raises(ValueError):
            PrivateKey.from_minikey(minikey)

    @pytest.mark.parametrize("network,WIF,hex_str,compressed", WIF_tests)
    def test_from_WIF(self, network, WIF, hex_str, compressed):
        p = PrivateKey.from_WIF(WIF)
        assert p.to_hex() == hex_str
        assert p.network() is network
        assert p._compressed == compressed
        assert len(p.public_key.to_bytes()) == (33 if compressed else 65)
        assert p.to_WIF() == WIF

    def test_from_WIF_bad(self):
        with pytest.raises(TypeError):
            PrivateKey.from_WIF(b'6')
        with pytest.raises(TypeError):
            PrivateKey.from_WIF(1)
        with pytest.raises(ValueError):
            PrivateKey.from_WIF('4t9WKfuAB8')

    @pytest.mark.parametrize("value", list(range(256)))
    def test_from_WIF_bad_suffix_byte(self, value):
        payload = pack_byte(
            Bitcoin.WIF_byte) + global_privkey.to_bytes() + pack_byte(value)
        WIF = base58_encode_check(payload)
        if value == 0x01:
            PrivateKey.from_WIF(WIF)
        else:
            with pytest.raises(ValueError):
                PrivateKey.from_WIF(WIF)

    @pytest.mark.parametrize("network,WIF,hex_str,compressed", WIF_tests)
    def test_to_WIF(self, network, WIF, hex_str, compressed):
        p = PrivateKey.from_hex(hex_str)
        assert p.to_WIF(network=network, compressed=compressed) == WIF

    def test_to_WIF_no_args(self):
        p = PrivateKey.from_random()
        assert p.to_WIF() == p.to_WIF(network=Bitcoin, compressed=True)

    def test_add_one(self):
        p1 = PrivateKey.from_random()
        p2 = p1.add(one)
        assert p2.to_int() == p1.to_int() + 1

    def test_subtract_one(self):
        p1 = PrivateKey.from_random()
        p2 = p1.add(int_to_be_bytes(CURVE_ORDER - 1))
        assert p1.to_int() - 1 == p2.to_int()

    def test_add(self):
        p1 = PrivateKey.from_random()
        p2 = PrivateKey.from_random()
        p2_int = p2.to_int()
        result = (p1.to_int() + p2_int) % CURVE_ORDER
        p3 = p2.add(p1._secret)
        assert p3.to_int() == result
        assert p2.to_int() == p2_int

    @pytest.mark.parametrize("network,WIF,hex_str,compressed", WIF_tests)
    def test_add_preserves_attributes(self, network, WIF, hex_str, compressed):
        p = PrivateKey.from_WIF(WIF).add(one)
        assert p.network() is network
        assert p.is_compressed() is compressed

    def test_add_bad(self):
        p1 = PrivateKey.from_random()
        with pytest.raises(ValueError):
            p1.add(int_to_be_bytes(CURVE_ORDER - p1.to_int()))
        with pytest.raises(ValueError):
            p1.add(int_to_be_bytes(CURVE_ORDER))
        with pytest.raises(ValueError):
            p1.add(int_to_be_bytes(CURVE_ORDER + 1))
        with pytest.raises(ValueError):
            p1.add(bytes(33))
        with pytest.raises(ValueError):
            p1.add(b'1' * 31)
        with pytest.raises(TypeError):
            p1.add('')

    def test_mult_one(self):
        p1 = PrivateKey.from_random()
        value = p1.to_int()
        p2 = p1.multiply(one)
        assert p2.to_int() == value

    def test_mult(self):
        p1 = PrivateKey.from_random()
        p2 = PrivateKey.from_random()
        p2_int = p2.to_int()
        result = (p1.to_int() * p2_int) % CURVE_ORDER
        p3 = p2.multiply(p1._secret)
        assert p3.to_int() == result
        assert p2.to_int() == p2_int

    @pytest.mark.parametrize("network,WIF,hex_str,compressed", WIF_tests)
    def test_mult_preserves_attributes(self, network, WIF, hex_str,
                                       compressed):
        p = PrivateKey.from_WIF(WIF).multiply(one)
        assert p.network() is network
        assert p.is_compressed() is compressed

    def test_mult_bad(self):
        p1 = PrivateKey.from_random()
        with pytest.raises(ValueError):
            p1.multiply(bytes(32))
        with pytest.raises(ValueError):
            p1.multiply(int_to_be_bytes(CURVE_ORDER))
        with pytest.raises(ValueError):
            p1.multiply(int_to_be_bytes(CURVE_ORDER + 1))
        with pytest.raises(ValueError):
            p1.add(bytes(33))
        with pytest.raises(ValueError):
            p1.add(b'1' * 31)
        with pytest.raises(TypeError):
            p1.add('')

    def test_sign(self):
        p = PrivateKey(bytes(range(32)))
        msg = b'BitcoinSV'
        result = p.sign(msg)
        assert result.hex() == (
            '3045022100914038d25ac6d195fa8e9b25c6c22a1cb711fde4a11fe305175b52fc77a12'
            '04002202575b92f63cc6b08893cbd6791d41259129c7d9d5162258c19836976871f63bd'
        )
        assert p.sign(msg, hasher=sha256) == result
        assert p.sign(sha256(msg), hasher=None) == result

    def test_sign_bad_privkey(self):
        p = PrivateKey(bytes(range(32)))
        p._secret = bytes(32)
        msg = b'BitcoinSV'
        with pytest.raises(ValueError):
            p.sign(msg)

    def test_sign_bad_hasher(self):
        p = PrivateKey(bytes(range(32)))
        msg = b'BitcoinSV'

        def bad_hasher(data):
            return sha256(data)[:31]

        with pytest.raises(ValueError):
            p.sign(msg, hasher=bad_hasher)

    def test_sign_string(self):
        p = PrivateKey(bytes(range(32)))
        msg = 'BitcoinSV'
        with pytest.raises(TypeError):
            p.sign(msg)

    def test_sign_recoverable(self):
        p = PrivateKey(bytes(range(32)))
        msg = b'BitcoinSV'
        result = p.sign_recoverable(msg)
        assert result.hex() == (
            '914038d25ac6d195fa8e9b25c6c22a1cb711fde4a11fe305175b52fc77a12040'
            '2575b92f63cc6b08893cbd6791d41259129c7d9d5162258c19836976871f63bd01'
        )
        assert p.sign_recoverable(msg, hasher=sha256) == result
        assert p.sign_recoverable(sha256(msg), hasher=None) == result

    def test_sign_recoverable_bad_privkey(self):
        p = PrivateKey(bytes(range(32)))
        p._secret = bytes(32)
        msg = b'BitcoinSV'
        with pytest.raises(ValueError):
            p.sign_recoverable(msg)

    def test_sign_recoverable_bad_hasher(self):
        p = PrivateKey(bytes(range(32)))
        msg = b'BitcoinSV'

        def bad_hasher(data):
            return sha256(data)[:31]

        with pytest.raises(ValueError):
            p.sign_recoverable(msg, hasher=bad_hasher)

    def test_sign_recoverable_string(self):
        p = PrivateKey(bytes(range(32)))
        msg = 'BitcoinSV'
        with pytest.raises(TypeError):
            p.sign_recoverable(msg)

    @pytest.mark.parametrize("msg", (b'BitcoinSV', 'BitcoinSV'))
    def test_sign_message_and_to_base64(self, msg):
        secret = 'L4n6D5GnWkASz8RoNwnxvLXsLrn8ZqUMcjF3Th2Uas476qusFKYf'
        priv = PrivateKey.from_WIF(secret)
        priv._compressed = True
        msg_sig = priv.sign_message(msg)
        for encoded_sig in (b64encode(msg_sig).decode(),
                            priv.sign_message_to_base64(msg)):
            assert encoded_sig == (
                'IIccCk2FG2xufHJmSqnrnOo/b6gPw+A+EpVAJEqfSqV0Nu'
                'LXYiio6UZfvY/vmuI6jyNj/REuTFxxkhBM+zWA7jE=')

        priv._compressed = False
        msg_sig = priv.sign_message(msg)
        assert b64encode(msg_sig) == (
            b'HIccCk2FG2xufHJmSqnrnOo/b6gPw+A+EpVAJEqfSqV0Nu'
            b'LXYiio6UZfvY/vmuI6jyNj/REuTFxxkhBM+zWA7jE=')

    def test_sign_message_hash(self):
        priv = PrivateKey.from_random()
        # Not a hash, a message
        priv.sign_message(bytes(32))
        # We don't permit signing message hashes directly
        with pytest.raises(ValueError):
            priv.sign_message(bytes(32), hasher=None)

    def test_sign_message_long(self):
        secret = 'L4n6D5GnWkASz8RoNwnxvLXsLrn8ZqUMcjF3Th2Uas476qusFKYf'
        priv = PrivateKey.from_WIF(secret)
        P = priv.public_key
        msg = (
            'A purely peer-to-peer version of electronic cash would allow online payments to be se'
            'nt directly from one party to another without going through a financial institution. '
            'Digital signatures provide part of the solution, but the main benefits are lost if '
            'a trusted third party is still required to prevent double-spending.'
        )
        msg_sig = priv.sign_message(msg)
        assert b64encode(msg_sig).decode() == (
            'H7iIlcANsbiKmCGgMus8GIw8AYjQs+uzUQX1z/bFyv8aeJlGBM'
            '1rG4B7SvE0vDDT1q2T6wSElIp6wysKJKOH7RQ=')

    def test_ecdh_shared_secret(self):
        p1 = PrivateKey.from_random()
        p2 = PrivateKey.from_random()
        ecdh_secret = p1.ecdh_shared_secret(p2.public_key)
        assert ecdh_secret == p2.ecdh_shared_secret(p1.public_key)
        assert p1.shared_secret(p2.public_key, bytes(32), None) == ecdh_secret

    def test_shared_secret(self):
        p = PrivateKey(bytes(range(32)))
        p2 = PrivateKey(bytes(range(1, 33)))
        msg = b'BitcoinSV'
        P = p.shared_secret(p2.public_key, msg)
        assert P.to_hex(
        ) == '034339a901d8526c4d733c8ea7c861f1a6324f37f6b86f838725820e0c5fc19570'
        assert P == p.shared_secret(p2.public_key, msg, sha256)
        assert P == p.shared_secret(p2.public_key, sha256(msg), hasher=None)

    def test_shared_secret_is_shared(self):
        ours = PrivateKey.from_random()
        theirs = PrivateKey.from_random()

        msg = b'BitcoinSV'
        our_P = ours.shared_secret(theirs.public_key, msg)
        their_P = theirs.shared_secret(ours.public_key, msg)

        assert our_P == their_P

    def test_decrypt_message_fails(self, AES_impl):
        priv = PrivateKey(bytes(range(32)))
        P = priv.public_key
        msg = b'BitcoinSV'
        enc_msg = P.encrypt_message(msg)

        with pytest.raises(DecryptionError) as e:
            priv.decrypt_message(b64encode(enc_msg).decode() + '%')
        assert 'invalid base64' in str(e.value)

        with pytest.raises(DecryptionError) as e:
            priv.decrypt_message(bytes(84))
        assert 'too short' in str(e.value)

        with pytest.raises(DecryptionError) as e:
            priv.decrypt_message(enc_msg, magic=b'Z')
        assert 'bad magic' in str(e.value)

        # Bad pubkey first byte
        enc_msg2 = bytearray(enc_msg)
        enc_msg2[4] ^= 2
        with pytest.raises(DecryptionError) as e:
            priv.decrypt_message(bytes(enc_msg2))
        assert 'invalid ephemeral public key' in str(e.value)

        # Bad padding.  Triggering this is work...
        ephemeral_pubkey = PublicKey.from_bytes(enc_msg[4:37])
        key = sha512(priv.ecdh_shared_secret(ephemeral_pubkey).to_bytes())
        iv, key_e, key_m = key[0:16], key[16:32], key[32:]

        encrypted_data = bytearray(enc_msg[:-32])
        encrypted_data[-1] ^= 1  # Bad padding
        enc_msg = bytes(encrypted_data) + hmac_digest(key_m, encrypted_data,
                                                      _sha256)
        with pytest.raises(DecryptionError) as e:
            priv.decrypt_message(enc_msg)
        assert 'padding' in str(e.value)

        enc_msg = bytes.fromhex(
            '4249453102e5cde5b5924d745958ba05c87d6d8930c6314481fbdefa02d8f4bafc8a2e1dee7d9c3e9d704'
            '8d72c63fc3e7b76f7f0d0b99c9b75ac78af43442e5926ea9fbaab1d4d32d71a4237e432bc2bbf7808fcd3'
        )
        with pytest.raises(DecryptionError) as e:
            priv.decrypt_message(enc_msg)
        assert 'inconsistent padding bytes' in str(e.value)

        # A valid encrypted message but for the wrong key; should give hmac mismatch
        enc_msg = P.add(one).encrypt_message(msg)
        with pytest.raises(DecryptionError) as e:
            priv.decrypt_message(enc_msg)
        assert 'bad HMAC' in str(e.value)

    def test_str(self):
        p = PrivateKey.from_random()
        assert str(p) == sha256(p.to_bytes()).hex()
コード例 #9
0
 def test_subtract_one(self):
     p1 = PrivateKey.from_random()
     p2 = p1.add(int_to_be_bytes(CURVE_ORDER - 1))
     assert p1.to_int() - 1 == p2.to_int()