9

Let's say some software gave me the following combination:

public key:

13YcHBzsBX8SxHoBftb69cXJkdXLfAVQos

private key:

KwfNqMip1ZdgG2o6wYQUBXv8BqkMQ8VWWeScVU5TLPZp31M5EHeq

And let's say that for whatever reason, I want to test the validity:

  1. that the public key is a valid bitcoin address that can receive payments, and

  2. that the private key is valid for this particular public key

What are my options to evaluate the validity of 1 and 2?

4
  • Key encoding will be useful to give you a precise example. Are the values you are asking actually correct keys, or just made up strings?
    – sr_gi
    Commented Oct 19, 2017 at 18:14
  • Alright, it's WIF.
    – sr_gi
    Commented Oct 19, 2017 at 18:21
  • 1
    @sr-gi Not made up, some software gave them to me. (For others: WIF = wallet import format.) Commented Oct 20, 2017 at 19:11
  • Yep, I got it. Actually I've proved that they match in my answer.
    – sr_gi
    Commented Oct 20, 2017 at 19:14

4 Answers 4

4

First, what you defining as public key and private key are actually a bitcoin address and a private key encoded in Wallet Import Format (WIF).

In order to check that the WIF and the bitcoin addresses are from the same key pair, we will need to decode the private key from its WIF format (checking that the encoding is ok), derive the public key from the private key, and generate the bitcoin address using the public key. If the generated bitcoin address matches with the provided one, then the provided one and the WIF are created from the same key pair.

In order to decode the WIF we will follow the steps from the bitcoin wiki.

Lets see how we can do this in python:

from binascii import hexlify, unhexlify
from ecdsa import SigningKey, SECP256k1
from hashlib import sha256
from bitcoin_tools.wallet import generate_btc_addr, WIF, TESTNET_WIF


def wif_to_sk(wif, network='main'):
    if network not in ['main', 'test']:
        # Add more networks if needed.
        raise Exception('Bad network')
    else:
        if network is 'main':
            version = WIF
        else:
            version = TESTNET_WIF

    decoded_wif = b58decode(wif)

    c = decoded_wif[-4:]
    v = decoded_wif[:1]

    # The byte defines the version, assert that is correct.
    assert v == chr(version)

    # The four last bytes of the WIF are the four first bytes of the checksum, check that it holds
    checksum = sha256(sha256(decoded_wif[:-4]).digest()).digest()
    assert checksum[:4] == c

    # If the private key in the WIF corresponds to a compressed public key, you must also drop the last byte, that will
    # be 01. We can check that by checking the length of the current key. 32 bytes wil mean uncompressed, while 33 and
    # a leading 01 means compressed.
    sk = hexlify(decoded_wif[1:-4])

    compressed = False

    # Notice that since we have hexlified the sk, the sizes are doubled.
    if len(sk) is 66 and sk[-2:] == '01':
        sk = unhexlify(sk[:-2])
        compressed = True
    else:
        sk = unhexlify(sk)

    return sk, compressed

# Your provided data
wif = 'KwfNqMip1ZdgG2o6wYQUBXv8BqkMQ8VWWeScVU5TLPZp31M5EHeq'
btc_addr = '13YcHBzsBX8SxHoBftb69cXJkdXLfAVQos'
network = 'main'

sk, compressed = wif_to_sk(wif, network=network)

# Derive the public key from the private key
pk = SigningKey.from_string(sk, curve=SECP256k1).get_verifying_key()

# Assert that the computed bitcoin address and the provided one matches.
assert generate_btc_addr(pk, v=network, compressed=compressed) == btc_addr

To decode the WIF format there is a couple of things that you may know. First, the version of the network (normally either mainnet or testnet) and then, if the private key corresponds to a compressed or uncompressed public key. The version of the network will determine the first byte of the WIF format, while whether the related public key is compressed or uncompressed will determine the last byte before the checksum.

Disclaimer: The provided code uses a function generate_btc_addr, from a python library I've developed, that computes a bitcoin address from a given public key. Such function call a bunch of other simple functions to derive the bitcoin address, but including all on the answer will make it even longer that what it is. You can either get the library from GitHub, or get the functions from the specific file.

0
3

1) A Bitcoin address is between 25 and 34 characters long

2) the address always starts with a 1 (in this case)

3) an address can contain all alphanumeric characters, with the exceptions of 0, O, I, and l.

(ref: https://thomas.vanhoutte.be/tools/validate-bitcoin-address.php)

Now here comes the hard part: The last four bytes of the address are the first four bytes of Sha256(Sha256(2,3,4...21th bytes of the address))

More info: https://bitcoin.stackexchange.com/a/50879/38618

2

public key:

13YcHBzsBX8SxHoBftb69cXJkdXLfAVQos

That is not a public key. That is an address, which is an encoding of the hash of a public key.

that the public key is a valid bitcoin address that can receive payments, and

You can check that the Bitcoin address is valid by decoding the Base58 Check Encoding of the address. When you decode the base58 string, you should have 25 hex bytes. The last 4 bytes are a checksum. They are the first 4 bytes of the double sha256 hash of the first 21 bytes of what you decoded. So you can hash those 21 bytes and make sure that they match the last 4 bytes. If they do, then the address is valid and coins can be sent to it.

that the private key is valid for this particular public key

Decode the private key to its bytes in the same way as you did for the address, it uses the same encoding scheme. The checksum will be the hash of everything that is not the checksum.

The actual private key will be 32 bytes long, starting from the second byte of the decode Base58 string. Take those 32 bytes and derive the ECDSA public key from them.

If your private key string (the original base58 string you started with) began with a K or L, the public key will need to be compressed, so compress the public key as specified in Section 2.3.3 of the SEC 1: Elliptic Curve Cryptography standard. Take the public key and hash it first with SHA256 then that result with RIPEMD 160. Compare your resulting bytes to your decoded address from earlier. It should match the 20 bytes starting from the second byte of the decoded address.

1

alternativly, if you don't want to go through the details by yourself, there are web services:

https://www.bitaddress.org/bitaddress.org-v3.2.0-SHA256-ad4fd171c647772aa76d0ce828731b01ca586596275d43a94008766b758e8736.html

You may want to play with the menu "wallet details". It shows the derived keys as well.

(for sure you wouldn't use a web provided priv key for your production)

2
  • Unfortunately, that link is broken now :( Returns a 404 status code Commented Dec 10, 2023 at 17:09
  • 1
    yup, Internet is a living thing, isn't it? maybe try here and find the way through: bitaddress.org Commented Dec 14, 2023 at 17:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.