비트코인 주소 생성

비트코인 주소

비트코인의 주소는 1, 3, 혹은 bc1 으로 시작하는데, 비트코인 Legacy 주소는 1 또는 3으로 시작하고, SegWit 주소는 bc1 으로 시작한다. 만약 1로 시작하면 (예: 1PJ2er3A8sh2M7S9tGuhxRmRizTM2fCHqU), 그것은 P2PKH (Pay to Pubkey Hash) 주소 포맷이다. 만약 3으로 시작하면, 이는 P2SH (Pay to script hash) 주소 포맷으로 불리운다.

P2PKH 주소

비트코인 주소는 Public Key를 해싱하여 몇가지 변환을 통해 만들어 진다. 주소는 압축된 Public Key 혹은 압축되지 않은 Public Key로부터 만들어 질 수 있는데, Bitcoin v0.6 이상에서는 압축된 Public Key 를 사용한다. 압축된 Public Key는 33 바이트로서 이는 여러 정해진 단계를 거쳐 20 바이트 해시로 변형되게 된다. 다음은 Public Key를 비트코인 주소로 변형하는 단계들이다.

  1. Public Key를 SHA256로 해싱한 후 다시 RIPEMD-160 해싱한다. SHA256은 256비트를 만들어 내는데, 이를 다시 160비트로 축약하기 위해 RIPEMD-160를 사용한다.
  2. 전단계 결과 앞에 1 바이트 Version Byte를 추가한다. Version Byte는 어떤 비트코인 네트워크을 사용하는가에 달라지는데, Main Network의 경우는 0x00, Test Network의 경우는 0x6f를 사용한다.
  3. 체크섬을 계산하여 전단계 결과값 뒤에 추가한다. 스텝2 결과에 대해 SHA256 해시를 두번 실행(더블해싱)한 후, 첫번째 4개의 바이트를 체크섬으로 한다.
  4. 마지막으로 전단계 결과에 Base58check 인코딩을 수행한다. 아래 Code Snippet에서는 Base58Check.Encode() 메서드가 체크섬 추가와 인코딩을 함께 수행한다.

아래 그림은 위 변환 과정을 요약해서 표현한 것이다.

아래 예제는 Public Key로부터 비트코인 주소를 생성하는 과정을 개략적으로 표현한 것이다.

public static string PublicToAddress(byte[] publicKey, NetworkType networkType)
{
    // ripemd160(sha256(pubkey))
    byte[] payload = PublicToHash160(publicKey);

    // public prefix (main: 0x00, testnet/regtest: 0x6f) for P2PKH
    byte version = (byte)(networkType == NetworkType.MainNet ? PublicPrefix.MainNet : PublicPrefix.TestNet);
    
    // version + payload
    byte[] vp = new byte[payload.Length + 1];
    vp[0] = version;
    Array.Copy(payload, 0, vp, 1, payload.Length);

    // base58check encoded address 
    return Base58Check.Encode(vp);
}

public static byte[] PublicToHash160(byte[] publicKey)
{
    byte[] data;
    // SHA256
    using (var sha = SHA256.Create())
    {
        data = sha.ComputeHash(publicKey);
    }
    // RIPEMD-160
    using (var ripemd = RIPEMD160.Create())
    {
        data = ripemd.ComputeHash(data);
    }
    return data;
}

아래는 체크섬을 함께 수행하는 Base58Check.Encode() 메서드이다. 체크섬이 추가된 후, 전체 payload에 대해 일반적인 Base58 인코딩을 수행한다.

public class Base58Check
{
    public static string Encode(byte[] payload)
    {
        // hash(hash(payload))
        byte[] hash;
        using(var sha = SHA256.Create())
        {
            // double hashing
            hash = sha.ComputeHash(sha.ComputeHash(payload));
        }
        byte[] hash4 = hash[0..4];

        List<byte> t = new List<byte>();
        t.AddRange(payload);
        // append first 4 bytes of hash
        t.AddRange(hash4);
        byte[] combined = t.ToArray();

        return Base58.Encode(combined);
    }

    //...
}