5️⃣

비트코인의 키와 주소

Base58Check 인코딩

만약 여러분의 개인키가 아래와 같다고 해보자
1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
소문자 엘과 대문자 아이, 숫자 영과 대문자 오를 구분하는것이 너무 어렵다.
혹시나 타이핑 오류가 나지 않았을지 걱정된다.
그래서 아래와 같이 개인키를 표현할 수 있다.
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
여기엔 대문자 오, 대문자 아이, 소문자 엘, 숫자 영이 없다.
타이핑 오류를 방지해준다.
인코딩은 데이터를 다양한 목적으로 다른 형식으로 변환하는 것을 의미한다.
예를들어 영상파일을 mp4로 인코딩 하는 이유는 고품질의 압축을 하기 위해서이다.
비트코인에서 Base58Check 인코딩 하는 이유는 사용자의 타이핑 실수를 방지하기 위해서이다.
Base58 인코딩은 다음과 같은 방법을 쓴다.
숫자 1000을 Base58 인코딩을 해보자.
먼저 다음 치환표가 필요하다.
1000을 58로 나눈다. 나누면 몫은 17이고 나머지는 14이다.
14는 치환표 상에서 F이므로 이를 기록한다.
다시 몫 17을 58로 나눈다. 몫은 0이고 나머지는 17이다.
(보통은 훨씬 더 큰 수인데 몫이 0이 될때까지 반복적으로 수행한다.)
17은 치환표 상에서 J이므로 이를 기록한다.
결국 숫자 1000을 Base58인코딩하면 ‘JF’ 가 된다.
Check 구하기
체크섬(checksum)은 앞에 적힌 글자가 타이핑 오류가 없었는지 확인하는데 유용하다.
예를들어 1357이라는 숫자가 작 적혔는지 확인하기 위해 각 숫자를 더한 16을 뒤에 붙여보자.
135716이라 적으면 뒤에 붙어있는 16을 통해 앞의 것이 정확히 적혔는지 알 수 있다.
Base58Check에서 체크섬을 위해 해시함수를 사용한다.
원본 데이터를 SHA-256 해시함수에 넣어 해시코드를 추출한다.
해시코드의 앞 4바이트를 자른다.
원본 데이터 뒤에 자른 4바이트를 붙인다.
Base58 인코딩을 수행한다.
Base58Check 인코딩 종료

개인키와 엔트로피

비트코인의 개인키는 무작위로 추출된 256비트 길이의 숫자이다.
개인키의 소유권과 통제권은 당사자에게 있다.
키를 생성하는 과정 중 가장 첫번째 단계는 엔트로피를 위한 무작위성을 찾는 것이다.
비트코인 소프트웨어는 운영체제의 난수생성기로 엔트로피를 만든다.

개인키의 표현

개인키를 직렬화해서 보낼 경우는 별로 없다.
원칙적으로는 개인키는 네트워크로 전파하지 않기 때문이다.
그래도 개인적으로 개인키를 옮기고 싶다면 아래의 포멧을 사용한다.
유형
접두어
설명
원형
없음
32바이트
Hex
없음
64 자리 16진수 숫자
WIF
5
Base58Check 인코딩: 버전 접두어 0x80(128)과 4바이트 체크섬을 포함하는 Base58
WIF-압축형
K 또는 L
위와 같음. 인코딩 전 접미어 0x01 추가
포맷
개인키 예시
Hex
1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
WIF
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
WIF-압축형
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
WIF(Wallet Import Format)는 오타나 사용자의 실수로부터 안전한 형태의 포멧이다.
개인키를 네트워크로 전파하는 목적 보다는 손으로 옮겼을 때를 고려한 형태의 포멧이다.
WIF 형식은 다음과 같은 방식으로 만든다.
접두어를 설정한다. 메인넷은 0x80, 테스트넷은 0xef로 한다.
공개키 형식이 압축형 형식이면 접미어 0x01을 붙인다.(WIF-압축형)
개인키를 10진수로 전환한다.
접두어, 10진수 개인키, 접미어를 순서대로 연결한다.
결과를 sha256으로 해시하고 앞 4바이트를 체크섬으로 취한다.
체크섬을 붙인다.
Base58인코딩을 수행한다.

공개키의 표현

공개키는 타원위의 점(x,y)라는 점을 상기하자.
x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
Plain Text
복사
520 비트의 공개키는 앞에 공개키를 표현하는 접두어 04와 256비트의 숫자 2개를 이어붙여 표현한다.
K = 04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A↵ 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
Plain Text
복사
크기의 한계를 극복하기 위해 비트코인에서는 압축 공개키를 사용한다.
x 값을 알면 타원곡선의 함수를 통해 y값을 계산할 수 있기 때문에 x값만 저장하여 256비트를 절약한다.
압축 공개키는 접두어가 02, 03이다.
타원곡선의 특성상 x값 1개에 y값이 2개일 수 있다. 어떤 y값을 갖느냐에 따라 접두어가 결정된다.
두 개의 값은 짝수이거나 홀수인데, 짝수면 02, 홀수면 03을 붙인다.
타원곡선에서 한 x에 두 개의 해가 짝수와 홀수로 이루어지는 이유
곡선 상에 두 개의 점 (x,y) (x,-y)를 만족해야 한다.
유한체에서는 -y=(p-y)%p로 결정되기 때문에, y 좌표값은 p-y가 된다.
p는 소수이기 때문에 홀수이다. (2 제외)
따라서 홀수-짝수=홀수 이거나 홀수-홀수=짝수 임을 감안한다면,
하나가 짝수이면 나머지 하나는 홀수, 하나가 홀수이면 나머지 하나는 짝수가 된다.
압축된 공개키의 예시는 다음과 같다.
K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
Plain Text
복사
공개키의 표현 방법

