비트코인/비트코인 구조

[비트코인 구조] P2SH

라이튼 2022. 9. 16. 16:47

미리 알아야 할 내용들


 

[비트코인 구조] P2PKH

미리 알아야 할 내용들 [비트코인 구조] 개인키(Private key), 공개키(Public key), 주소(Address) 생성 미리 알아야 할 내용들 '비트코인/암호학' 카테고리의 글 목록 평범한 대학생의 블록체인 기술 관련

kwjdnjs.tistory.com


P2SH

 

 P2SH는 Pay-To-Script-Hash의 약자로 BIP16을 통해 제안되었으며, P2PK와 P2PKH와는 다르게 공개키가 아닌 리딤 스크립트(redeem script)라고 부르는 스크립트의 해시값을 이용해 비트코인을 전송하는 방식입니다. 공개키가 아닌 스크립트의 해시값을 사용하는 이유는 다중 서명과 같은 상황에서 커질 수 있는 트랜잭션과 UTXO의 용량을 줄이기 위해서입니다. 참고로 다중 서명이란 개인키가 분실될 경우 비트코인을 못 찾는 상황이나 유일한 개인키가 해킹당하여 비트코인을 모두 잃어버리는 상황을 방지하기 위해 도입된 것으로, 개인키와 공개키를 여러 개 생성해 분할로 저장하고 몇 개 이상의 개인키가 올바를 경우 서명 검증을 성공시키는 것을 말합니다.

 

P2SH 잠금 스크립트

 P2SH 잠금 스크립트는 다음과 같이 구성되어 있습니다.

 

잠금 스크립트: [OP_HASH160] <리딤 스크립트 해시> [OP_EQUAL]

 

 글의 시작부분에서 다뤘던 것처럼 P2SH에서는 공개키 대신 최대 520바이트의 리딤 스크립트를 사용합니다. 리딤 스크립트 역시 스크립트이기 때문에 opcode로 구성되어 있습니다. 어떤 형태로든 리딤 스크립트를 구성할 수 있지만, 이번 글에서는 다음과 같이 2-of-3 다중 서명(총 3개의 공개키를 최소 2개 이상의 개인키로 서명을 검증하는 방식)으로 구성된 리딤 스크립트를 사용하겠습니다.

 

리딤 스크립트: [OP_2] <공개키1> <공개키2> <공개키3> [OP_3] [OP_CHECKMULTISIG]

 

 잠금 스크립트의 리딤 스크립트 해시값 부분에 위 리딤 스크립트의 hash160값이 들어가게 됩니다.

 

P2SH 주소 생성

 P2SH는 공개키 대신 리딤 스크립트를 사용하는 만큼 기존의 비트코인 주소와 다른 형태의 주소를 사용합니다. 바로 세그윗 주소라고 불리는 3으로 시작하는 주소입니다. 물론 P2SH는 세그윗이 아니며 P2SH-P2WPKH가 세그윗(또는 네스티드 세그윗)이라고 불립니다. 해당 방식 역시 P2SH가 기본형이므로 3으로 시작하는 주소를 사용합니다. 지금까지 사용했던 1로 시작하는 주소는 레거시 주소라고 합니다.

 

 P2SH 주소를 생성하는 과정은 기존의 레거시 주소 생성 과정과 크게 다르지 않습니다. 가장 큰 차이점은 리딤 스크립트의 hash160 해시값을 사용하다는 것과 메인넷의 주소 구분 값이 '00'이 아닌 '05'를 사용한다는 것입니다.

 

 다음과 같은 리딤 스크립트의 hash160 값이 존재한다고 가정해보겠습니다.

 

리딤 스크립트 hash160: 6ffaa4b642c4d528675c12b03d07cba4bdf7ab36

 

 여기에 기존과는 다르게 '05'를 앞에 붙입니다.

 

메인넷: 056ffaa4b642c4d528675c12b03d07cba4bdf7ab36

 

 이전에 사용했던 코드를 이용하여 체크섬을 구합니다.

