Esempio n. 1
0
def test_slip132_test_vectors() -> None:
    """SLIP132 test vector

    https://github.com/satoshilabs/slips/blob/master/slip-0132.md
    """
    mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
    kpath = "m/0/0"
    test_vectors: List[Tuple[bytes, str, str, str, str]] = [
        (
            NETWORKS["mainnet"].bip32_prv,
            "m / 44h / 0h / 0h",
            "xprv9xpXFhFpqdQK3TmytPBqXtGSwS3DLjojFhTGht8gwAAii8py5X6pxeBnQ6ehJiyJ6nDjWGJfZ95WxByFXVkDxHXrqu53WCRGypk2ttuqncb",
            "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
            "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
        ),
        (
            NETWORKS["mainnet"].slip132_p2wpkh_p2sh_prv,
            "m / 49h / 0h / 0h",
            "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF",
            "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
            "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
        ),
        (
            NETWORKS["mainnet"].slip132_p2wpkh_prv,
            "m / 84h / 0h / 0h",
            "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE",
            "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
            "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
        ),
    ]
    for version, der_path, prv, pub, addr in test_vectors:
        rxprv = bip39.mxprv_from_mnemonic(mnemonic, "")
        mxprv = bip32.derive(rxprv, der_path, version)
        assert prv == mxprv
        mxpub = bip32.xpub_from_xprv(mxprv)
        assert pub == mxpub
        xpub = bip32.derive(mxpub, kpath)
        address = slip132.address_from_xpub(xpub)
        assert addr == address
        address = slip132.address_from_xkey(xpub)
        assert addr == address
        xprv = bip32.derive(mxprv, kpath)
        address = slip132.address_from_xkey(xprv)
        assert addr == address
        if version == NETWORKS["mainnet"].bip32_prv:
            address = b58.p2pkh(xpub)
            assert addr == address
            address = b58.p2pkh(xprv)
            assert addr == address
        elif version == NETWORKS["mainnet"].slip132_p2wpkh_p2sh_prv:
            address = b58.p2wpkh_p2sh(xpub)
            assert addr == address
            address = b58.p2wpkh_p2sh(xprv)
            assert addr == address
        elif version == NETWORKS["mainnet"].slip132_p2wpkh_prv:
            address = b32.p2wpkh(xpub)
            assert addr == address
            address = b32.p2wpkh(xprv)
            assert addr == address
Esempio n. 2
0
def test_eq() -> None:
    pub_key = "02 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf"
    script_pub_key = ScriptPubKey.p2pkh(pub_key)

    addr = b58.p2pkh(pub_key)
    assert ScriptPubKey.from_address(addr) == script_pub_key

    addr = b58.p2pkh(pub_key, "testnet")
    assert ScriptPubKey.from_address(addr) != script_pub_key

    assert Script(script_pub_key.script) != script_pub_key
    assert script_pub_key != Script(script_pub_key.script)
Esempio n. 3
0
def test_exceptions() -> None:

    pub_key = "02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
    payload = b"\xf5" + hash160(pub_key)
    invalid_address = b58encode(payload)
    with pytest.raises(BTClibValueError,
                       match="invalid base58 address prefix: "):
        b58.h160_from_address(invalid_address)

    with pytest.raises(BTClibValueError,
                       match="not a private or public key: "):
        b58.p2pkh(pub_key + "00")