비트코인의 주소

비트코인 주소는 개인키, 공개키와 연관을 지을 수 있어야 한다.
비트코인 주소의 주인은 연관된 개인키를 알고 있는 사람이기 때문이다.
따라서 비트코인 주소는 공개키로부터 추출된다는 사실은 매우 당연하다.
비트코인의 주소는 공개키(K)로부터 해시함수를 사용하여 생성된다.
해시함수란?
해시함수는 같은 값을 입력하면 같은 값을, 다른 값을 입력하면 완전히 다른 값을 출력한다.
비트코인의 주소를 만들기 위해서는 해시함수에 대한 이해가 필요하다
해시함수는 입력값에 대해 임의의 길이로 고정된 결과값을 보여준다.
def simple_hash(input_string): # 입력 문자열을 아스키 코드 값들의 합으로 변환 total_ascii = sum(ord(char) for char in input_string) # 고정된 범위 내로 해시 값 조정 hash_value = total_ascii % 100 return hash_value input_data = "Hello, Hash Function!" hashed_value = simple_hash(input_data) print("Input Data:", input_data) print("Hashed Value:", hashed_value)
Python
복사
해시함수의 일종인 SHA256의 경우 다음 6개의 연산을 사용한다.
비트연산 : 비트 단위의 AND, OR, NOT. XOR 연산
비트 시프트 연산 : <<, >> 비트를 왼쪽 오른쪽으로 이동하고 끝에 0을 추가하는 연산
논리 함수 : 여러 비트간의 AND, OR, NOT. XOR 연산
회전 : 비트들을 주어진 수 만큼 왼쪽, 오른쪽으로 회전하는 연산
덧셈 : 정수나 비트간의 덧셈연산
체이닝 함수 : 위 연산 단계를 여러번 반복적으로 수행하는 연산.
해시함수의 특징은 다음 셋만 기억하자.
단방향성 : 결과값(output)으로 입력(input)을 추론할 수 없다.
고정길이 : 일정한 길이로만 출력된다.
패턴없음 : 결과값에 패턴이 없다.
비트코인에서 사용하는 해시함수의 종류는 다음과 같다.
SHA-256 : 256비트의 고정된 길이로 만들어주는 해시함수이다. 미국국립표준기술연구소에서 공인된 계열 중 하나이다.
RIPEMD-160 : 160비트의 고정된 길이로 만들어주는 해시함수이다. SHA 계열보다 처리 속도가 빠르다.
HMAC-SHA-512 : SHA-512보다 보안성이 높은 해시함수이다. SHA-512에 입력 데이터와 키를 조합하여 결과를 생성한다.
공개키에 SHA256를 먼저 적용 후 그 결과에 RIPEMD160를 적용하여 값을 얻는다.
A = RIPEMD160(SHA256(K))
공개키에서 비트코인 주소로 변환하는 과정
이후엔 Base58Check로 인코딩한다. 이 과정을 천천히 알아보자.
Base58Check 인코딩 과정
앞에 버전 정보를 기록하여 해당 문자가 어떤 주소를 나타내는지 표현한다.
유형
버전 접두어(16진수)
Base58 접두어 결과값
P2PK, P2PKH
0x00
1
P2SH
0x05
3
비트코인 테스트넷 주소
0x6F
m 혹은 n
개인키 WIF(Wallet Import Format)
0x80
5, K 혹은 L
BIP-38 암호화 개인키
0x0142
6P
BIP-32 확장 공개키
0x0488B21E
xpub
결과값 A는 Base58Check로 인코딩된다.
주소 생성시 체크섬은 SHA256해시를 2회 실시한 후 앞의 4글자로 쓴다.
checksum = SHA256(SHA256(prefix + data))
아래의 소스코드를 참고하자.
import hashlib BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' s=bytes.fromhex('7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d') def encode_base58(s): count = 0 for c in s: # <1> if c == 0: count += 1 else: break # 16진수를 10진수로 전환한다. num = int.from_bytes(s, 'big') prefix = '1' * count result = '' while num > 0: # <2> #num을 58로 나눈 뒤 몫은 num, 나머지는 mod에 저장한다. num, mod = divmod(num, 58) #result에 나머지를 누적하여 기록한다. result = BASE58_ALPHABET[mod] + result return prefix + result def encode_base58_checksum(b): #sha256 해시를 2회 실시한 후 앞의 4자리를 붙여서 리턴한다. return encode_base58(b + hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]) h='7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d' print(encode_base58_checksum(bytes.fromhex(h))) #-----결과----- wdA2ffYs5cudrdkhFm5Ym94AuLvavacapuDBL2CAcvqYPkcvi
Python
복사

구현: 비트코인 주소 생성

#공개키 클래스에서 구현 class S256Point(Point): #주소를 리턴하는 함수이다. def address(self, compressed=True, testnet=False): '''Returns the address string''' h160 = self.hash160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' return encode_base58_checksum(prefix + h160)
Python
복사
#테스트 코드 from ecc import PrivateKey priv = PrivateKey(5002) print(priv.point.address(compressed=False, testnet=False)) #-----결과----- 16wSJUKH9aMz7Fx9E6iiV9oR4eHaMFaWCB
Python
복사

다른 형태의 주소

P2SH 주소는 3으로 시작하는 주소이다.
P2SH는 멀티시그 주소를 의미한다.
P2SH는 공개키의 소유주가 아닌 스크립트 해시로 지정한다. (BIP-16)
P2SH 주소 인코딩하려면 스크립트를 해시하여 주소를 표현한다.
script hash = RIPEMD160(SHA256(script))
현재는 가장 대중적으로 사용되고 있다.
향후 P2SH에서 이를 다루고자 한다.

참고자료