비트코인 스크립트에는 P2PKH 말고도 용도에 따라 다양한 타입의 트랜잭션이 존재할 수 있다.
지난 글에서는 P2SH 라는 타입의 트랜잭션을 추가적으로 정리하였다.
이번 글에서는 다른 종류의 트랜잭션들에 대해 추가적으로 정리해본다.
Timelock
비트코인 input 부분의 sequence, 트랜잭션 자체의 timelock 외에도 스크립트와 관련하여 timelock을 사용할 수도 있다.
타임 락은 트랜잭션 자체에서 명시할 수도 있지만, output script 에서도 명시할 수 있다.
그러면 그 시간이 지난 시점부터 이 output 에서 비트코인을 꺼낼 수 있도록 하는 것이다.
타임락은 이렇게 트랜잭션 레벨의 타임락과 UTXO 레벨의 타임락으로 구분이 될 수 있다.
그래서 사실 input 에 제시되는 sequence 는 output 에서 제시된 timelock 과 비교해 output이 제시한 lock 조건을 만족시키는 sequence 값이 들어가야만 해당 아웃풋으로부터 비트코인을 꺼내 쓸 수 있는 구조로 되어있다.
먼저 트랜잭션 레벨의 타임락의 경우, 만약 어떤 트랜잭션이 이 타임락을 만족하지 않는 상태에서 뿌려지게 되면, 그 트랜잭션을 받은 노드는 락타임을 보고 만족하지 않은 경우, 다른 노드에게 전파하지 않고 트랜잭션을 버린다.
예를 들어 앨리스가 트랜잭션에 3개월 락타임을 걸고 밥에게 3개월 뒤에 뿌리라고 주었을 때, 밥이 무시하고 그냥 뿌려도 노드들이 이를 받아들이지 않기 때문에 트랜잭션이 컨펌되지 않는다. (invalid 로 간주한다.)
CLTV
CLTV 라는 명령어는 output script 에서 사용하는 명령어로, Timelock 을 검증하는 스크립트 코드이다.
output script 에 이 명령어가 있으면 output script 가 리딤하는 트랜잭션의 락 타임이 OP_CLTV 에 의해 확인된다.
이 명령어 (연산자) 는 하나의 파라미터를 받으며, 그 파라미터는 트랜잭션의 타임락 포맷과 같다.
(5억보다 작으면 블록 수, 크거나 같으면 실제 시간)
만약 트랜잭션에 명시된 락타임 시간이 지나지 않았는데 트랜잭션을 뿌리면 이 명령어가 검증한 결과가 False 가 나오면서 스크립트 실행이 멈춘다.
사용 예시는 위와 같다.
원래는 pubkey, checksig 만 들어있었다면 그 뒤에 추가적으로 시간 조건과 CHECKLOCKTIMEVERIFY (CLTV) 연산을 추가로 넣는 것이다. 이 2개가 모두 True 여야 유효한 트랜잭션이 된다.
아래에서는 CHECKSIG 대신 CHECKSIGVERIFY 를 사용했다.
왜냐하면 하나를 검증한 뒤에 추가적으로 수행할 작업이 있으므로 True 를 남길 필요가 없기 때문이다.
그래서 만약 현재 이 output 트랜잭션이 들어가 있는 블록의 높이가 10만인데, 이 output 에서 돈을 꺼내 쓰는 트랜잭션이 채굴될 때 그 블록의 높이가 119280 보다 크거나 같다면 허용되고, 작다면 invalid 가 된다.
따라서 이를 지정하기 위해 내가 이 output 트랜잭션을 가지고 뭔가 지불을 하고 싶다면 트랜잭션을 만들 때 lock time 에 119280보다 큰 값을 지정해서 넣어두면 그 시점이 지날 때 유효하다고 인정을 받을 수 있다.
참고로 CLTV 연산자를 사용할 때 이 연산자는 TOP 에 들어있는 시간을 검증만 하고 지우지는 않기 때문에 추가적으로 DROP 이라는 연산자를 통해서 시간을 비워줘야 이후에 실행되는 코드에 영향을 주지 않을 수 있다.
Relative Timelock
트랜잭션 lock time 은 absolute time lock 이었다.
반면 sequence에 들어가는 relative timelock 은 상대적이다.
이 시간을 체크할 때는 CSV (CheckSequenceVerify) 라는 연산자를 사용한다.
그래서 어떤 output 이 확정된 이후의 시간을 재는 것을 말한다.
이 연산자는 sequence 와 관련이 있는 만큼, 당연히 input script 에 존재한다.
이런 타임락은 a->b, b->c, c->d 와 같은 트랜잭션이 있을 때, 각 트랜잭션마다 시간 간격이 어느 정도 필요하다면 그걸 명시하는데 사용된다. (즉, 필요한 시간 간격의 하한을 명시하는 것이다.)
이 CSV 역시 sequence 가 사용하는 시간/블록 단위를 그대로 사용한다.
인풋의 sequence number 가 CSV 에서 조건으로 제시한 값보다 크거나 같아야 한다.
이 relative timelock 의 경과시간은 UTXO가 블록체인에 포함되기 전에는 카운트되지 않는다.
블록체인에 포함되어서 Confirm이 되었을 때, 그때부터 카운트 된다고 생각하면 된다.
Flow Control
스크립트 안에서는 위와 같은 다양한 흐름 제어 명령어와 bool 대수 연산자를 제공한다.
NOTIF 는 조건에 not 을 붙인 경우 사용하는 것이라고 생각하면 된다.
그래서 위 명령어를 사용하여 스크립트 코드를 작성할 때는
이런 형식으로 작성하면 위의 슈도코드와 같은 의미가 된다.
먼저 condition 을 제시하고 if 로 True 인지 체크하고, 그러면 그 다음 스크립트 코드가 실행되고 아니면 ELSE 에 넘어가서 그쪽의 코드가 실행되고, ENDIF 이후의 코드는 항상 실행된다. 또한 이런 컨디션은 얼마든지 중첩될 수 있다.
VERIFY 연산자
VERIFY 연산자는 연산 결과값이 NOT TRUE 이면 그냥 거기서 스크립트가 종료되는 연산자이다.
만약 VERIFY 라는 말이 붙어있지 않으면 해당 연산자는 연산 결과를 스택에 push 를 하지만 이 연산자는 그냥 종료시킨다는 점이 차이점이다. 그리고 그 트랜잭션이 invalid 하다고 간주한다.
예를 들어 밥이 자신의 서명과 has pre-image 를 제시했다고 했을 때, 이 인풋에 이어서 위와 같은 output 을 제시했다고 해보자.
그러면 먼저 hash pre-image 를 해싱하고, 기대하는 해시와 같은지 검증한다.
만약 검증에 성공하면 별도 리턴값 없이 그대로 스크립트가 실행되어 밥의 서명과 공개키를 CHECKSIG를 통해 검증한다.
즉 시그니처를 검증하기 전에 먼저 pre-image 를 검증하는 기능을 수행하는데 사용할 수 있다.
그리고 같은 기능을 하는 스크립트 코드를 위와 같이 작성할 수도 있다.
먼저 preiimage 를 해싱한 뒤, 그 값이 EQUAL 인지 체크한다. 같다면 TRUE 가 남을 테니 IF 연산자에 대해 실행이 될 것이다.
그러면 기존 밥의 공개키를 스택에 추가해서 CHECKSIG 연산자가 실행될 것이고,
만약 해시값이 다르다면 바로 ENDIF 로 가면서 스크립트가 종료되는데, TRUE 가 남아있지 않으므로 (?) valid 하지 않다고 간주한다.
segwit 을 지원하지 않는 노드가 스크립트를 검증하는 경우, segwit 자체를 데이터로 간주하고 스크립트가 유효하다고 판정한다고 했는데 데이터가 남아있으면 일단 valid 라고 보는게 아닌건가?
=> 세그윗의 경우 데이터가 input 이 아니라 별도 영역으로 빠지므로, 지원하지 않는 노드는 input 을 검증할 때 비어있다고 생각하고 유효하다고 넘긴다. => input 이 없으면 output 에 있는 segwit version + segiw program 만 잇는데 이거 2개만 보고 유효하다고 넘기나?
=> 그렇다면 꼭 True 가 아니어도 데이터가 남아있으면 되는건가? 0이 아니면 다 true 로 보는 건가?
=> 그냥 데이터 덩어리만 있다면 알아서 TRUE 를 넣어주는 것 같다.
그렇다면 언제 IF 를 쓰고 언제 VERIFY 를 사용하는 것이 좋을까?
만약 if 문에 해당하는 경우만 실행하고자 한다면 verify 를 쓰는 것이 더 낫고, else 라는 분기가 존재한다면 그때 if 문을 쓰는 것이 좋다.
예를 들면 위와 같은 상황이다.
앨리스와 밥이 공동 창업자라서 이 둘 중에 한명의 서명만 있으면 output 에서 돈을 뺄 수 있다고 해보자.
이떄는 위와 같이 output script 를 작성하고, input script 는 앨리스의 서명을 쓰는 경우 1을 밥의 서명을 쓰는 경우 0을 같이 넘기면 된다. (1 = true 로 간주하고 0 = false로 간주한다.)
중첩된 if 문을 사용한다면 이렇게 작성할 수 있고, 만약 script B를 실행하고 싶다면 unlocking script 로는 1 0 을 제시하고, C 스크립트를 실행하고 싶다면 0 0 을 제시하면 된다.
SegWit
세그윗은 비트코인 블록에 트랜잭션을 많이 담지 못하는 문제를 해결하고자 서명 부분을 분리한 것이다.
그래서 기존의 base block 은 1MB를 유지하되 segwit 부분을 추가적인 블록으로 만들어 총 4MB의 블록으로 크기를 증가시키게 되었다.
기존의 P2PKH 는 다음과 같은 output script 를 갖고 있을 때,
이 output 을 가져다가 사용하려고 하는 경우, 서명을 제시할 때는 input script 로 서명 데이터를 제시해야 했다.
하지만 SegWit이 적용된 Pay-to-Witness-Public-Key-Hash (P2WPKH) 방식에서는 위와 같은 형태로 witness version 과 witness 프로그램이 합쳐진 output을 사용한다.
그리고 이 ouput을 가져다가 지불에 사용하고자 할 때는 input script 를 비워놓고 witness 영역에 밥의 witness 데이터를 제공하면 된다. 세그윗은 송신자와 수신자가 모두 세그윗을 지원해야 한다.
그래야 송신자가 제시한 witness 프로그램을 월렛 프로그램이 해석할 수 있기 때문이다.
P2SH 의 경우도 마찬가지다 이렇게 기존처럼 했었다면,
P2WSH 에서는 이렇게 output 을 제시하고,
해당 위치에 지불할 때는 이렇게 witness 영역에서 원본 스크립트와 서명을 제공하면 된다.
이떄 P2WPKH 와 P2WSH 를 어떻게 구분할 수 있을까?
정답은 해시의 길이를 보면 된다.
P2WPKH 에서는 퍼블릭 키 해시의 길이가 20byte 이고, P2WSH 방식에서는 스크립트 hash 의 길이가 32byte이다.
SegWit 으로의 업그레이드
세그윗을 지원하지 않는 노드가 세그윗을 지원하도록 업그레이드 할 때는 월렛 프로그램이 세그윗 아웃풋을 만들 수 있도록 업그레이드 한다. (2024년 기준으로는 100% 세그윗을 지원한다. 2017년 등장한 직후에 해당하는 얘기다)
이때 월렛은 기존 방식도 알고는 있어야 한다. 2017년 이전의 트랜잭션에 대해 지불하는 트랜잭션을 처리해야 할 수도 있기 때문이다.
Segwit 을 사용하기 전에는 txid 에 서명이 포함되었지만, segwit 이후에는 txid를 계산할 때 서명이 제외되고, 대신에 witness 에 대해서 추가적인 id 인 wtxid 를 계산하게 된다. (기존의 txid 계산에 필요한 데이터 + witness 를 포함한 것)
세그윗은 경제적으로도 도움이 된다.
세그윗이 없었다면 트랜잭션 수요를 감당하지 못해서 수수료가 증가하고, 마이너들도 많은 UTXO를 저장해야 하기때문에 더 많은 컴퓨팅 리소스를 사용해야 하기 때문이다.
'CS > 블록체인' 카테고리의 다른 글
[블록체인] 13. Mining (0) | 2024.10.25 |
---|---|
[블록체인] 12. 비트코인 script (1) (0) | 2024.10.25 |
[블록체인] 11. 비트코인 트랜잭션 (1) | 2024.10.25 |
[블록체인] 10. 비트코인이 해결하는 문제들 (1) | 2024.10.25 |
[블록체인] 9. 암호학 개념들 (0) | 2024.10.24 |