def test_msgsign_p2pkh() -> None:
    msg = "test message".encode()
    # sigs are taken from (Electrum and) Bitcoin Core

    q = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"

    # uncompressed
    wif1u = b58.wif_from_prv_key(q, "mainnet", False)
    assert wif1u == "5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw"
    add1u = b58.p2pkh(wif1u)
    assert add1u == "1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD"
    bms_sig1u = bms.sign(msg, wif1u)
    assert bms.verify(msg, add1u, bms_sig1u)
    assert bms_sig1u.rf == 27
    exp_sig1u = "G/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4="
    assert bms_sig1u.b64encode() == exp_sig1u

    # compressed
    wif1c = b58.wif_from_prv_key(q, "mainnet", True)
    assert wif1c == "L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk"
    add1c = b58.p2pkh(wif1c)
    assert add1c == "14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY"
    bms_sig1c = bms.sign(msg, wif1c)
    assert bms.verify(msg, add1c, bms_sig1c)
    assert bms_sig1c.rf == 31
    exp_sig1c = "H/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4="
    assert bms_sig1c.b64encode() == exp_sig1c

    assert not bms.verify(msg, add1c, bms_sig1u)
    assert not bms.verify(msg, add1u, bms_sig1c)

    bms_sig = bms.Sig(bms_sig1c.rf + 1, bms_sig1c.dsa_sig)
    assert not bms.verify(msg, add1c, bms_sig)

    # malleate s
    s = ec.n - bms_sig1c.dsa_sig.s
    dsa_sig = dsa.Sig(bms_sig1c.dsa_sig.r, s, bms_sig1c.dsa_sig.ec)
    # without updating rf verification will fail, even with lower_s=False
    bms_sig = bms.Sig(bms_sig1c.rf, dsa_sig)
    assert not bms.verify(msg, add1c, bms_sig, lower_s=False)

    # update rf to satisfy above malleation
    i = 1 if bms_sig1c.rf % 2 else -1
    bms_sig = bms.Sig(bms_sig1c.rf + i, dsa_sig)
    assert bms.verify(msg, add1c, bms_sig, lower_s=False)

    # anyway, with lower_s=True malleation does fail verification
    err_msg = "not a low s"
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, add1c, bms_sig, lower_s=True)
Esempio n. 5
0
def test_p2pkh_from_pub_key() -> None:
    # https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
    pub_key = "02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
    address = "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs"
    assert address == b58.p2pkh(pub_key)
    assert address == b58.p2pkh(pub_key, compressed=True)
    _, h160, _ = b58.h160_from_address(address)
    assert h160 == hash160(pub_key)

    # trailing/leading spaces in address string
    assert address == b58.p2pkh(" " + pub_key)
    assert h160 == hash160(" " + pub_key)
    assert address == b58.p2pkh(pub_key + " ")
    assert h160 == hash160(pub_key + " ")

    uncompr_pub_key = bytes_from_point(point_from_octets(pub_key),
                                       compressed=False)
    uncompr_address = "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"
    assert uncompr_address == b58.p2pkh(uncompr_pub_key, compressed=False)
    assert uncompr_address == b58.p2pkh(uncompr_pub_key)
    _, uncompr_h160, _ = b58.h160_from_address(uncompr_address)
    assert uncompr_h160 == hash160(uncompr_pub_key)

    err_msg = "not a private or uncompressed public key: "
    with pytest.raises(BTClibValueError, match=err_msg):
        assert uncompr_address == b58.p2pkh(pub_key, compressed=False)

    err_msg = "not a private or compressed public key: "
    with pytest.raises(BTClibValueError, match=err_msg):
        assert address == b58.p2pkh(uncompr_pub_key, compressed=True)
Esempio n. 6
0
def test_p2ms_3() -> None:
    # tx_id 33ac2af1a6f894276713b59ed09ce1a20fed5b36d169f20a3fe831dc45564d57
    # output n 0
    keys: List[Command] = [
        "036D568125A969DC78B963B494FA7ED5F20EE9C2F2FC2C57F86C5DF63089F2ED3A",
        "03FE4E6231D614D159741DF8371FA3B31AB93B3D28A7495CDAA0CD63A2097015C7",
    ]
    cmds: List[Command] = ["OP_1", *keys, "OP_2", "OP_CHECKMULTISIG"]
    script_pub_key = ScriptPubKey(serialize(cmds))
    assert script_pub_key == ScriptPubKey.p2ms(1, keys)

    pub_keys = script_pub_key.addresses
    exp_pub_keys = [
        "1Ng4YU2e2H3E86syX2qrsmD9opBHZ42vCF",
        "14XufxyGiY6ZBJsFYHJm6awdzpJdtsP1i3",
    ]
    for pub_key, key, exp_pub_key in zip(pub_keys, keys, exp_pub_keys):
        assert pub_key == b58.p2pkh(key)
        assert pub_key == exp_pub_key

    # tx 56214420a7c4dcc4832944298d169a75e93acf9721f00656b2ee0e4d194f9970
    # input n 1
    cmds_sig: List[Command] = [
        "OP_0",
        "3045022100dba1e9b1c8477fd364edcc1f81845928202daf465a1e2d92904c13c88761cbd002200add6af863dfdb7efb95f334baec041e90811ae9d81624f9f87f33a56761f29401",
    ]
    script_sig = Script(serialize(cmds_sig))
    script = script_sig + script_pub_key
    # parse(serialize(*)) is to enforce same string case convention
    assert script.asm == parse(serialize(cmds_sig + cmds))
