‘Mastering Bitcoin 2nd’ 세미나 9, 7장. Advanced Transactions and Scripting 1/2
Chapter 7. Advanced Transactions and Scripting
이번 장은 분량이 많지는 않지만 한 번에 진행할 경우 뒷 부분에서 집중도가 떨어질 것 같아, 두 번으로 나누어 진행합니다. 이번 세미나에서는 Scripts with Flow Control (Conditional Clauses) 전 까지만 진행합니다.
Multisignature
이번 장에서는 좀 더 복잡한 스크립트들을 다룹니다. 첫 번째로 다중서명을 가능하게 하는 스크립트를 다룹니다.
Multisignature scripts set a condition where N public keys are recorded in the script and at least M of those must provide signatures to unlock the funds.
다중서명 스크립트는 이름 그대로 다중서명을 요구하는 스크립트입니다. 잠금에 N개의 공개키가 사용되고, 그 중에 적어도 N개의 서명이 올바르게 제공될 때 잠금이 해제되는 스크립트로 M-of-N으로 표현됩니다. 예를 들어 3개의 공개키가 있고 그 중 적어도 두 개 이상의 올바른 서명이 요구되는 다중서명 스크립트는 2-of-3라고 합니다.
잠금 스크립트는 다음과 같은 형식으로 작성됩니다.
- M <Public Key 1> <Public Key 2> … <Public Key N> N CHECKMULTISIG
해제 스크립트는 다음과 같은 형식으로 작성됩니다.
- <Signature 1> <Signature 2> … <Signature M>
CHECKMULTISIG 구현에는 버그가 있습니다. M개를 스택에서 꺼내야 하는 데 M + 1개를 꺼냅니다. 이 버그 때문에 연결된 스크립트의 맨 앞에 의미 없는 0을 추가해야 합니다. 해제 스크립트는 이 버그로 인해 위에서와 같이 작성하는 것이 아니라 다음과 같은 형식으로 작성되어야 합니다.
- 0 <Signature 1> <Signature 2> … <Signature M>
예) 2-of-3 다중서명 연결 스크립트 작성
- 0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
Pay-to-Script-Hash (P2SH)
Pay-to-Script-Hash (P2SH) was introduced in 2012 as a powerful new type of transaction that greatly simplifies the use of complex transaction scripts. To explain the need for P2SH, let’s look at a practical example.
P2SH는 복잡한 트랜잭션 스크립트 사용을 좀 더 간단하게 할 수 있도록 2012년에 새롭게 도입한 것입니다.
비트코인 시스템에서 다중서명 스크립트는 대표적인 복잡한 트랜잭션 스크립트입니다. P2SH는 사실 다중서명 스크립트의 문제를 해결하기 위한 것입니다.
M-of-N 다중서명 스크립트 작성방법은 여러 가지 문제가 있습니다. 그 중 가장 중요한 문제는 트랜잭션 출력의 잠금 스크립트에 N개의 공개키 해시를 가져야 한다는 점입니다. 비트코인은 트랜잭션 출력이 사용되기 전까지 UTXO로 관리되는데, UTXO는 램 메모리를 사용하는 비용이 높은 자원입니다.
P2SH는 공개키 해시 값들을 잠금 스크립트에 포함하지 않고, 이들을 별도 스크립트(Redeem Script라 불림)로 작성하고 이 스크립트에 대한 해시 값만을 포함하게 함으로 이 문제를 해결합니다.
표 1. Complex script without P2SH와 표 2. Complex script as P2SH를 비교해 봅니다. P2SH로 작성된 다중서명 스크립트에서는 잠금 스크립트에서 감당해야 하는 짐을 해제 스크립트로 옮김으로 M-of-N 다중서명 방식의 문제를 해결하고 있음을 알 수 있습니다.
여기까지 읽고 나면 의문이 하나 들 것입니다. ‘Redeem Script는 어디에서 관리되는가?’입니다.
비트코인 시스템은 Redeem Script를 블록체인에 기록하지도 않고, 다중서명 과정도 지원하지 않기 때문에 이를 위한 별도 시스템을 갖추고 있어야 합니다.
P2SH Addresses
P2SH에서 스크립트에 대한 해시 값도 20바이트이기 때문에 P2PKH에서와 같은 방식으로 주소를 생성할 수 있습니다. 사용자들은 비트코인을 전송할 때 그 주소가 공개키 해시 값을 사용하는 주소인지, 스크립트 해시 값을 사용하는 주소인지 구분할 필요가 없습니다. P2SH 주소는 버전 번호로 5를 사용하는데 이를 Base58Check으로 인코딩하면 3으로 시작됩니다.
The two scripts are combined in two stages.
- First, the redeem script is checked against the locking script to make sure the hash matches.
- If the redeem script hash matches, the unlocking script is executed on its own, to unlock the redeem script
P2SH 스크립트는 두 단계로 실행됩니다. 첫 번째 단계에서는 Redeem Script가 스크립트 해시 값과 같은지 비교하기 위해서이고, 두 번째 단계는 Redeem Script를 실행하는 것입니다.
Data Recording Output (RETURN)
별로 중요한 내용은 아닙니다. ‘이런 것이 있다’는 정도로 확인하고 넘어 갑니다.
Many developers have tried to use the transaction scripting language to take advantage of the security and resilience of the system for applications such as digital notary services, stock certificates, and smart contracts.
비트코인 시스템은 지불(payment)에 특화된 블록체인입니다. 하지만 블록체인이 갖는 고유한 역량을 활용하고 싶어하는 개발자들은 지불 외에도 다양한 용도로 비트코인 시스템을 사용하고자 했습니다.
RETURN allows developers to add 80 bytes of nonpayment data to a transaction output.
RETURN 연산자는 다음과 같은 형식으로 트랜잭션 출력에 80바이트의 비지불 정보를 작성할 수 있도록 합니다.
- RETURN <data>
Timelocks
Timelocks are restrictions on transactions or outputs that only allow spending after a point in time.
Bitcoin has had a transaction-level timelock feature from the beginning. It is implemented by the nLocktime field in a transaction.
타임락은 이름 그대로 트랜잭션을 시간을 기준으로 잠궈두는 것입니다. 타임락은 트랜잭션을 기준으로 적용되는 것으로 트랜잭션의 nLockTime 필드로 구현되어 있습니다.
타임락은 이중지불 문제가 발생할 수 있기 때문에 비트코인 시스템 초기부터 있어 왔지만 거의 사용되지 않습니다.
타임락으로 잠겨 있는 트랜잭션은 특정 시간이 되기 전까지는 트랜잭션을 전송받은 노드에서 유효하지 않은 것이 되어 비트코인 네트워크로 전파되지 않습니다. 따라서 잠금 시간 전까지는 제약사항이 비트코인 시스템 밖에서만 존재하고 비트코인 시스템 내에는 존재하지 않습니다.
예를 들어 엘리스가 밥에게 특정 시간 이후에 사용할 수 있도록 트랜잭션을 작성하고 서명해서 밥에게 전달합니다. 이 트랜잭션은 비트코인 네트워크로 전송해도 전파되지 않습니다. 밥은 특정 시간이 지나고 나서야 이 트랜잭션을 전송할 수 있게 됩니다. 시간 제약은 비트코인 시스템 밖에서는 작동하고 있지만 비트코인 시스템 내에서는 작동하지 않고 있습니다. 따라서 엘리스는 밥에 지불하기로 한 비트코인을 시간 제약 이전에 사용해 버릴 수 있게 됩니다.
이 문제를 해결하기 위한 등장한 것이 UTXO 수준의 타임락입니다. UTXO 수준에서 타임락을 제공하기 위해 CHECKLOCKTIMEVERIFY와 CHECKSEQUENCEVERIFY 연산자가 사용됩니다.
Check Lock Time Verify (CLTV)
CLTV is a per-output timelock, rather than a per-transaction timelock as is the case with nLocktime. The standard is defined in BIP-65 (CHECKLOCKTIMEVERIFY).
CLTV는 트랜잭션 마다 락타임을 작성하는 것이 아니라, 트랜잭션 출력마다 CHECKLOCKTIMEVERIFY 연산자를 사용해서 작성됩니다. 사용되지 않은 트랜잭션 출력은 UTXO로 관리되니 결국 UTXO 수준의 시간 제약이 되는 것입니다.
CHECKLOCKTIMEVERIFY 연산자는 nLocktime과 같은 형식으로 작성된 하나의 입력을 받고, 검증 성공 유무를 리턴합니다.
CLTV로 트랜잭션 출력을 잠그려면 시간 제약이 없는 잠금 스크립트에 CHECKLOCKTIMEVERIFY를 추가해서 Redeem Script로 작성해야 합니다. P2PKH인 경우 Redeem Script는 다음과 같이 작성됩니다.
- <시간 제약> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <Public Key Hash> EQUALVERIFY CHECKSIG
- 시간 제약은 nLocktime과 같이 작성되어 블록 높이나 유닉스 타임스탬프로 적용됩니다.
- 0보다 크고 500,000,000 보다 작을 때는 블록 높이로 적용되고, 이거 보다 크거나 같으면 유닉스 타임스탬프로 적용됩니다.
- 시간 제약은 nLocktime과 같이 작성되어 블록 높이나 유닉스 타임스탬프로 적용됩니다.
CHECKLOCKTIMEVERIFY에서 주의할 것은 트랜잭션의 nLocktime 값이 CHECKLOCKTIMEVERIFY의 입력 값으로 작성된 시간 제약보다 크거나 같아야 한다는 것입니다. 또한 트랜잭션의 nSequence 값이 0xffffffff가 아니어야 합니다. 그렇지 않을 경우 검증에 실패합니다.
Script 연산자들은 실행 후 결과를 스택 상단에 추가합니다. CHECKLOCKTIMEVERIFY는 검증에 성공하면 입력으로 사용한 시간 제약을 스택 상단에 추가합니다. 이것은 이후 진행에 필요 없기 때문에 제거해야 하는데 이때 사용하는 연산자가 DROP입니다.
Relative Timelocks
Relative timelocks are implemented according to the specifications in BIP-68, Relative lock-time using consensus-enforced sequence numbers and BIP-112, CHECKSEQUENCEVERIFY.
상대적인 타임락도 지원합니다. 상대적인 타임락도 트랜잭션 수준이나 스크립트 수준으로 적용 가능합니다. 트랜잭션 수준의 타임락은 트랜잭션 입력마다 작성 가능하며, 트랜잭션 입력 필드인 nSequence로 작성됩니다.
The nSequence field was originally intended (but never properly implemented) to allow modification of transactions in the mempool.
nSequence는 메모리풀에 있는 트랜잭션을 수정할 수 있도록 하기 위한 필드입니다. 하지만 적당히 구현된 적이 없기 때문에 사용되지 않았습니다. nSequence 값이 0xFFFFFFFF이면 채굴자들은 블록에 트랜잭션을 포함합니다. 그 값보다 작으면 블록에 포함하지 않습니다. 블록에 포함되지 않기 때문에 메모리풀에 있게 남아 있게 되고, 0xFFFFFFFF 값을 가진 트랜잭션이 전달되면 대체됩니다.
nLocktime이 CHECKLOCKTIMEVERIFY의 등장으로 제대로 사용된 것처럼, nSequence도 CHECKSEQUENCEVERIFY의 등장으로 제대로 사용되게 됩니다. CHECKLOCKTIMEVERIFY는 BIP-112로 제안되었습니다.
nSequence as a consensus-enforced relative timelock
Transaction inputs with nSequence values less than 231 are interpreted as having a relative timelock.
nSequence 값이 231보다 작으면 상대 타임락을 갖는 것으로 해석됩니다. 그렇지 않으면 상대 타임락이 적용되지 않습니다.
231보다 큰 값은 CHECKLOCKTIMEVERIFY, nLocktime, Opt-In-Replace-By-Fee와 같은 미래에 개발 될 것들을 위한 영역입니다.
그림 1. BIP-68 definition of nSequence encoding (Source: BIP-68)는 nSequence의 인코딩 방법을 설명합니다.
nLocktime과 달리 타임 플래그를 사용해서 블록높이를 사용할 것인지 유닉스 타임스탬프를 사용할 것인지를 정합니다. 타임 플래그는 23번째 비트 값으로 설정됩니다. 타입 플래그 값이 설정되지 않으면 블록높이로, 설정되어 있으면 512초의 배수로 해석됩니다. 그림 7.1에서 보여주듯이 nSequence 값은 16비트 값으로 작성됩니다. 최상위 비트 값은 nSequence를 적용할 것인지에 대한 여부를 나타냅니다. 최상위 비트 값이 설정되면 231보다 크거나 같은 값이 되기 때문에 nSequence가 적용되지 않게 됩니다.
nLocktime과 마찬가지로 nSequence 값도 CHECKSEQUENCEVERIFY 입력 값보다 크거나 같아야 합니다.
Relative timelocks with CSV are especially useful when several (chained) transactions are created and signed, but not propagated, when they’re kept “off-chain.” A child transaction cannot be used until the parent transaction has been propagated, mined, and aged by the time specified in the relative timelock. One application of this use case can be seen in “Payment Channels and State Channels” and “Routed Payment Channels (Lightning Network)”.
상대 시간 제약은 서로 연결된(chained) 다수의 트랜잭션들을 생성하고 서명은 하지만 네트워크에 전파되지 않아야 할 때(off-chain) 유용하게 사용할 수 있습니다. Payment Channels, State Channels, Routed Payment Channels (Lightning Network)는 이러한 사용사례를 보여준 것이라 할 수 있습니다.
Median-Time-Past
In bitcoin there is a subtle, but very significant, difference between wall time and consensus time. Bitcoin is a decentralized network, which means that each participant has his or her own perspective of time. Events on the network do not occur instantaneously everywhere. Network latency must be factored into the perspective of each node. Eventually everything is synchronized to create a common ledger. Bitcoin reaches consensus every 10 minutes about the state of the ledger as it existed in the past.
비트코인 시스템에서 시간의 정확성에 대해서 생각할 때는 비트코인 시스템이 P2P 네트워크라는 것에 주의해야 합니다.
The timestamps set in block headers are set by the miners. There is a certain degree of latitude allowed by the consensus rules to account for differences in clock accuracy between decentralized nodes. However, this creates an unfortunate incentive for miners to lie about the time in a block so as to earn extra fees by including timelocked transactions that are not yet mature. See the following section for more information.
또한 블록 헤더의 타임스탬프는 채굴자가 작성한다는 점에도 주의해야 합니다.
To remove the incentive to lie and strengthen the security of timelocks, a BIP was proposed and activated at the same time as the BIPs for relative timelocks. This is BIP-113, which defines a new consensus measurement of time called Median-Time-Past.
BIP-113은 P2P 네트워크에서 발생할 수 밖에 없는 시간 정확성 문제를 해결하기 위해 Median-Time-Past라는 새로운 시간 측정 방법을 정의합니다.
Median-Time-Past is calculated by taking the timestamps of the last 11 blocks and finding the median. That median time then becomes consensus time and is used for all timelock calculations.
Median-Time-Past는 최근 11개의 블록들의 타임스탬프를 가지고 중간값을 구하고, 그 값을 시간 제약과 관련된 계산에 사용합니다.
블록이 하나 만들어지는데 약 10분이 걸리고 11개의 블록을 취했으니, 전체 시간은 약 2시간이 되고, 그것의 중간 값을 취하면 중간 값은 실제 시간의 약 1시간 전이 됩니다. 시간 제약을 두는 스크립트를 작성한다면 이 점에 주의해서 시간 제약 값을 작성해야 합니다.
Timelock Defense Against Fee Sniping
fee sniping은 수수료가 높은 트랜잭션들로 이전 블록을 생성하는 공격인데, 블록 보상이 있는 현재에는 현실적이지 않지만 블록 보상이 0이 되는 경우 가능할 수도 있다고 보는 공격입니다.
비트코인 코어는 이 공격을 막기 위해 nLocktime의 기본 값을 다음 블록으로 설정하도록 합니다. nSequence 값이 0xFFFFFFFF이면 nLocktime이 적용되지 않기 때문에 비트코인 코어는 nSequence의 기본 값으로 0xFFFFFFFE로 설정합니다.