import hashlib

segwit = '056ffaa4b642c4d528675c12b03d07cba4bdf7ab36'

def hash256(p):
    h1 = hashlib.sha256(p)
    h2 = hashlib.sha256(h1.digest())

    print(h2.hexdigest()[:8])

hash256(bytes.fromhex(segwit))

 

체크섬: f830be7e

결괏값: 056ffaa4b642c4d528675c12b03d07cba4bdf7ab36f830be7e

 

마지막으로 Base58Check로 인코딩합니다.

 

import hashlib

segwit = '056ffaa4b642c4d528675c12b03d07cba4bdf7ab36f830be7e'

BASE58_CODE = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def base58_check(p):

    prefix = ''
    for c in p:
        if c == 0:
            prefix += '1'
        else:
            break
        
    num = int.from_bytes(p,'big') # 'big'은 빅엔디안
    result = ''

    while num > 0:
        num, mod = divmod(num, 58)
        result = BASE58_CODE[mod] + result
        
    print(prefix + result)

base58_check(bytes.fromhex(segwit))

 

 P2SH 메인넷 주소: 3Bu7Br2W3jQ79Ztne2KMrF1yTEFMbp1Y5B

 

 최종적으로 3으로 시작하는 P2SH 주소를 생성하였습니다.

 

P2SH 해제 스크립트

 P2SH의 해제 스크립트는 다음과 같이 구성합니다. P2SH 해제 스크립트에는 서명과 함께 원본 리딤 스크립트를 포함시킵니다.

 

[OP_0] <서명1> <서명2> <리딤 스크립트>

 

P2SH 서명 검증

 마지막으로 해제 스크립트와 잠금 스크립트를 이용하여 서명을 검증해보겠습니다. 지금까지 와 마찬가지로 가장 먼저 스택에 해제 스크립트를 담습니다.

 

 

 이후 잠금 스크립트를 사용합니다. 이 과정에서 P2SH를 지원하는 노드들은 추가적으로 리딤 스크립트를 검증하기 위한 준비를 합니다.

 

 먼저 잠금 스크립트에서 OP_HASH160를 불러와 스택 가장 위의 값인 리딤 스크립트의 hash160 값을 구해 스택에 넣습니다.

 

 

 그다음 잠금 스크립트에 포함된 리딤 스크립트의 해시 값을 스택에 넣습니다.

 

 

 마지막으로 OP_EQUAL을 통해 스택 가장 위에 있는 두 리딤 스크립트의 해시값을 꺼내 같은지 확인합니다. OP_EQUAL은 두 값이 같을 경우 1을 반환합니다.

 

 

 P2SH를 지원하지 않는 노드는 여기에서 검증을 종료하고 스택 가장 위에있는 값을 확인합니다. 하지만 P2SH를 지원하는 노드의 경우 리딤 스크립트의 값을 불러와 스택에 담습니다.

 

 

 마지막으로 OP_CHECKMULTISIG를 불러옵니다. 해당 명령어는 스택에서 n(여기서는 OP_3) + m(여기서는 OP_2) + 3개의 값을 불러와 다중 서명 검증을 진행합니다. 원래 n + m + 2개의 값을 불러와야 하지만 OP_CHECKMULTISIG가 버그로 인해 1개의 값을 더 불러오기 때문에 OP_0이 추가되었습니다.

 

 

 최종적으로 스택에 남은 값을 확인하고 서명 검증을 종료하게 됩니다.

 

 지금까지 P2SH에 대해 알아봤습니다. 감사합니다.

 

 

이어지는 글들


 

[비트코인 구조] 세그윗(Segwit), Bech32 주소

미리 알아야 할 내용들 [비트코인 구조] DER 형식 서명(Signature) 생성 미리 알아야 할 내용들 '비트코인/암호학' 카테고리의 글 목록 평범한 대학생의 블록체인 기술 관련 블로그 입니다. kwjdnjs.tisto

kwjdnjs.tistory.com