Esempio n. 7
0
def test_derive_from_account() -> None:

    seed = "bfc4cbaad0ff131aa97fa30a48d09ae7df914bcc083af1e07793cd0a7c61a03f65d622848209ad3366a419f4718a80ec9037df107d8d12c19b83202de00a40ad"
    rmxprv = rootxprv_from_seed(seed)

    der_path = "m / 44 h / 0 h"
    mxpub = xpub_from_xprv(derive(rmxprv, der_path))

    test_vectors = [
        [0, 0],
        [0, 1],
        [0, 2],
        [1, 0],
        [1, 1],
        [1, 2],
    ]

    for branch, index in test_vectors:
        full_path = der_path + f"/{branch}/{index}"
        addr = p2pkh(derive(rmxprv, full_path))
        assert addr == p2pkh(derive_from_account(mxpub, branch, index))

    err_msg = "invalid private derivation at branch level"
    with pytest.raises(BTClibValueError, match=err_msg):
        derive_from_account(mxpub, 0x80000000, 0, True)

    err_msg = "too high branch: "
    with pytest.raises(BTClibValueError, match=err_msg):
        derive_from_account(mxpub, 0xFFFF + 1, 0)

    err_msg = "invalid branch: "
    with pytest.raises(BTClibValueError, match=err_msg):
        derive_from_account(mxpub, 2, 0)

    err_msg = "invalid private derivation at address index level"
    with pytest.raises(BTClibValueError, match=err_msg):
        derive_from_account(mxpub, 0, 0x80000000)

    err_msg = "too high address index: "
    with pytest.raises(BTClibValueError, match=err_msg):
        derive_from_account(mxpub, 0, 0xFFFF + 1)

    der_path = "m / 44 h / 0"
    mxpub = xpub_from_xprv(derive(rmxprv, der_path))
    err_msg = "unhardened account/master key"
    with pytest.raises(BTClibValueError, match=err_msg):
        derive_from_account(mxpub, 0, 0)
Esempio n. 8
0
def test_derive() -> None:

    test_vectors = {
        "xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS": [
            ["m / 0 h / 0 h / 463 h", "1DyfBWxhVLmrJ7keyiHeMbt7N3UdeGU4G5"],
            ["M / 0H / 0h // 267' / ", "11x2mn59Qy43DjisZWQGRResjyQmgthki"],
        ],
        "tprv8ZgxMBicQKsPe3g3HwF9xxTLiyc5tNyEtjhBBAk29YA3MTQUqULrmg7aj9qTKNfieuu2HryQ6tGVHse9x7ANFGs3f4HgypMc5nSSoxwf7TK": [
            ["m / 0 h / 0 h / 51 h", "mfXYCCsvWPgeCv8ZYGqcubpNLYy5nYHbbj"],
            ["m / 0 h / 1 h / 150 h", "mfaUnRFxVvf55uD1P3zWXpprN1EJcKcGrb"],
        ],
    }

    for rootxprv, value in test_vectors.items():
        for der_path, address in value:
            assert address == p2pkh(derive(rootxprv, der_path))

            indexes = _indexes_from_bip32_path_str(der_path)
            assert address == p2pkh(derive(rootxprv, indexes))

        assert derive(rootxprv, "m") == rootxprv
Esempio n. 9
0
def test_exceptions() -> None:

    with pytest.raises(BTClibValueError, match="not a private or public key: "):
        # invalid checksum
        xprv = "xppp9s21ZrQH143K2oxHiQ5f7D7WYgXD9h6HAXDBuMoozDGGiYHWsq7TLBj2yvGuHTLSPCaFmUyN1v3fJRiY2A4YuNSrqQMPVLZKt76goL6LP7L"
        p2pkh(xprv)

    with pytest.raises(BTClibValueError, match="not a private key: "):
        xpub = "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
        xpub_from_xprv(xpub)

    seed = "5b56c417303faa3fcba7e57400e120a0"
    with pytest.raises(BTClibValueError, match="unknown extended key version: "):
        version = b"\x04\x88\xAD\xE5"
        rootxprv_from_seed(seed, version)

    with pytest.raises(BTClibValueError, match="too many bits for seed: "):
        rootxprv_from_seed(seed * 5)

    with pytest.raises(BTClibValueError, match="too few bits for seed: "):
        rootxprv_from_seed(seed[:-2])
