직렬화
SEC(Standards for Efficient Cryptography)
- ECDSA 공개키를 직렬화하는 표준
- 비압축식
- 65바이트
- 표현
- 0x04의 1바이트 접두부로 시작
- x 좌표를 32바이트 빅엔디언 정수로 표현
- y 좌표를 32바이트 빅엔디언 정수로 표현
- 압축식
- 33바이트
- 아래 원리로, y가 짝수인지 여부만 기록하는 방식
-y % p = (p-y) % p
이기에, (x, y)가 방정식을 만족시키면 (x, p-y) 도 방정식을 만족시킴- p는 2보다 큰 소수이기에 p는 홀수임
- y는 정수이므로, y와 p-y 중 하나는 짝수이고 하나는 홀수
- 표현
- y값이 짝수면 0x02, 홀수면 0x03 접두부로 시작
- x 좌표를 32바이트 빅엔디언 정수로 표현
- x로부터 y 계산하는 법
- 알려진 v에 대해 w² = v를 만족하는 w를 구해야함.
- 페르마의 소정리 wᵖ⁻¹ % p = 1로부터 다음처럼 전개할 수 있다.
- w² = w²⋅1 = w²⋅wᵖ⁻¹ = w⁽ᵖ⁺¹⁾
- w = w⁽ᵖ⁺¹⁾/² = w²⁽ᵖ⁺¹⁾/⁴ = (w²)⁽ᵖ⁺¹⁾/⁴ = v⁽ᵖ⁺¹⁾/⁴
- secp256k1에서 사용하는 p는 p%4=3인 성질을 만족하기 때문에 (p+1)%4=0이 됨, 즉 (p+1)/4는 정수
- 따라서 유한체 w² = v를 만족하는 w 값은 v⁽ᵖ⁺¹⁾/⁴
DER(Distinguished Encoding Rules)
- 최대 72바이트
- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
- signature도 직렬화해야함. r로부터 s를 유도할 수 없기에 SER처럼 압축할 수 없음
- 표현
- 0x30 바이트로 시작
- 서명의 길이를 붙임 (보통
0x44
0x45
, 10진수로68
67
) - r 값을 시작하는 표식으로 0x02을 붙임
- 빅엔디언 정수로 r 값 표현.
- 그 결과의 첫 번째 바이트가 0x80보다 크거나 같으면 00을 앞에 붙임. 이후, 바이트 단위의 길이를 다시 앞에 붙임.
- 부호 있는 이진수에서 첫 번째 비트가 1인 경우(=0x80보다 크면) 음수를 의미하기 때문
- s 값을 시작하는 표식으로 0x02을 붙임
- 빅엔디언 정수로 s 값 표현.
- 그 결과의 첫 번째 바이트가
0x80
보다 크거나 같으면 00을 앞에 붙임. 이후, 바이트 단위의 길이를 다시 앞에 붙임.
- 그 결과의 첫 번째 바이트가
비트코인 주소 및 WIF 형식
-
주소 표현을 위해 오랫동안 Base58을 사용하고 있으나, BIP0173에서 Bech32 표준이 제안되어 일부(Segwit)에서 사용되기 시작하는 추세
-
스크립트 유형(P2PKH, P2SH, P2WPKH 등)에 따라 version 바이트(주소 타입 구분자)와 해싱 대상이 달라지지만, Base58 인코딩 과정 자체는 동일하다.
-
Base58 주소 생성 방법 (P2PKH 예시)
- version 바이트 선택: 메인넷
0x00
, 테스트넷0x6f
(주소 타입 구분) - 대상 데이터를 hash160 (SHA256 + RIPEMD160)으로 해시하여 20바이트 생성
- 이렇게 연속으로 2개의 해시함수를 적용하는 방법을 hash160이라 함
- P2PKH는 공개키를, P2SH는 redeemScript를 해시함
- version 바이트와 20바이트 해시 결과를 합침 (21바이트)
- 3의 결과를 hash256으로 해시하고 첫 4바이트를 체크섬으로 추가
- 최종 25바이트를 Base58로 부호화
- version 바이트 선택: 메인넷
-
비트코인에서 비밀키는 256 비트 숫자, 보통 전파하지 않지만 드물게 종이 지갑에서 소프트웨어 지갑으로 전송하는 경우 WIF(Wallet import format) 형식 사용
- 메인넷 비밀키는
0x80
, 테스트넷 주소는0xef
로 시작 - 비밀키를 32바이트 길이의 빅 엔디언으로 표현
- 공개키를 압축 SEC 형식으로 표현했다는 표식으로
0x01
를 붙임 - 1번을 순서대로 연결
- 4의 결과와 체크섬(4에서 얻은 결과를 hash256로 해시하고 그 결과에서 첫 4바이트를 취함)을 합친 후 Base58로 부호화
- 메인넷 비밀키는
트랜잭션
- 트랜잭션은 암호화되지 않으며 공개적으로 조회할 수 있다.
- 각 트랜잭션은 이중 SHA-256 해시를 통해 생성된 고유한 트랜잭션 ID(TXID)를 가짐
- 이전 트랜잭션의 출력(UTXO, Unspent Transaction Output)을 입력으로 참조하는 UTXO 모델을 사용한다.
트랜잭션 구성 요소
- Version: 트랜잭션 형식의 버전 번호
- Inputs: 4개의 하부 필드를 가지고 있음
- Previous Tx ID
- Previous Tx index (출력 번호)
- ScriptSig (해제 스크립트, varInt 형식)
- Sequence
- Outputs: 2개의 하부 필드를 가지고 있음
- 비트코인 금액
- ScriptPubKey (잠금 스크립트)
- Locktime: 트랜잭션의 유효 시점을 규정
Inputs
-
하나의 입력은 1개부터 2³² 개의 이전 트랜잭션 출력을 참조할 수 있으며, 모든 입력값은 완전히 할당되어야 하고, 할당되지 않은 사토시는 채굴 수수료로 지불된다.
-
시퀀스 필드는 매우 빈번한 거래를 록타임 필드와 함께 표현하기 위함이었으나, 채굴자들이 악용할 수 있는 구조적 문제로 인해 원래 의도대로 사용되지 못했다.
-
원래 의도는 같은 입력을 사용하는 여러 버전의 트랜잭션 중 가장 높은 시퀀스 번호를 가진 최신 버전을 채택하는 것이었다.
-
e.g. alice와 bob이 지불 채널을 운영
- 처음에 alice가 밥에게 1 BTC를 지불하는 트랜잭션(시퀀스 1)을 생성하고
- 나중에 합의를 변경하여 0.5 BTC만 지불하는 트랜잭션(시퀀스 2)을 생성하는 경우
- 가장 높은 시퀀스 번호인 2번 트랜잭션이 최종적으로 블록에 포함되어야 한다.
-
하지만 채굴자는 mempool에 있는 여러 버전의 트랜잭션 중 자신에게 유리한 것을 선택할 수 있는 권한이 있기 때문에, 밥으로부터 뇌물을 받거나 더 높은 수수료를 제공받아 시퀀스 1 트랜잭션(1 BTC 지불)을 의도적으로 채택할 수 있다.
-
이런 경우 앨리스는 최신 합의인 0.5 BTC가 아닌 1 BTC를 잃게 되므로, 채굴자의 선택에 따라 거래 당사자의 의도와 다른 결과가 발생할 수 있다.
-
-
따라서 현재는 시퀀스 대신 RBF(Replace-By-Fee)와
OP_CHECKSEQUENCEVERIFY
로 주로 사용되고 있다.- 아주 작은 수수료로 트랜잭션을 보낸 경우 채굴자들은 수수료가 작기 때문에 이 트랜잭션을 블록에 포함시키는 것을 주저할 수 있음. 이 경우 보낸 트랜잭션은 mempool이라고 하는 채굴 컴퓨터의 메모리에 계속해서 머무르게 됨.
- 본인의 트랜잭션을 빨리 블록에 포함시키고 싶을 때, 같은 입력에서 출력 수수료를 올려 다시 보내는 것을 RBF라 함.
- 단 대체되는 트랜잭션의 시퀀스 필드는
0xfeffffff
로 설정되어있어야 한다.
Outputs
-
비트코인 단위는 64bit 사토시 단위로 표현한다. (1 BTC = 100,000,000 satoshi)
-
UTXO(Unspent transaction output)은 아직 사용하지 않은 트랜잭션 출력으로, 네트워크상의 풀 노드들은 UTXO 집합을 항상 최신 상태로 유지해야한다.
- UTXO 집합에서 이전 트랜잭션 출력을 확인해보는 것으로 손쉡게 이중 지불을 막을 수 있다.
- 만약 새 트랜색션의 입력이 UTXO 집합에 없는 이전 트랜잭션의 출력을 가리킨다면 전파하지 않고 버릴 수 있다.
- 트랜잭션을 검증하기 위해 이전 트랜잭션 출력으로부터 금액과 잠금 스크립트를 자주 확인해야하기 때문에, 집합 내 UTXO 내용을 빠르게 확인할 수 있으면 검증도 빨라진다.
-
채굴자가 트랜잭션을 블록에 포함시킬 동기가 되는 수수료는, 단순이 입력의 합에서 출력의 합을 뺀 값이다.
- 입력은 금액 필드를 갖고 있지 않기 때문에 이전 트랜잭션의 출력에서 찾아야한다. 만양 풀 노드를 갖고 있지 않다면 믿을 수 있는 제 3자가 제공하는 풀 노드로부터 이 정보를 얻어야한다.
Locktime
- 트랜잭션 전파 후 실행을 지연시키는 방법을 제공한다. (e.g. 600,00의 록타임을 가지는 트랜잭션은 600,001 블록까지는 블록체인에 포함될 수 없다.)
- 록타임 값이 500,000,000보다 작으면 블록 높이로, 같거나 크면 유닉스 타임으로 해석한다.
- 입력에 포함된 시퀀스 값이
ffffffff
면 록타임은 무시된다. - 록타임의 주요 문제는 록타임에 도달했을 때 트랜잭션의 수신자가 트랜잭션이 유효한지 확신할 수 없다는 점이다. 시가닝 많이 지나 부도 가능성이 있는 은행 수표와 비슷하다.
- BIP65에서 도입한
OP_CHECKSEQUENCEVERIFY
는 록타임까지 출력을 사용하지 못하게 해서 이러한 상황을 방지한다.
- BIP65에서 도입한
Mempool
Mempool(Memory Pool)은 비트코인 노드가 아직 블록에 포함되지 않은 검증된 트랜잭션들을 임시로 저장하는 메모리 영역이다.
- 각 노드는 독립적인 mempool을 유지하며, 트랜잭션 전파 시점과 노드 정책에 따라 내용이 다를 수 있다.
- 트랜잭션이 mempool에 있다고 해서 반드시 블록에 포함된다는 보장은 없다.
- 채굴자는 mempool에서 수수료가 높은 트랜잭션을 우선적으로 선택하여 블록에 포함시킨다.
Mempool이 “full”인 상태
- Bitcoin Core는 기본적으로 300MB의 메모리를 mempool에 할당한다.
- Mempool이 할당된 300MB를 모두 사용하면 “full” 상태라고 한다.
- Full 상태에서는 특정 수수료율 임계값 이하의 새로운 트랜잭션을 거부한다.
- 이 임계값은 mempool에서 가장 낮은 수수료율을 가진 트랜잭션의 수수료율이다.
- 트랜잭션을 전송할 때는 이 임계값을 초과하는 수수료율을 설정해야 네트워크에 전파된다.
- Mempool 혼잡도가 높을수록 트랜잭션 확인 시간이 길어지고 필요한 수수료도 증가한다.
Memory usage와 실제 트랜잭션 크기
- Mempool의 memory usage는 실제 시스템 메모리 사용량을 나타낸다.
- 이 값은 항상 트랜잭션들의 실제 크기 합보다 크다.
- 인덱스, 포인터, 기타 오버헤드가 포함되기 때문이다.
- Bitcoin Core가 트랜잭션 저장과 처리를 위해 사용하는 추가 데이터 구조 때문이다.
- 300MB 제한을 가진 대부분의 노드(예: Raspberry Pi 노드)는 300MB를 초과하지 않는다.
- 일부 노드는 더 큰 mempool 메모리 제한을 설정하여 혼잡 시에도 낮은 수수료율의 트랜잭션을 거부하지 않고 보관할 수 있다.
Mempool 모니터링
- mempool.space와 같은 서비스는 여러 종류의 노드를 사용한다.
- Small Node: 기본 300MB 제한을 가진 노드로, 대부분의 네트워크 노드를 대표한다.
- Big Node: 훨씬 큰 mempool 메모리 제한을 가진 노드로, 트랜잭션을 거부하지 않는다.
- Small Node로부터 “Purging” 수수료율(거부 임계값)을 확인할 수 있다.
- Big Node로부터 모든 대기 중인 트랜잭션 정보를 조회할 수 있다.
트랜잭션 수수료와 선택
수수료율과 실효 수수료율
- 트랜잭션 수수료율은 일반적으로 sat/vB(satoshi per virtual byte) 단위로 표시된다.
- 실효 수수료율(effective feerate)은 CPFP(Child Pays For Parent) 관계를 고려한 수수료율이다.
- 낮은 수수료율의 부모 트랜잭션도 높은 수수료율의 자식 트랜잭션에 의해 실효 수수료율이 상승할 수 있다.
- 채굴자는 부모-자식 트랜잭션을 함께 포함시켜 평균 수수료율이 높으면 선택한다.
- 블록의 수수료율 범위는 실효 수수료율을 기준으로 표시된다.
- 예: 1 sat/vB 트랜잭션이 5-72 sat/vB 범위의 블록에 포함된 경우, 자식 트랜잭션이 부모의 실효 수수료율을 5 sat/vB 이상으로 끌어올렸기 때문이다.
CPFP (Child Pays For Parent)
- 낮은 수수료로 전송한 트랜잭션의 확인을 가속화하는 방법이다.
- 해당 트랜잭션의 출력을 입력으로 사용하는 새로운 트랜잭션(자식)을 높은 수수료율로 생성한다.
- 채굴자는 부모와 자식 트랜잭션을 하나의 패키지로 평가하여 평균 수수료율이 충분히 높으면 함께 블록에 포함시킨다.
- RBF와 달리 수신자도 CPFP를 사용할 수 있다는 장점이 있다.
Sigops (Signature Operations)
- Sigops는 비트코인 스크립트에서 서명 연산의 비용을 계산하는 방법이다.
- 다음 오피코드가 sigops에 해당한다.
OP_CHECKSIG
,OP_CHECKSIGVERIFY
: 각 1 sigopOP_CHECKMULTISIG
,OP_CHECKMULTISIGVERIFY
: 스크립트에 명시된 공개키 개수만큼 sigops
- Sigops의 위치에 따라 계산 비용이 다르다.
- 레거시 스크립트: P2PKH는 2 sigops, P2SH 내부는 redeemScript 실행 시 계산
- Witness 스크립트: Witness v0에서는 가중치 기반 계산 적용
- 합의 규칙으로 각 블록은 최대 80,000 sigops까지만 포함할 수 있다.
Adjusted vsize (조정된 가상 크기)
- 비트코인 블록은 두 가지 독립적인 제약이 있다.
- 4MWU(million weight units) 크기 제한
- 80,000 sigops 제한
- 대부분의 트랜잭션은 크기 제한을 더 많이 사용하지만, 일부 트랜잭션은 크기 대비 sigops를 불균형하게 많이 사용한다.
- Adjusted vsize는 이를 보정하기 위한 값이다.
adjusted_vsize = max(actual_vsize, sigops × 5)
- Sigops 개수에 5를 곱한 값과 실제 vsize 중 큰 값을 사용한다.
- Bitcoin Core는 블록 템플릿 생성 시 adjusted vsize 기준으로 sat/adjusted_vB 수수료율이 높은 순서로 트랜잭션을 선택한다.
- mempool.space에서도 미확인 트랜잭션의 실효 수수료율은 adjusted vsize 기준으로 표시된다.
예상 블록 수수료 범위의 겹침
- Mempool에서 표시되는 예상 블록들은 지금 채굴되면 어떤 트랜잭션이 포함될지 보여준다.
- 각 예상 블록은 실제 블록과 동일한 제약(크기 제한, sigops 제한)을 따른다.
- 수수료율이 낮은 트랜잭션이 높은 트랜잭션보다 먼저 포함되는 경우
- 블록에 남은 공간이 매우 작아서 작은 저수수료 트랜잭션만 들어갈 수 있는 경우
- Sigops가 많은 고수수료 트랜잭션으로 80,000 sigops 한도에 도달한 후, 남은 공간을 sigops가 없는 저수수료 트랜잭션으로 채우는 경우
- 극단적인 경우 여러 예상 블록이 겹치는 수수료 범위를 가질 수 있다.
- 각 블록이 고수수료-고sigops 트랜잭션과 저수수료-제로sigops 트랜잭션을 함께 포함하기 때문이다.
스크립트
비트코인 트랜잭션에서 사용되는 스크립트 시스템에 대한 자세한 내용은 비트코인 스크립트 문서를 참고한다.
비트코인 스크립트는 트랜잭션 출력의 사용 조건을 정의하는 스택 기반 프로그래밍 언어이다. 주요 스크립트 타입은 다음과 같다:
-
레거시 스크립트
- P2PK (Pay to Public Key) - 초기 비트코인에서 사용
- P2PKH (Pay to Public Key Hash) - 가장 일반적인 레거시 타입
- P2MS (Pay to Multi-Signature) - 다중 서명
- P2SH (Pay to Script Hash) - 복잡한 스크립트를 해시로 압축
-
Segwit v0 스크립트 (2017)
- P2WPKH (Pay to Witness Public Key Hash) - Segwit 단일 서명
- P2WSH (Pay to Witness Script Hash) - Segwit 복잡한 스크립트
- P2SH-P2WPKH, P2SH-P2WSH - 하위 호환 래핑
-
Taproot (Segwit v1) (2021)
- P2TR (Pay to Taproot) - Schnorr 서명과 MAST 사용
- 향상된 프라이버시와 효율성
트랜잭션 검증과 생성
트랜잭션을 수신한 후 모든 노드는 트랜잭션이 네트워크 규칙에 부합되도록 만들어졌는지 확인한다. 아래와 같은 항목을 주로 확인한다.
-
트랜잭션의 입력이 가리키는 비트코인이 존재하고 사용 가능한가? (이중 지불 방지)
- 모든 풀 노드는 자신이 관리하고 있는 UTXO 집합을 들여다보는 것으로 비트코인이 존재하고 사용 가능한지 확인할 수 있다. 단순히 트랜잭션 하나를 보는 것으론 알 수 없다.
- 풀 노드는 이중 지불 여부를 쉽게 확인할 수 있지만 라이트 노드는 이런 정보를 외부에서 가져와야한다.
-
입력 비트코인의 합이 출력 비트코인의 합보다 크거나 같은가? (코인이 생성되지 않도록 함)
- 코인을 생성할 수 있는 것은 코인 베이스 트랜잭션 뿐이다. 이외 트랜잭션에선 코인이 생성되면 안된다.
- 위에서도 언급했듯, 입력에는 명시적인 비트코인 금액 정보가 없으므로 UTXO 집합에서 찾아야한다. 라이트 노드는 풀 노드에게 이 정보를 물어야한다.
-
입력 해제 스크립트는 참조하는 출력의 잠금 스크립트를 해제하는가?
-
검증의 수학적 원리
-
z와 r을 검증 공식에 포함시키는 것은 “화살촉에 목표물을 새기는 것”과 같다.
-
z는 메시지 해시(트랜잭션 내용의 요약)이고, r은 서명의 일부이다.
-
v = r/s
계산에 r이 포함되므로, 검증 과정이 서명 자체와 수학적으로 결합된다. -
서명 생성 시:
r = (kG).x
,s = (z + re)/k
(e는 개인키, k는 임시 비밀값) -
검증 계산:
R' = uG + vP= (z/s)G + (r/s)P= (z/s)G + (r/s)(eG) // P = eG= (1/s)(z + re)G= (1/s) · s · kG // s = (z + re)/k= kG -
따라서
R'.x = (kG).x = r
이므로, 서명자가 정확히 이 메시지(z)를 이 개인키(e)로 서명했음이 증명된다.
-
-
z에 ScriptPubKey를 포함시키는 이유
- z는 특정 UTXO의 ScriptPubKey를 포함한 트랜잭션을 해시한 값이다.
- 서명은 “나는 이 특정 ScriptPubKey를 해제할 권한이 있다”는 증명이다.
- ScriptPubKey를 z에 포함시켜야 공격자가 서명을 다른 출력에 재사용하는 것을 방지할 수 있다.
- 계약서에 서명하는 것처럼, 서명할 메시지에 계약 내용(ScriptPubKey)이 포함되어야 “이 서명이 정확히 이 계약에 대한 것”임을 보장한다.
-
이 과정을 통해 서명자가 개인키를 소유하고 있음을 증명하면서도, 개인키 자체는 노출하지 않는다.
-
e.g. ECDSA 서명 검증 과정
-
서명
(r, s)
, 공개키P
, 메시지 해시z
를 준비한다.- z 생성 과정 (순환 참조 문제 해결)
- 서명은 ScriptSig에 포함되지만, 서명을 만들 때는 아직 서명이 존재하지 않음
- 트랜잭션을 다음과 같이 수정하여 서명할 메시지를 생성함
- 모든 입력의 ScriptSig를 빈 스크립트로 교체
- 현재 서명 중인 입력의 ScriptSig만 해당 입력이 참조하는 이전 출력의 ScriptPubKey로 교체
- 수정된 트랜잭션을 이중 SHA-256 해시하여 z 생성
- 예: 2개 입력이 있는 트랜잭션에서 Input 0 서명 시
- Input 0: ScriptSig =
<이전 출력 0의 ScriptPubKey>
- Input 1: ScriptSig =
(empty)
- Input 0: ScriptSig =
- 각 입력은 자신이 참조하는 UTXO의 ScriptPubKey에 대해서만 서명하므로, 입력들은 독립적으로 서명 가능
- r과 s는 DER 형식으로 직렬화된 서명에서 파싱한다.
- P는 SEC 형식으로 직렬화된 공개키를 파싱한다.
- z 생성 과정 (순환 참조 문제 해결)
-
u = z/s mod n
을 계산한다. (n은 secp256k1 곡선의 위수) -
v = r/s mod n
을 계산한다. -
R' = uG + vP
를 계산한다. (G는 생성자점, P는 공개키)- 타원곡선 점 덧셈 연산을 수행하여 새로운 점 R’을 얻는다.
-
r == R'.x mod n
인지 확인한다.- 일치하면 스택에 1을 푸시하고, 일치하지 않으면 0을 푸시한다.
R'
의 x 좌표와 서명의 r 값이 일치하면 서명이 유효하다.
-
-
- https://teachbitcoin.io/presentations/transaction_sighash.html
- https://developer.bitcoin.org/devguide/transactions.html
블록
-
비트코인에서 블록은 트랜잭션 순서를 정하는 방법이다. 트랜잭션 순서를 정한다면 같은 비트코인을 사용하려는 트랜잭션 중 첫 번째 것만 유효하고 나머지는 무효로 간주하여 이중 지불을 방지할 수 있다.
-
이를 위해선 네트워크의 모든 노드가 시시각각 발생하는 모든 트랜잭션의 순서에 합의해야한다. 비트코인은 이를 위해 트랜잭션을 10분마다 한 번씩 정산한다.
-
코인 베이스 트랜잭션
-
코인베이스 트랜잭션은 블록마다 들어가는 첫 번째 트랜잭션이며 비트코인을 발행하는 유일한 트랜잭션이다.
-
정확히 하나의 입력을 갖고 이전 트랜잭션 해시값으로 32바이트의 0을, 이전 트랜잭션 출력 번호로
ffffffff
를 갖는 경우 코인베이스 트랜잭션이다.def is_coinbase(self):'''Returns whether this transaction is a coinbase transaction or not'''# check that there is exactly 1 inputif len(self.tx_ins) != 1:return False# grab the first inputfirst_input = self.tx_ins[0]# check that first input prev_tx is b'\x00' * 32 bytesif first_input.prev_tx != b'\x00' * 32:return False# check that first input prev_index is 0xffffffffif first_input.prev_index != 0xffffffff:return Falsereturn True
-
-
보통 P2PKH 잠금 스크립트로 채굴자가 지정한 주소에 블록 보상으로 주어지는 비트코인과 블록 내 모든 트랜잭션 수수료를 잠가 놓는다.
-
이러한 방법으로 채굴자의 채굴 활동은 코인베이스 트랜잭션으로 보상된다.
-
바이트 단위로 똑같은 코인베이스 트랜잭션을 그 트랜잭션 ID 역시 동일하다. 트랜잭션 ID의 중복을 방지하기 위해, Gavin Andresen은 채굴하고 있는 블록의 높이를 코인베이스 해제 스크립트의 첫 원소로 한다는 soft-fork 규정을 BIP0034로 제안했다.
- 여기서 fork는 비트코인 네트워크를 구선하는 채굴 노드의 소프트웨어를 새 버전으로 업데이트하는 것을 의비한다. 이 때 soft-fork는 예전 버전 노드와 최신 버전 노드가 혼재되어있어도 네트워크가 멈추지 않고 돌아가도록 하는 것, hark-fork는 양립할 수 없는 방식의 소프트웨어 업데이트이다. 하드포크는 주로 업데이트보다는 새로운 블록체인을 분기시키는 경우에 사용된다.
- 이러한 인위적 포크 외에 일상적인 상황에서 2개 이상의 노드가 거의 동시에 작업 증명을 찾았을 때도 포크가 발생했다고 말한다. 이 경우는 일시적으로 노드들이 가진 블록체인의 마지막 블록이 동기되지 못하는 경우로 시간이 지날수록 우세한 블록이 생기면서 이러한 상황이 해소된다.
-
-
빈 블록 (Empty Block)
- 빈 블록은 코인베이스 트랜잭션만 포함하고 다른 트랜잭션을 포함하지 않는 블록이다.
- 새로운 블록이 발견되면 채굴 풀은 새 블록을 완전히 검증하기 전에, 심지어 새 블록을 수신하기도 전에 채굴자들에게 새로운 블록 템플릿을 전송하는 경우가 많다.
- 이 시간 동안 채굴 풀은 어떤 트랜잭션이 새 블록에 포함되었는지 확실히 알 수 없어 트랜잭션 충돌을 피하기 위해 다음 블록의 트랜잭션을 선택할 수 없다.
- 따라서 이 짧은 기간 동안에는 빈 블록 템플릿으로 채굴을 진행한다.
- 빈 블록은 추가 트랜잭션을 블록체인에 포함시키지는 않지만, 이미 체인에 있는 트랜잭션들의 전체 보안에 기여한다.
블록 해시와 작업증명
-
Tx와 마찬가치로 해시 값이 ID로 쓰인다.
-
블록 해시값은 앞에 많은 0 자릿수를 가진다. proof of work을 찾기 위해 이 값이 기준치 이하이도록 하는 조건이 붙기 때문이다.
- 작업증명을 찾는 것은 특정한 조건을 만족하는 매우 희소한 숫자를 찾는 것이다. 채굴자는 많은 숫자를 발생시켜 특정한 조건을 만족하는지 조사해야한다.
- sha256 해시 값은 균일하게 분산된 값을 생성하고, 처음 비트가 0으로 시작하는 블록을 생성하기 위한 확률은 매우 낮다.
-
채굴 난이도를 높이기 위해, 비트코인 네트워크에선 블록을 등록하기 위해 이 해시값이 특정 기준치 이하의 값이도록 강제한다.
-
채굴자는 논스 값을 원하는대로 변경하여 블록 헤더의 해시값을 찾는다.
-
난이도
-
난이도는 서로 다른 난이도간 쉽게 비교하도록 목푯값에 반비례하게 정의된다.
difficulty = fxfff * 256^0x1d-2 / target -
비트코인은 평균 10분마다 블록을 생성하도록 설계되었다.
- 네트워크 해시파워 변화에 대응하여 2016블록(약 2주)마다 난이도를 자동으로 조정한다.
-
조정 시점: 블록 높이가 2016의 배수일 때 (블록 #2016, #4032, #6048, …)
-
난이도 조정 계산식
new_target = previews_target × (최근 2016블록 소요 시간 / 20160분)- 20160분 = 2016블록 × 10분 (이상적인 소요 시간)
- 최근 2016블록 소요 시간 = (마지막 블록 타임스탬프) - (2016블록 전 타임스탬프)
- 조정 제한: 최대 4배 증가 또는 1/4 감소로 제한됨 (급격한 변화 방지)
- 실제 소요 시간이 8주(80640분)를 초과하면 8주로 간주
- 실제 소요 시간이 3.5일(5040분) 미만이면 3.5일로 간주
-
예시
- 최근 2016블록이 1주(10080분)에 생성됨 → 너무 빠른 상태
- 새 목표값 = 현재 목표값 × (10080 / 20160) = 현재 목표값 × 0.5
- 목표값이 절반으로 줄어들어 난이도가 2배 증가
- 결과: 다음 2016블록은 다시 2주에 가깝게 생성됨
-
Merkle Tree
-
머클트리(Merkle Tree)는 해시 트리의 일종으로, 블록에 포함된 모든 트랜잭션을 효율적으로 요약하고 검증하기 위한 자료구조이다.
- 세그먼트 트리에서, 합 대신 각 데이터를 연결해 해시한 데이터를 저장하는 버전이다.
-
머클트리를 사용하면 특정 트랜잭션의 블록 포함 여부(proof of inclusion)를 알아내는 데 유용하다. 이렇게 알아내는 과정을 단순 지급 검증이라 부른다(Simplified Payment Verifycation).
-
구조
-
노드 생성
- 트랜잭션 ID(TXID)가 리프 노드의 값이 된다.
- 블록의 각 트랜잭션을 이중 SHA-256 해시하여 리프 노드를 생성한다.
-
부모 노드 계산
- 인접한 두 개의 노드를 연결(concatenate)하여 이중 SHA-256 해시한다.
parent_hash = SHA-256(SHA-256(left_child + right_child))
- 이 과정을 트리의 맨 위(루트)에 도달할 때까지 반복한다.
-
홀수 개 노드 처리
- 각 레벨에서 노드 개수가 홀수인 경우, 마지막 노드를 복제하여 짝을 맞춘다.
- 예: 트랜잭션이 5개면 5번째 트랜잭션 해시를 복제하여 6개로 만든다.
-
루트
- 트리의 최상위 노드가 머클 루트(Merkle Root)이다.
- 이 값이 블록 헤더의
Merkle Root
필드에 저장된다. - 32바이트(256비트) 해시값 하나로 블록 내 모든 트랜잭션을 대표한다.
Merkle Root|+-------+-------+| |Hash01 Hash23| |+--+--+ +--+--+| | | |Hash0 Hash1 Hash2 Hash3| | | |Tx0 Tx1 Tx2 Tx3 -
-
n개의 트랜잭션이 있을 때, 검증에 필요한 해시값은 log₂(n)개이다. 검증 과정은 다음과 같다.
- 검증하려는 트랜잭션의 해시값을 계산한다.
- 머클 경로를 따라 형제 노드와 결합하여 상위 노드를 계산한다.
- 최종적으로 계산된 루트 해시가 블록 헤더의 머클 루트와 일치하는지 확인한다.
-
SPV(Simplified Payment Verification)와 PV(Payment Verification)
- 풀 노드는 블록에 대한 제한된 정보를 보내고, 라이트노드는 이로부터 머클 루트를 계산해 블록 헤더의 루트와 계산된 루트의 일치 여부를 확인할 수 있다.
- 라이트 노드의 보안(SPV)은 네트워크에서 정직한 노드의 비율과 작업증명 생성 비용이 높을 수록 증가한다. 반대로, 불손한 노드가 많은 경우 라이트 노드가 완전히 속을 수도 있다.
- 100 비트코인 이상의 큰 금액을 움직이는 트랜잭션의 경우 풀 노드가 라이트 노드를 속이고자 하는 경제적 인센티브를 가질 수 있다. 따라서 그러한 트랜잭션은 안전하게 풀 노드에서 검증되어야 한다.
- 풀 노드의 지급 검증과 라이트노드의 단순 지급 검증의 가장 큰 차이는 이중 지불 검증 여부이다.
- 라이트 노드는 UTXO 데이터베이스가 없고 블록체인의 헤더만 갖고 있어서 UTXO 확인을 통한 이중 지불 검증이 불가능하다.
getutxo
커맨드로 UTXO를 풀 노드에게 물어볼 순 있지만 그 응답을 일반적으로 신뢰할 수 없다. (BIP-0064)- 트랜잭션이 블록 안에 있고 블록이 체인 상에 충분히 깊숙이 들어가 있다면 라이트 노드는 이런 상황이 되기 위해 풀 노드가 검증에 들인 에너지가 크다고 판단하여 이중 지불 없이 확정된 트랜잭션이라고 간주한다. 즉 풀 노드의 검증 노력을 믿는다.
-
단순 지급 검증은 유용하지만 중요한 단점이 있어 대부분의 라이트 노드 지갑에선 단순 지급 검증을 사용하지 않는다. 대신 지갑 업체 자체 서버의 데이터를 활용한다.
- 단순 지급 검증의 가장 큰 단점은 풀 노드가 라이트 노드의 관심 트랜잭션을 알게 된다느 ㄴ것이다. 즉, 트랜잭션에 들어있는 사용자의 계정 정보가 알려지게 된다.
- 라이트 노드는 자시닝 관심 있는 트랜잭션을 풀 노드에 알려줄 때 블룸 필터를 사용하여 이러한 트랜잭션 공개 문제를 완화시킬 수 있다.
bloom filter
-
라이트 노드가 풀 노드에게 관심 트랜잭션 집합만 알려주는 것이 아니라 이를 포함한 더 많은 트랜잭션이 있는 superset(상위집합)을 알려줌으로써 해결할 수 있다.
-
bloom filter는 superset에 포함되는 트랜잭션을 선정하는 필터이다.
- 예를 들어 50개의 트랜잭션이 있으면, 라이트 노드는 5개씩 10 그룹으로 만들고 그 중 관심 트랜잭션이 포함된 한 그룹을 보낸다. 이러한 트랜잭션의 그루핑은 동일한 입력에 대해서 매번 동일해야한다.
- 이를 위해 해시 함수와 나머지 함수를 이용한다. (해시 후,
해시결과 mod m
번째 그룹 선택) - 블룸 필터는 다음 항목들을 검사한다:
- 트랜잭션 해시 (TXID)
- 출력 스크립트의 데이터 요소들
- 입력 outpoint (previous tx hash + index)
- 입력 스크립트의 데이터 요소들
-
그룹이 작고 많아질 수록, 오탐률이 낮아지고 비트 필드 크기가 커져 전송 용량에 부담을 줄 수 있다. 반대로 그룹이 크고 적어질 수록 오탐률이 높아지고 전송 용량이 작아진다.
- 원하는 오탐률을 맞추기 위해 그룹 갯수(필터 크기)와 해시 함수 갯수를 조정한다. 해시 함수 갯수를 조정하는 것은, 필터에서 무작위 비트를 1로 설정하는 것과 비슷한 역할을 한다.
-
적절한 오탐률을 맞추기 위해서, 아래 식으로 계산할 수 있다.
-
필터 크기
S = -1 / (ln(2))² × N × ln(P) / 8S
: 필터 크기 (바이트)N
: 필터에 추가할 원소 개수P
: 원하는 거짓 긍정 확률
-
해시 함수
K = S × 8 / N × ln(2)K
: 해시 함수 개수
-
예시: N=10개 원소, P=0.01 (1% 거짓 긍정률)
- S ≈ 12바이트
- K ≈ 7개 해시 함수
-
-
구조 및 파라미터 (BIP0037)
- 비트 필드 (
Filter Byte Array
)- 최대 크기: 36,000바이트
- 관심 대상을 표시하는 비트맵
- 해시 함수 개수 (
nHashFuncs
)- 최대: 50개
- 여러 해시 함수를 사용하여 거짓 긍정률을 조절한다.
- Tweak 파라미터 (
nTweak
)- 해시 함수의 시드값
- 같은 필터라도 다른 tweak 값으로 다른 결과를 생성하여 프라이버시를 향상시킨다.
- 업데이트 플래그 (
nFlags
)BLOOM_UPDATE_NONE
: 필터를 업데이트하지 않음BLOOM_UPDATE_ALL
: 매칭된 scriptPubKey의 outpoint를 필터에 추가BLOOM_UPDATE_P2PUBKEY_ONLY
: P2PK나 P2MS 형식일 때만 선택적으로 추가
- 비트 필드 (
-
암호학적으로 안전하지 않지만, 출력을 빠르게 계산할 수 있는 Murmur3 해시를 사용한다.
-
해시 함수는 다음 공식으로 생성한다
hash(i) = MurmurHash3(nHashNum * 0xFBA4C795 + nTweak, data) mod (filterSize * 8)i
: 해시 함수 인덱스 (0 ~ nHashFuncs-1)0xFBA4C795
: 큰 소수 (해시 함수 다양화)data
: 검사할 데이터 (트랜잭션 해시, 스크립트 등)
-
라이트 노드가 블룸 필터를 생성하면 이를 풀 노드에게 알려줘야한다. 그러면 풀 노드는 포함증명을 보낼 때 이를 활용한다. 블룸필터에 관련된 command는 아래와 같은 것들이 있다.
-
filterload
: 블룸 필터 설정- 라이트 노드가 풀 노드에게 필터를 전송한다.
- 이후 풀 노드는 필터에 매칭되는 트랜잭션과 블록만 전송한다.
-
filteradd
: 필터에 원소 추가- 기존 필터에 새로운 원소를 동적으로 추가한다.
- 새 주소를 감시하고 싶을 때 사용한다.
-
filterclear
: 필터 제거- 현재 설정된 블룸 필터를 제거한다.
- 이후 풀 노드는 필터링 없이 모든 트랜잭션을 전송한다.
-
merkleblock
: 머클 경로와 함께 블록 전송- 블록 헤더 + 매칭된 트랜잭션들의 머클 경로를 포함한다.
- 라이트 노드는 이를 통해 트랜잭션이 블록에 포함되었음을 검증할 수 있다.
-
블록 감사와 건강도
블록 감사 (Block Audit)
-
블록 감사는 mempool.space와 같은 서비스에서 예상 블록과 실제 채굴된 블록을 시각적으로 비교하는 기능이다.
-
채굴자가 의도적으로 트랜잭션을 포함하거나 제외했는지 추론하는 것이 목적이다.
-
작동 방식
- mempool.space는 mempool을 모니터링하고 Bitcoin Core의 트랜잭션 선택 알고리즘을 재구현하여 예상 블록을 생성한다.
- 이 알고리즘은 2초마다 실행되어 실시간에 가까운 예측을 제공한다.
- 새 블록이 채굴되는 순간, 해당 블록 높이에 대한 예상 블록 스냅샷을 저장한다.
- 이후 예상 블록과 실제 블록을 비교하여 차이를 분석한다.
-
트랜잭션 분류 (색상 코드)
-
Added (파란색): 예상 블록에 없고 실제 블록에 있는 트랜잭션
- 예상 수수료율 범위에서 크게 벗어난 경우 (채굴자가 의도적으로 우선순위를 부여했을 가능성)
- mempool에 전혀 없는 경우 (채굴자가 대역 외 방식으로 수락했을 가능성)
- 블록 건강도에 부정적 영향을 주지 않는다.
-
Recently broadcasted (진한 분홍색): 예상 블록에 있지만 실제 블록에 없으며, 블록 채굴 3분 이내에 처음 확인된 트랜잭션
- 네트워크 지연으로 인해 채굴자 노드가 아직 수신하지 못했을 가능성이 있다.
- 블록 건강도에 부정적 영향을 주지 않는다.
-
Marginal fee (어두운 색): 예상 수수료율 범위의 하단에 있으며 예상 블록이나 실제 블록 중 하나에서 누락된 트랜잭션
- 추가된 트랜잭션에 의해 밀려났거나, 동일한 낮은 수수료율의 다른 트랜잭션으로 대체되었을 수 있다.
- 블록 건강도에 부정적 영향을 주지 않는다.
-
Removed (밝은 분홍색): 예상 블록에 있지만 실제 블록에 없으며, recently-broadcasted도 marginal-fee도 아닌 트랜잭션
- mempool에 충분히 오래 있어 널리 전파되었고, 블록에 포함될 것으로 예상되는 수수료율을 가진 트랜잭션이다.
- 의도적으로 제외되었을 가능성이 있다.
- 블록 건강도에 부정적 영향을 준다.
-
-
이 기능은 리소스 사용량과 가용성 요구사항으로 인해 공식 mempool.space 인스턴스에서만 지원된다.
블록 건강도 (Block Health)
-
블록 건강도는 의도적으로 제외된 것으로 보이는 트랜잭션이 얼마나 있는지 측정하는 지표이다.
-
의도적으로 제외된 트랜잭션이 없는 블록은 100% 건강도를 가진다.
-
1개 이상의 트랜잭션이 의도적으로 제외된 것으로 보이는 블록은 100% 미만의 건강도를 가진다.
-
계산 방식
sexpected
: 예상 블록의 모든 트랜잭션 집합sactual
: 실제 블록의 모든 트랜잭션 집합n
:sexpected
와sactual
모두에 있는 트랜잭션 개수r
: 의도적으로 제외된 것으로 추론되는 트랜잭션 개수 (Removed 분류)- 블록 건강도 =
n / (n + r)
-
계산 시 양쪽 집합에 모두 있는 트랜잭션만 사용하는 이유
- Recently-broadcast 트랜잭션은 채굴자가 단순히 수신하지 못했을 가능성이 있다.
- 특정 낮은 수수료 트랜잭션은 채굴자가 더 수익성 높은 대역 외 트랜잭션으로 교체했을 수 있다.
- 블록 건강도는 예상 블록과 실제 블록이 얼마나 유사한지를 측정하는 것이 아니라, 의도적 제외가 있었는지를 측정하는 것이다.
- 따라서 실제 블록이 예상 블록과 크게 다르더라도, 의도적으로 제외된 트랜잭션이 없으면 높은 건강도를 가질 수 있다.
-
이 기능도 리소스 사용량과 가용성 요구사항으로 인해 공식 mempool.space 인스턴스에서만 지원된다.
블록 헤더 구성
- 블록 헤더는 아래처럼 구성된다.
- Version
- Previous block
- Merkel root
- Timestamp
- Bits
- Nonce
Version
-
블록 버전을 나타내는 4바이트 리틀 엔디언 정수이다.
-
블록이 따르는 검증 규칙을 나타내며, 소프트포크 신호용으로도 사용된다.
-
노드는 Version 값으로 블록이 따르는 합의 규칙을 판단한다.
-
알 수 없는 버전의 블록도 대부분 허용하지만, 특정 규칙 위반 시 거부할 수 있다.
-
버전별 지원 규칙
- Version 1: 초기 비트코인 블록 (2009년)
- Version 2: BIP34 활성화 - 블록 높이를 코인베이스에 포함 (2012년)
- Version 3: BIP66 활성화 - strict DER 서명 검증 (2015년)
- Version 4: BIP65 활성화 -
OP_CHECKLOCKTIMEVERIFY
추가 (2015년)
-
여러 소프트포크를 동시에 신호할 수 있도록 BIP9 버전 비트 시그널링이 도입되었다.
- 구조
- 상위 3비트:
001
고정 (BIP9 신호임을 표시) - 나머지 29비트: 소프트포크 제안 신호용으로 사용
- 상위 3비트:
- 작동 방식
- 채굴자가 특정 비트를 1로 설정하여 해당 소프트포크 지지를 표명한다.
- 2016블록(약 2주) 기간 동안 95% 이상 신호 시 소프트포크가 활성화된다.
- 예시
- Segwit: 비트 1 사용 (
0x20000002
) - Taproot: 비트 2 사용 (
0x20000004
) 0x20000000
: BIP9 형식 기본값 (모든 신호 비트 0)
- Segwit: 비트 1 사용 (
- 구조
Previous block
- 이전 블록의 해시 값이다.
Merkel root
- 단순 지급 검증(SPV)에서 활용되는 머클 트리의 루트 해시값이다.
Timestamp
-
유닉스 형식으로 표현된 4바이트 값이다. (2106년까지 사용)
-
크게 두 용도로 사용된다
- 블록에 포함된 트랜잭션의 록타임이 유닉스 형식 시간으로 표현되었을 때 그 트랜잭션이 활성화되는 시점을 알아내기 위한 비교 기준 (BIP0113에 따라 지난 11 블록의 타임스탬프 중 중앙값과 비교, 많은 수수료를 얻으려고 자신이 생성하는 블록의 타임스탬프를 실제보다 증가시키지 않게 하기 위함)
- 2016개 블록마다 비트값/목푯값/난이도를 재계산하는 과정
-
타임스탬프가 항상 증가하지 않는 이유
- 블록 검증 규칙은 블록의 타임스탬프가 이전 블록보다 엄격히 더 최신이어야 한다고 요구하지 않는다.
- 중앙 기관이 없는 분산 시스템에서는 정확한 시간을 알 수 없다.
- 대신 비트코인 프로토콜은 블록 타임스탬프가 특정 요구사항을 충족하도록 요구한다.
- 블록 타임스탬프는 이전 12개 블록 타임스탬프의 중앙값보다 오래되어서는 안 된다. (Median Time Past, MTP 규칙)
- 블록 타임스탬프는 네트워크 조정 시간보다 2시간 이상 미래여서는 안 된다.
- 이로 인해 타임스탬프는 약 1시간 정도의 정확도를 가지며, 때때로 순서가 바뀌어 보일 수 있다.
- 예: 블록 N의 타임스탬프가 블록 N+1의 타임스탬프보다 늦은 경우가 발생할 수 있다.
Bits
-
목표값(target)을 압축 표현한 4바이트 필드이다.
-
bits는 과학적 표기법과 유사하게 목표값을 인코딩한다.
- 첫 1바이트(exponent): 지수를 나타냄
- 나머지 3바이트(coefficient): 계수를 나타냄
- 변환 공식:
target = coefficient × 256^(exponent - 3)
-
예시: bits가
0x1d00ffff
인 경우- exponent =
0x1d
(29) - coefficient =
0x00ffff
- target =
0x00ffff × 256^(29-3) = 0x00ffff × 256^26
- 이는 약
0x00ffff0000000000000000000000000000000000000000000000000000000000
- exponent =
-
이 압축 표현을 통해 256비트 target 값을 32비트로 저장할 수 있다.
-
블록 해시값이 이 target 값보다 작거나 같아야 블록이 유효하다.
Nonce
- Number used only ONCE의 줄임말로 작업증명을 위해 채굴자가 변경하는 값이다.
네트워크
-
비트코인 네트워크의 모든 노드는 자신이 알고 있는 트랜잭션, 블록, 인접 노드 리스트를 전파하기에 broadcast, gossip 네트워크이다.
-
메시지 구조
Field Size Description Data type Comments 4 magic uint32_t
메시지의 출처 네트워크를 나타내는 매직 값. 스트림 상태를 알 수 없을 때 다음 메시지를 찾는 데 사용됨 12 command char[12]
패킷 내용을 식별하는 ASCII 문자열, NULL로 패딩됨 (NULL이 아닌 패딩은 패킷 거부됨) 4 length uint32_t
payload의 바이트 단위 길이 4 checksum uint32_t
sha256(sha256(payload))의 첫 4바이트 ? payload uchar[]
실제 데이터 -
magic: 메인넷은
f9beb4d9
, 테스트넷은0b110907
-
command: 모든 커맨드는 문서에서 볼 수 있다.
-
checksum: payload를 이중 SHA-256 해시한 값의 첫 4바이트로, 데이터 무결성을 검증한다.
-
핸드셰이킹
-
version 메시지 교환
- 노드 A가 노드 B에 TCP 연결을 시작하고
version
메시지를 전송한다. version
메시지에는 다음 정보가 포함된다- 프로토콜 버전 (e.g. 70015)
- 노드가 지원하는 서비스 (services 비트필드)
- 타임스탬프
- 수신자 주소 (IP:포트)
- 발신자 주소 (IP:포트)
- nonce (연결 고유 식별자, 자기 자신에게 연결하는 것을 방지)
- user agent (노드 소프트웨어 식별, e.g. “/Satoshi:0.21.0/“)
- 최신 블록 높이 (start_height)
- relay 플래그 (트랜잭션 중계 여부)
- 노드 B도 자신의
version
메시지를 노드 A에게 전송한다.
- 노드 A가 노드 B에 TCP 연결을 시작하고
-
verack 메시지 교환
- 각 노드는 상대방의
version
메시지를 받으면verack
(version acknowledgement) 메시지를 전송한다. verack
메시지는 payload가 없는 간단한 확인 메시지이다.- 양쪽 노드가 모두
verack
를 받으면 핸드셰이킹이 완료된다.
- 각 노드는 상대방의
-
연결 확립
- 핸드셰이킹이 완료되면 노드들은 트랜잭션, 블록, 인벤토리 등의 메시지를 교환할 수 있다.
- 이후
getaddr
,getdata
,inv
,tx
,block
등의 커맨드로 네트워크 통신이 진행된다.
-
관련 테스트 및 탐방 사이트
- https://cryptolinks.com/
- testnet faucet에서 테스트넷용 코인을 무료로 얻을 수 있음. faucet 목록 확인
- 현재 동작중인 풀 노드의 IP 주소와 port 번호 검색: https://bitnodes.io/nodes/
- 블록 탐색기
참고
- 밑부터 시작하는 비트코인 (Programming Bitcoin by Jimmi Song, O’Reilly)
- https://github.com/bitcoin/bips/tree/master
- https://wiki.bitcoinsv.io/index.php/Bitcoin_Transactions
- https://wiki.bitcoinsv.io/index.php/VarInt
- https://www.btcschools.net/bitcoin/bitcoin_tool_hash160.php
- https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
- https://en.bitcoin.it/wiki/Protocol_documentation
- https://en.bitcoin.it/wiki/Wallet_import_format