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에서 이를 다루고자 한다.