Esempio n. 10
0
def test_address_from_wif() -> None:

    q = 0x19E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725

    test_cases: List[Tuple[bool, str, str, str]] = [
        (
            False,
            "mainnet",
            "5J1geo9kcAUSM6GJJmhYRX1eZEjvos9nFyWwPstVziTVueRJYvW",
            "1LPM8SZ4RQDMZymUmVSiSSvrDfj1UZY9ig",
        ),
        (
            True,
            "mainnet",
            "Kx621phdUCp6sgEXPSHwhDTrmHeUVrMkm6T95ycJyjyxbDXkr162",
            "1HJC7kFvXHepkSzdc8RX6khQKkAyntdfkB",
        ),
        (
            False,
            "testnet",
            "91nKEXyJCPYaK9maw7bTJ7ZcCu6dy2gybvNtUWF1LTCYggzhZgy",
            "mzuJRVe3ERecM6F6V4R6GN9B5fKiPC9HxF",
        ),
        (
            True,
            "testnet",
            "cNT1UjhUuGWN37hnmr754XxvPWwtAJTSq8bcCQ4pUrdxqxbA1iU1",
            "mwp9QoLuLK65XZUFKhPtvfujBjmgkZnmPx",
        ),
    ]
    for compressed, network, wif, address in test_cases:
        assert wif == b58.wif_from_prv_key(q, network, compressed)
        assert prv_keyinfo_from_prv_key(wif) == (q, network, compressed)
        assert address == b58.p2pkh(wif)
        script_type, payload, net = b58.h160_from_address(address)
        assert net == network
        assert script_type == "p2pkh"

        if compressed:
            b32_address = b32.p2wpkh(wif)
            assert (0, payload, net) == b32.witness_from_address(b32_address)

            b58_address = b58.p2wpkh_p2sh(wif)
            script_bin = hash160(b"\x00\x14" + payload)
            assert ("p2sh", script_bin,
                    net) == b58.h160_from_address(b58_address)

        else:
            err_msg = "not a private or compressed public key: "
            with pytest.raises(BTClibValueError, match=err_msg):
                b32.p2wpkh(wif)  # type: ignore
            with pytest.raises(BTClibValueError, match=err_msg):
                b58.p2wpkh_p2sh(wif)  # type: ignore
Esempio n. 11
0
def test_p2pkh_from_wif() -> None:
    seed = b"\x00" * 32  # better be a documented test case
    rxprv = bip32.rootxprv_from_seed(seed)
    path = "m/0h/0h/12"
    xprv = bip32.derive(rxprv, path)
    wif = b58.wif_from_prv_key(xprv)
    assert wif == "L2L1dqRmkmVtwStNf5wg8nnGaRn3buoQr721XShM4VwDbTcn9bpm"
    pub_key, _ = pub_keyinfo_from_prv_key(wif)
    address = b58.p2pkh(pub_key)
    xpub = bip32.xpub_from_xprv(xprv)
    assert address == slip132.address_from_xpub(xpub)

    err_msg = "not a private key: "
    with pytest.raises(BTClibValueError, match=err_msg):
        b58.wif_from_prv_key(xpub)
Esempio n. 12
0
def test_p2pkh() -> None:

    # self-consistency
    pub_key = (
        "04 "
        "cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf"
        "f7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4")
    payload = hash160(pub_key)
    script_pub_key = serialize(
        ["OP_DUP", "OP_HASH160", payload, "OP_EQUALVERIFY", "OP_CHECKSIG"])
    assert_p2pkh(script_pub_key)
    assert script_pub_key == ScriptPubKey.p2pkh(pub_key).script
    assert ("p2pkh", payload) == type_and_payload(script_pub_key)

    # base58 address
    network = "mainnet"
    addr = b58.p2pkh(pub_key, network)
    assert addr == address(script_pub_key, network)
    assert addr == b58.address_from_h160("p2pkh", payload, network)

    # back from the address to the script_pub_key
    assert script_pub_key == ScriptPubKey.from_address(addr).script
    assert network == ScriptPubKey.from_address(addr).network

    # documented test case: https://learnmeabitcoin.com/guide/p2pkh
    payload = bytes.fromhex("12ab8dc588ca9d5787dde7eb29569da63c3a238c")
    script_pub_key = bytes.fromhex("76a914") + payload + bytes.fromhex("88ac")
    assert_p2pkh(script_pub_key)
    addr = "12higDjoCCNXSA95xZMWUdPvXNmkAduhWv"
    assert addr == address(script_pub_key, network)
    assert script_pub_key == ScriptPubKey.from_address(addr).script
    assert network == ScriptPubKey.from_address(addr).network

    err_msg = "missing final OP_EQUALVERIFY, OP_CHECKSIG"
    with pytest.raises(BTClibValueError, match=err_msg):
        assert_p2pkh(script_pub_key[:-2] + b"\x40\x40")

    err_msg = "missing leading OP_DUP, OP_HASH160"
    with pytest.raises(BTClibValueError, match=err_msg):
        assert_p2pkh(b"\x40\x40" + script_pub_key[2:])

    err_msg = "invalid pub_key hash length marker: "
    with pytest.raises(BTClibValueError, match=err_msg):
        assert_p2pkh(script_pub_key[:2] + b"\x40" + script_pub_key[3:])
