비트코인 Key 생성
비트코인 Private/Public Key 생성하는 방법
비트코인은 한 쌍의 Private Key와 Public Key를 기반으로 한 Elliptic Curve Digital Signature Algorithm (ECDSA) 암호화 방식을 사용한다.
DSA는 RSA와 비슷한 방식으로 Private/Public 키를 사용하는데, 일반적으로 DSA는 RSA에 비해 Signature 생성이 빠르지만 Validation은 더 느리다.
DSA의 한 유형으로 Elliptic Curve 를 사용하는 방식을 ECDSA라 하는데, 비트코인은 여러 Elliptic Curve 중 secp256k1 커브를 사용하고 있다.
secp256k1 는 아래와 같은 수학적 함수를 통해 생성된 그래프를 사용한다.
.NET Framework은 자체적으로 ECDSA 구현을 제공하고 있지 않으므로, ECDSA를 사용하기 위해서 3rd Party 라이브러리를 사용하게 되는데,
여기서는 이 중 BouncyCastle 라이브러리를 사용해 본다. BouncyCastle를 사용하기 위해서는 다음과 같이 Nuget 패키지를 설치하면 된다.
PM> Install-Package BouncyCastle
BouncyCastle에서 SecNamedCurves.GetByName() 메서드를 사용하면 Well Known Elliptic Curve를 불러올 수 있는데,
예를 들어, GetByName("secp256k1")와 같이 지정하여 secp256k1 커브에 대한 파라미터들을 가져올 수 있다.
ECKeyPairGenerator 클래스는 Private Key와 Public Key 쌍을 생성하는 기능을 가지고 있는데,
이 클래스의 초기화(Init) 메서드에 Elliptic Curve 정보와 Random 생성을 위한 객체를 넣어 주고,
이어 키생성 메서드인 GenerateKeyPair()을 호출하여 키쌍을 생성한다.
생성된 키쌍은 AsymmetricCipherKeyPair의 객체로서 이 객체의 Private 속성에는 Private 키 정보가,
Public 속성에는 Public 키 정보가 각각 들어 있다.
/*
using System;
using System.Linq;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
*/
// Elliptic Curve
X9ECParameters ec = SecNamedCurves.GetByName("secp256k1");
ECDomainParameters domainParams = new ECDomainParameters(ec.Curve, ec.G, ec.N, ec.H);
SecureRandom random = new SecureRandom();
// Generate EC KeyPair
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
ECKeyGenerationParameters keyParams = new ECKeyGenerationParameters(domainParams, random);
keyGen.Init(keyParams);
AsymmetricCipherKeyPair keyPair = keyGen.GenerateKeyPair();
ECPrivateKeyParameters privateKeyParams = keyPair.Private as ECPrivateKeyParameters;
ECPublicKeyParameters publicKeyParams = keyPair.Public as ECPublicKeyParameters;
// Get Private Key
BigInteger privD = privateKeyParams.D;
byte[] privBytes = privD.ToByteArray();
if (privBytes.Length == 33)
{
var temp = new byte[32];
Array.Copy(privBytes, 1, temp, 0, 32);
privBytes = temp;
}
else if (privBytes.Length < 32)
{
var temp = Enumerable.Repeat<byte>(0x00, 32).ToArray();
Array.Copy(privBytes, 0, temp, 32 - privBytes.Length, privBytes.Length);
privBytes = temp;
}
string privKey = BitConverter.ToString(privBytes).Replace("-", "");
// Get Compressed Public Key
ECPoint q = publicKeyParams.Q;
FpPoint fp = new FpPoint(ec.Curve, q.AffineXCoord, q.AffineYCoord);
byte[] enc = fp.GetEncoded(true);
string compressedPubKey = BitConverter.ToString(enc).Replace("-", "");
// Get Uncompressed Public Key
enc = fp.GetEncoded(false);
string uncompressedPubKey = BitConverter.ToString(enc).Replace("-", "");
// Output
Console.WriteLine(privKey);
Console.WriteLine(compressedPubKey);
Console.WriteLine(uncompressedPubKey);
비트코인에서 Private Key는 32 바이트이다 (주: 일부 최신 지갑에서 128 ~ 512 비트 사용하기도 함). Public Key는 압축된 키와 압축이 되지 않은 키 두 종류가 있다.
Public Key는 EC 커브 상의 한 점 (X, Y) 좌표로 구성되는데, X와 Y 좌표는 각각 32 바이트 숫자이다.
압축되지 않은 경우는 이 X, Y 좌표값과 1 바이트의 Prefix를 붙여 65 바이트가 되며,
압축된 경우는 X 좌표값과 1 바이트의 Prefix를 붙여 33바이트가 된다 (이때 Y 좌표는 계산에 의해 산출됨).
Public Key의 첫번째 Prefix 바이트는 압축된 경우 0x02 혹은 0x03 이 되며, 압축되지 않은 경우 0x04 값을 갖는다.
Public Key가 압축된 경우 Y 좌표값이 짝수이면 0x02 가 앞에 붙게되고, 홀수이면 0x03 이 붙게된다.
BouncyCastle에서 FpPoint.GetEncoded(bool) 메서드에 true를 넣으면 키가 압축되고, false 이면 압축이 되지 않는다.
Bitcoin Private Key와 Public Key가 올바르게 생성되었는지 체크하기 위해 bitaddress.org 웹사이트의 [Wallet Details]
기능을 사용할 수 있다. 이곳에서 위에서 생성된 Private Key를 넣으면 상응하는 Public Key를 비롯하여 여러 주소 정보를 얻을 수 있다.