Esempio n. 13
0
def gen_keys(
    prv_key: Optional[PrvKey] = None,
    network: Optional[str] = None,
    compressed: Optional[bool] = None,
) -> Tuple[str, str]:
    """Return a private/public key pair.

    The private key is a WIF, the public key is a base58 P2PKH address.
    """

    if prv_key is None:
        if network is None:
            network = "mainnet"
        ec = NETWORKS[network].curve
        # q in the range [1, ec.n-1]
        prv_key = 1 + secrets.randbelow(ec.n - 1)

    wif = wif_from_prv_key(prv_key, network, compressed)
    return wif, p2pkh(wif)
Esempio n. 14
0
def sign(msg: Octets, prv_key: PrvKey, addr: Optional[String] = None) -> Sig:
    "Generate address-based compact signature for the provided message."

    # first sign the message
    magic_msg = magic_message(msg)
    q, network, compressed = prv_keyinfo_from_prv_key(prv_key)
    dsa_sig = dsa.sign(magic_msg, q)

    # now calculate the key_id
    # TODO do the match in Jacobian coordinates avoiding mod_inv
    pub_keys = dsa.recover_pub_keys(magic_msg, dsa_sig)
    Q = mult(q)
    # key_id is in [0, 3]
    # first two bits in rf are reserved for it
    key_id = pub_keys.index(Q)
    pub_key = bytes_from_point(Q, compressed=compressed)

    if isinstance(addr, str):
        addr = addr.strip()
    elif isinstance(addr, bytes):
        addr = addr.decode("ascii")

    # finally, calculate the recovery flag
    if addr is None or addr == p2pkh(pub_key, network, compressed):
        rf = key_id + 27
        # third bit in rf is reserved for the 'compressed' boolean
        rf += 4 if compressed else 0
    # BIP137
    elif addr == p2wpkh_p2sh(pub_key, network):
        rf = key_id + 35
    elif addr == p2wpkh(pub_key, network):
        rf = key_id + 39
    else:
        raise BTClibValueError("mismatch between private key and address")

    return Sig(rf, dsa_sig)
Esempio n. 15
0
def test_segwit() -> None:

    msg = "test".encode()
    wif = "L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK"
    b58_p2pkh = b58.p2pkh(wif)
    b32_p2wpkh = b32.p2wpkh(wif)
    b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif)

    # p2pkh base58 address (Core, Electrum, BIP137)
    exp_sig = "IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU="
    assert bms.verify(msg, b58_p2pkh, exp_sig)
    bms_sig = bms.sign(msg, wif)  # no address: p2pkh assumed
    assert bms.verify(msg, b58_p2pkh, bms_sig)
    assert bms_sig.b64encode() == exp_sig

    # p2wpkh-p2sh base58 address (Electrum)
    assert bms.verify(msg, b58_p2wpkh_p2sh, bms_sig)

    # p2wpkh bech32 address (Electrum)
    assert bms.verify(msg, b32_p2wpkh, bms_sig)

    # p2wpkh-p2sh base58 address (BIP137)
    # different first letter in bms_sig because of different rf
    exp_sig = "JBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU="
    assert bms.verify(msg, b58_p2wpkh_p2sh, exp_sig)
    bms_sig = bms.sign(msg, wif, b58_p2wpkh_p2sh)
    assert bms.verify(msg, b58_p2wpkh_p2sh, bms_sig)
    assert bms_sig.b64encode() == exp_sig

    # p2wpkh bech32 address (BIP137)
    # different first letter in bms_sig because of different rf
    exp_sig = "KBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU="
    assert bms.verify(msg, b32_p2wpkh, exp_sig)
    bms_sig = bms.sign(msg, wif, b32_p2wpkh)
    assert bms.verify(msg, b32_p2wpkh, bms_sig)
    assert bms_sig.b64encode() == exp_sig
def addresses(script_pub_key: Octets, network: str = "mainnet") -> List[str]:
    "Return the p2pkh addresses of the pub_keys used in a p2ms script_pub_key."

    script_pub_key = bytes_from_octets(script_pub_key)
    # p2ms [m, pub_keys, n, OP_CHECKMULTISIG]
    length = len(script_pub_key)
    if length < 37:
        raise BTClibValueError(f"invalid p2ms length {length}")
    if script_pub_key[-1] != 0xAE:
        raise BTClibValueError("missing final OP_CHECKMULTISIG")
    m = script_pub_key[0] - 80
    if not 0 < m < 17:
        raise BTClibValueError(f"invalid m in m-of-n: {m}")
    n = script_pub_key[-2] - 80
    if not m <= n < 17:
        raise BTClibValueError(f"invalid m-of-n: {m}-of-{n}")

    stream = bytesio_from_binarydata(script_pub_key[1:-2])
    pub_keys = [var_bytes.parse(stream) for _ in range(n)]

    if stream.read(1):
        raise BTClibValueError("invalid p2ms script_pub_key size")

    return [b58.p2pkh(pub_key, network) for pub_key in pub_keys]
Esempio n. 17
0
from btclib.b58 import p2pkh, p2wpkh_p2sh
from btclib.b58 import wif_from_prv_key
from btclib.b32 import p2wpkh
from btclib.ecc.bms import sign, verify
from btclib.to_prv_key import prv_keyinfo_from_prv_key
from btclib.to_pub_key import pub_keyinfo_from_prv_key

msg = "Paolo is afraid of ephemeral random numbers".encode()
print("\n0. Message:", msg.decode())

wif = b"Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C"
print("1. Compressed WIF:", wif.decode())
pubkey, network = pub_keyinfo_from_prv_key(wif)

print("2. Addresses")
address1 = p2pkh(pubkey)
print("      p2pkh:", address1)
address2 = p2wpkh_p2sh(pubkey)
print("p2wpkh_p2sh:", address2)
address3 = p2wpkh(pubkey)
print("     p2wpkh:", address3)


print(
    "\n3. Sign message with no address (i.e., with default compressed p2pkh address):"
)
sig1 = sign(msg, wif)
print(f"rf1: {sig1.rf}")
print(f" r1: {hex(sig1.dsa_sig.r).upper()}")
print(f" s1: {hex(sig1.dsa_sig.r).upper()}")
Esempio n. 18
0
def test_exceptions() -> None:

    msg = "test".encode()
    wif = "KwELaABegYxcKApCb3kJR9ymecfZZskL9BzVUkQhsqFiUKftb4tu"
    address = b58.p2pkh(wif)
    exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc="
    bms.assert_as_valid(msg, address, exp_sig)
    bms_sig = bms.Sig.b64decode(exp_sig)

    err_msg = "not a p2wpkh address: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b32.p2wsh(32 * b"\x00"), exp_sig)

    err_msg = "invalid recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.Sig(26, bms_sig.dsa_sig)

    exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLoVc="
    err_msg = "invalid decoded length: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, address, exp_sig)
    assert not bms.verify(msg, address, exp_sig)

    exp_sig = "GpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU="
    err_msg = "invalid recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, address, exp_sig)
    assert not bms.verify(msg, address, exp_sig)
    exp_sig = "QpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU="
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, address, exp_sig)
    assert not bms.verify(msg, address, exp_sig)

    # compressed wif, uncompressed address
    wif = "Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ"
    address = "19f7adDYqhHSJm2v7igFWZAqxXHj1vUa3T"
    err_msg = "mismatch between private key and address"
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.sign(msg, wif, address)

    # uncompressed wif, compressed address
    wif = "5JDopdKaxz5bXVYXcAnfno6oeSL8dpipxtU1AhfKe3Z58X48srn"
    address = "1DAag8qiPLHh6hMFVu9qJQm9ro1HtwuyK5"
    err_msg = "not a private or compressed public key for mainnet: "
    # FIXME puzzling error message
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.sign(msg, wif, address)

    msg = "test".encode()
    wif = "L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK"
    b58_p2pkh = b58.p2pkh(wif)
    b32_p2wpkh = b32.p2wpkh(wif)
    b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif)

    wif = "Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ"
    err_msg = "mismatch between private key and address"
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.sign(msg, wif, b58_p2pkh)
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.sign(msg, wif, b32_p2wpkh)
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.sign(msg, wif, b58_p2wpkh_p2sh)

    # Invalid recovery flag (39) for base58 p2pkh address
    exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc="
    bms_sig = bms.Sig.b64decode(exp_sig)
    bms_sig = bms.Sig(39, bms_sig.dsa_sig, check_validity=False)
    sig_encoded = bms_sig.b64encode(check_validity=False)
    err_msg = "invalid p2pkh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b58_p2pkh, sig_encoded)

    # Invalid recovery flag (35) for bech32 p2wpkh address
    exp_sig = "IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU="
    bms_sig = bms.Sig.b64decode(exp_sig)
    bms_sig = bms.Sig(35, bms_sig.dsa_sig, check_validity=False)
    err_msg = "invalid p2wpkh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b32_p2wpkh, bms_sig)
Esempio n. 19
0
def test_ledger() -> None:
    """Hybrid ECDSA Bitcoin message signature generated by Ledger"""

    mnemonic = (
        "barely sun snack this snack relief pipe attack disease boss enlist lawsuit"
    )

    # non-standard leading 31 in DER serialization
    derivation_path = "m/1"
    msg = b"\xfb\xa3\x1f\x8cd\x85\xe29#K\xb3{\xfd\xa7<?\x95oL\xee\x19\xb2'oh\xa7]\xd9A\xfeU\xd8"
    dersig_hex_str = "3144022012ec0c174936c2a46dc657252340b2e6e6dd8c31dd059b6f9f33a90c21af2fba022030e6305b3ccf88009d419bf7651afcfcc0a30898b93ae9de9aa6ac03cf8ec56b"

    # pub_key derivation
    rprv = bip39.mxprv_from_mnemonic(mnemonic)
    xprv = bip32.derive(rprv, derivation_path)

    # the actual message being signed
    magic_msg = magic_message(msg)

    # save key_id and patch dersig
    dersig = bytes.fromhex(dersig_hex_str)
    key_id = dersig[0]
    dsa_sig = dsa.Sig.parse(b"\x30" + dersig[1:])

    # ECDSA signature verification of the patched dersig
    dsa.assert_as_valid(magic_msg, xprv, dsa_sig)
    assert dsa.verify(magic_msg, xprv, dsa_sig)

    # compressed address
    addr = b58.p2pkh(xprv)

    # equivalent Bitcoin Message Signature
    rec_flag = 27 + 4 + (key_id & 0x01)
    bms_sig = bms.Sig(rec_flag, dsa_sig)

    # Bitcoin Message Signature verification
    bms.assert_as_valid(msg, addr, bms_sig)
    assert bms.verify(msg, addr, bms_sig)
    assert not bms.verify(magic_msg, addr, bms_sig)

    bms.sign(msg, xprv)

    # standard leading 30 in DER serialization
    derivation_path = "m/0/0"
    msg_str = "hello world".encode()
    dersig_hex_str = "3045022100967dac3262b4686e89638c8219c5761017f05cd87a855edf034f4a3ec6b59d3d0220108a4ef9682b71a45979d8c75c393382d9ccb8eb561d73b8c5fc0b87a47e7d27"

    # pub_key derivation
    rprv = bip39.mxprv_from_mnemonic(mnemonic)
    xprv = bip32.derive(rprv, derivation_path)

    # the actual message being signed
    magic_msg = magic_message(msg_str)

    # save key_id and patch dersig
    dersig = bytes.fromhex(dersig_hex_str)
    key_id = dersig[0]
    dsa_sig = dsa.Sig.parse(b"\x30" + dersig[1:])

    # ECDSA signature verification of the patched dersig
    dsa.assert_as_valid(magic_msg, xprv, dsa_sig, lower_s=True)
    assert dsa.verify(magic_msg, xprv, dsa_sig)

    # compressed address
    addr = b58.p2pkh(xprv)

    # equivalent Bitcoin Message Signature
    rec_flag = 27 + 4 + (key_id & 0x01)
    bms_sig = bms.Sig(rec_flag, dsa_sig)

    # Bitcoin Message Signature verification
    bms.assert_as_valid(msg_str, addr, bms_sig)
    assert bms.verify(msg_str, addr, bms_sig)
    assert not bms.verify(magic_msg, addr, bms_sig)
Esempio n. 20
0
def test_one_prv_key_multiple_addresses() -> None:

    msg = "Paolo is afraid of ephemeral random numbers".encode()

    # Compressed WIF
    wif = "Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C"
    b58_p2pkh_compressed = b58.p2pkh(wif)
    b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif)
    b32_p2wpkh = b32.p2wpkh(wif)

    # sign with no address
    sig1 = bms.sign(msg, wif)
    # True for Bitcoin Core
    bms.assert_as_valid(msg, b58_p2pkh_compressed, sig1)
    assert bms.verify(msg, b58_p2pkh_compressed, sig1)
    # True for Electrum p2wpkh_p2sh
    bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig1)
    assert bms.verify(msg, b58_p2wpkh_p2sh, sig1)
    # True for Electrum p2wpkh
    bms.assert_as_valid(msg, b32_p2wpkh, sig1)
    assert bms.verify(msg, b32_p2wpkh, sig1)

    # sign with p2pkh address
    sig1 = bms.sign(msg, wif, b58_p2pkh_compressed)
    # True for Bitcoin Core
    bms.assert_as_valid(msg, b58_p2pkh_compressed, sig1)
    assert bms.verify(msg, b58_p2pkh_compressed, sig1)
    # True for Electrum p2wpkh_p2sh
    bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig1)
    assert bms.verify(msg, b58_p2wpkh_p2sh, sig1)
    # True for Electrum p2wpkh
    bms.assert_as_valid(msg, b32_p2wpkh, sig1)
    assert bms.verify(msg, b32_p2wpkh, sig1)
    assert sig1 == bms.sign(msg, wif, b58_p2pkh_compressed.encode("ascii"))

    # sign with p2wpkh_p2sh address (BIP137)
    sig2 = bms.sign(msg, wif, b58_p2wpkh_p2sh)
    # False for Bitcoin Core
    err_msg = "invalid p2pkh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b58_p2pkh_compressed, sig2)
    assert not bms.verify(msg, b58_p2pkh_compressed, sig2)
    # True for BIP137 p2wpkh_p2sh
    bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig2)
    assert bms.verify(msg, b58_p2wpkh_p2sh, sig2)
    # False for BIP137 p2wpkh
    err_msg = "invalid p2wpkh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b32_p2wpkh, sig2)
    assert not bms.verify(msg, b32_p2wpkh, sig2)
    assert sig2 == bms.sign(msg, wif, b58_p2wpkh_p2sh.encode("ascii"))

    # sign with p2wpkh address (BIP137)
    sig3 = bms.sign(msg, wif, b32_p2wpkh)
    # False for Bitcoin Core
    err_msg = "invalid p2pkh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b58_p2pkh_compressed, sig3)
    assert not bms.verify(msg, b58_p2pkh_compressed, sig3)
    # False for BIP137 p2wpkh_p2sh
    err_msg = "invalid p2wpkh-p2sh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig3)
    assert not bms.verify(msg, b58_p2wpkh_p2sh, sig3)
    # True for BIP137 p2wpkh
    bms.assert_as_valid(msg, b32_p2wpkh, sig3)
    assert bms.verify(msg, b32_p2wpkh, sig3)
    assert sig3 == bms.sign(msg, wif, b32_p2wpkh.encode("ascii"))

    # uncompressed WIF / p2pkh address
    q, network, _ = prv_keyinfo_from_prv_key(wif)
    wif2 = b58.wif_from_prv_key(q, network, False)
    b58_p2pkh_uncompressed = b58.p2pkh(wif2)

    # sign with uncompressed p2pkh
    sig4 = bms.sign(msg, wif2, b58_p2pkh_uncompressed)
    # False for Bitcoin Core compressed p2pkh
    with pytest.raises(BTClibValueError, match="invalid p2pkh address: "):
        bms.assert_as_valid(msg, b58_p2pkh_compressed, sig4)
    assert not bms.verify(msg, b58_p2pkh_compressed, sig4)
    # False for BIP137 p2wpkh_p2sh
    err_msg = "invalid p2wpkh-p2sh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig4)
    assert not bms.verify(msg, b58_p2wpkh_p2sh, sig4)
    # False for BIP137 p2wpkh
    err_msg = "invalid p2wpkh address recovery flag: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.assert_as_valid(msg, b32_p2wpkh, sig4)
    assert not bms.verify(msg, b32_p2wpkh, sig4)
    # True for Bitcoin Core uncompressed p2pkh
    bms.assert_as_valid(msg, b58_p2pkh_uncompressed, sig4)
    assert bms.verify(msg, b58_p2pkh_uncompressed, sig4)
    assert sig4 == bms.sign(msg, wif2, b58_p2pkh_uncompressed.encode("ascii"))

    # unrelated different wif
    wif3 = "KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617"
    b58_p2pkh_compressed = b58.p2pkh(wif3)
    b58_p2wpkh_p2sh = b58.p2wpkh_p2sh(wif3)
    b32_p2wpkh = b32.p2wpkh(wif3)

    # False for Bitcoin Core compressed p2pkh
    with pytest.raises(BTClibValueError, match="invalid p2pkh address: "):
        bms.assert_as_valid(msg, b58_p2pkh_compressed, sig1)
    assert not bms.verify(msg, b58_p2pkh_compressed, sig1)
    # False for BIP137 p2wpkh_p2sh
    with pytest.raises(BTClibValueError, match="invalid p2wpkh-p2sh address: "):
        bms.assert_as_valid(msg, b58_p2wpkh_p2sh, sig1)
    assert not bms.verify(msg, b58_p2wpkh_p2sh, sig1)
    # False for BIP137 p2wpkh
    with pytest.raises(BTClibValueError, match="invalid p2wpkh address: "):
        bms.assert_as_valid(msg, b32_p2wpkh, sig1)
    assert not bms.verify(msg, b32_p2wpkh, sig1)

    # FIXME: puzzling error message
    err_msg = "not a private or compressed public key for mainnet: "
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.sign(msg, wif2, b58_p2pkh_compressed)

    err_msg = "mismatch between private key and address"
    with pytest.raises(BTClibValueError, match=err_msg):
        bms.sign(msg, wif, b58_p2pkh_uncompressed)