밑바닥부터 시작하는 비트코인 - 중간정리
시리즈
2023.08.25 - [블록체인/비트코인] - 밑바닥부터 시작하는 비트코인 - 1장 유한체
2023.08.27 - [블록체인/비트코인] - 밑바닥부터 시작하는 비트코인 - 2장 타원곡선
2023.08.30 - [블록체인/비트코인] - 밑바닥부터 시작하는 비트코인 - 3장 타원곡선 암호
2023.09.02 - [블록체인/비트코인] - 밑바닥부터 시작하는 비트코인 - 4장 직렬화
2023.09.05 - [블록체인/비트코인] - 밑바닥부터 시작하는 비트코인 - 5장 트랜잭션
2023.09.11 - [블록체인/비트코인] - 밑바닥부터 시작하는 비트코인 - 6장 스크립트
2023.09.16 - [블록체인/비트코인] - 밑바닥부터 시작하는 비트코인 - 7장 트랜잭션 검증과 생성
중간정리
1장 유한체부터 시작해서 7장 트랜잭션 검증과 생성까지 진행하면서 각 요소들이 어떻게 연관되어 있는지 한눈에 알아보기 쉽게 그림으로 정리해 보겠습니다.

1장 유한체
유한체는 타원곡선 암호를 파악하기 위해 필요한 요소입니다. 유한체를 이해하기 위해 유한체의 정의와 나머지 연산 그리고 유한체 요소의 연산에 대해 알아보았습니다. 유한체의 위수 p는 반드시 소수이거나 소수의 거듭제곱이어야 하며, 이 점을 페르마의 소정리에 적용하여 유한체 원소의 나눗셈을 곱셈으로 치환하여 수행할 수 있습니다.

2장 타원곡선
타원곡선은 유한체와 마찬가지로 타원곡선 암호를 이해하기 위해 필요한 요소입니다. 타원곡선은 y2 = x3 + ax + b 식으로 표현하며 x축에 대칭인 모양입니다. 비트코인에서 사용되는 타원곡선은 secp256k1이라고 하며 방정식은 y2 = x3 +7입니다.
그리고 타원곡선의 점 덧셈과 성질 대해 알아보았습니다. 점 덧셈은 비선형 연산으로 결과를 쉽게 예측할 수 없다는 성질을 가지고 있습니다. 이 성질이 타원곡선 암호에서 중요한 역할을 합니다. 점 덧셈의 성질에 대해서는 덧셈의 항등원인 무한원점이 존재합니다.

3장 타원곡선 암호
타원곡선 암호는 앞서 배운 유한체와 타원곡선의 개념을 활용하여 비트코인의 메시지 서명과 검증에 필요한 동작을 수행하기 위해 필요한 요소입니다. 타원곡선 암호는 유한체에서 정의된 타원곡선을 사용합니다. 유한체에서 정의된 타원곡선은 유한체와 타원곡선의 특성을 가지고 있으면서도, 실수체에서 정의된 타원곡선이 매끄럽게 연결되어 있는 것과 달리 점들이 매우 산재되어 있는 형태입니다.
유한체에서 정의된 타원곡선은 곡선 상의 두 점의 덧셈이 가능합니다. 또한 어떤 한 점을 그 자신과 더할 수도 있습니다. 점 덧셈은 결합법칙이 성립하므로 점에 대한 스칼라 곱셈이 가능합니다. 스칼라 곱셈을 실행하는 것은 어렵지 않지만, 그 반대로 스칼라 곱셈의 결과를 통해 어떤 값을 사용해 이러한 결과가 나왔는지 역산하는 것은 굉장히 어렵습니다. 이를 이산로그 문제라고 하며 이 성질이 타원곡선 암호의 원리입니다.
스칼라 곱셈의 또 다른 성질로는, 어떤 점 G에 스칼라 값을 계속 증가시키면서 곱하다 보면 무한원점에 도달한다는 것입니다. 점 G와 무한원점에 도달할 때까지의 일련의 점들을 하나의 군이라고 하며, 무한원점을 구하기 위해 필요한 스칼라 값은 유한한 특정 값이므로 이를 유한군 또는 유한순환군이라고 합니다.

이제 공개키 암호를 생성할 수 있습니다. 공개키 암호 연산에 필요한 비대칭 방적식은 eG = P로 G는 생성점, e는 비밀키 그리고 P는 공개키를 나타냅니다. 여기서 e는 256비트 숫자로, 생성점 G에 곱하는 스칼라 값으로 사용되어 점 P를 생성할 수 있습니다. 그러나 앞서 언급한 대로 P와 G를 가지고 e를 역산하는 것은 이산 로그 문제로 굉장히 어렵습니다.
공개키와 비밀키를 만들었다면 이제 디지털 서명을 생성할 수 있습니다. 디지털 서명은 비밀키 소유자가 비밀키를 공개하지 않고도 비밀키를 소유하고 있다는 것을 증명하기 위해 사용합니다.
서명 생성 절차와 검증 절차는 이전 게시물을 참고해 주세요.

4장 직렬화
직렬화는 네트워크 사용 비용과 영구 저장매체에 보관하는 비용을 최소화하기 위해 필요한 요소입니다. 직렬화할 때 숫자를 바이트 스트림으로 변환하는 두 가지 방법으로 빅엔디언과 리틀엔디언이 있습니다. 빅엔디언은 일반적인 수를 표기하는 것과 마찬가지로 가장 큰 자리의 바이트를 왼쪽에서 오른쪽으로 채워 넣는 방식이고 리틀엔디언은 이와 반대로 오른쪽에서 왼쪽으로 채워 넣는 방식입니다. 비트코인은 두 가지를 모두 사용하고 있으나, 어디서 무엇을 사용해야 하는지에 대한 명확한 규칙은 없습니다.
SEC는 공개키를 직렬화하는 표준안입니다. SEC 형식에는 비압축식과 압축식 두 가지가 있습니다. 비압축식은 접두부 + x좌표 + y좌표로 총 65바이트 크기를 사용합니다. 반면, 압축식은 y좌표를 사용하지 않아 총 33바이트 크기를 사용합니다. 그런데 타원곡선은 동일한 x좌표를 가지는 점이 y와 p-y 2개가 존재합니다. 여기서 p가 소수이므로 두 값 중 한쪽은 홀수이고 한쪽은 짝수여야 합니다. 따라서 압축식은 접두부에 y가 홀수인지 짝수인지 여부를 표시해 줍니다.
DER은 서명을 직렬화하는 표준안입니다. 서명 (r, s)에서 s값을 r로부터 온전히 도출할 수 없으므로 SEC 형식을 사용할 수 없습니다. DER 서명 형식은 최대 72바이트까지 길어질 수 있습니다.
비트코인 주소는 비트코인을 주고 받기 위해 필요한 계좌번호 같은 것입니다. 비트코인 주소는 공개키를 사용합니다. 그러나 SEC 형식을 사용하기엔 길이가 너무 길고 가독성이 떨어져서 Base58 부호화를 사용합니다. Base58 부호화는 숫자 0과 알파벳 O, 알파벳 l과 알파벳 I를 제외한 아라비아 숫자 9개와 알파벳 대소문자 49개, 총 58개의 문자로 숫자를 표현하는 방식입니다.
WIF 형식은 비밀키를 다른 지갑으로 옮길 때 사용하기 위한 비밀키 직렬화 방식입니다.

5장 트랜잭션
트랜잭션은 블록체인 상에서 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위입니다. 블록체인 상에서 모든 트랜잭션은 공개되어 있고 분산되어 저장되어 있으므로 누구나 거래 내역을 확인할 수 있고 증명할 수 있습니다.
트랜잭션은 버전, 입력, 출력, 록타임 4가지로 구성되어 있습니다.
버전은 트랜잭션의 버전을 의미하며 어떤 부가기능을 사용할 수 있는지 명시해 주기 위해 사용됩니다. 이 값은 보통은 1을 4바이트 리틀엔디언으로 표현한 값으로 나타냅니다.
트랜잭션 입력은 여러 개가 있을 수 있으며, 각 입력은 4개의 하부 필드를 가지고 있습니다. 이전 트랜잭션의 해시값 혹은 ID, 이전 트랜잭션의 출력 번호, 해제 스크립트 그리고 시퀀스가 이에 해당합니다.
트랜잭션 출력도 입력과 마찬가지로 여러 개가 있을 수 있습니다. 각 출력은 비트코인 금액과 잠금 스크립트로 구성되어 있습니다.
록타임은 트랜잭션 전파 후 실행을 지연시키는 방법을 제공합니다. 록타임은 4바이트의 리틀엔디언으로 직렬화됩니다.
비트코인 합의 규칙에 따라 입력이 가리키는 비트코인 합이 출력의 비트코인 합보다 크거나 같아야 합니다. 트랜잭션의 수수료는 단순히 입력 합에서 출력 합을 빼는 것으로 계산하며 채굴자에게 채굴에 대한 보상으로 지급됩니다.

6장 스크립트
스크립트는 비트코인의 전송 메커니즘을 프로그램으로 실행하기 위해 사용되는 간단한 스택 기반의 언어입니다.
스크립트의 명령어에는 두 가지가 있습니다. 하나는 원소(element)이고 다른 하나는 연산자(operation)입니다. 원소는 스크립트 실행 명령어 집합 안에서 사용되는 데이터를 의미합니다. 대표적으로 DER 서명이나 SEC 공개키가 있습니다. 연산자는 데이터에 대해 무언가 동작을 실행합니다.
모든 명령어가 실행된 뒤에 스택에 0이 아닌 원소가 남아있어야 스크립트가 유효합니다. 스택이 비어있거나 0이 남아있다면 유효하지 않은 것으로 간주되어 해당 스크립트가 포함된 트랜잭션이 네트워크로 전파되지 않습니다.
잠금 스크립트(ScriptPubKey)와 해제 스크립트(ScriptSig) 모두 같은 방식으로 파싱 됩니다. 처음 읽은 한 바이트 값이 0x010x4b(175) 범위의 값이면 해당 길이만큼 읽어서 원소로 간주합니다. 원소 길이의 범위를 벗어난 경우(0 또는 76 이상)에는 연산자를 의미합니다.
스크립트를 실행하기 위해 잠금 스크립트와 해제 스크립트를 결합해야 하는데, 두 스크립트는 서로 다른 트랜잭션에 있습니다. 잠금 스크립트는 비트코인을 받았던 트랜잭션에서, 해제 스크립트는 비트코인을 소비하는 트랜잭션에서 가져옵니다. 해제 스크립트 명령어는 잠금 스크립트 명령어 위에 위치합니다.
비트코인 스크립트는 다양한 조합으로 작성할 수도 있지만, 네트워크상의 여러 노드들이 공통으로 사용할 수 있는 표준 스크립트가 있습니다. 대표적으로 p2pk, p2pkh, p2sh가 있습니다. 비표준 스크립트는 표준 스크립트가 아닌 형태로 여러 명령어들을 조합하여 잠금 스크립트와 해제 스크립트를 구성할 수 있음을 의미합니다.
p2pk 스크립트는 표준 스크립트로, 공개키를 사용해 비트코인을 보냅니다. 비밀키의 소유자는 서명을 통해 비트코인을 해제하고 사용할 수 있습니다. p2pk 잠금 스크립트는 공개키와 OP_CHECKSIG로 구성되어 있으며 해제 스크립트는 공개키에 대응하는 비밀키로 생성된 서명으로 구성되어 있습니다.

그러나 p2pk 스크립트는 SEC 형식으로 직렬화된 공개키가 잠금 스크립트에 들어가기 때문에 공간과 연산 자원이 많이 필요하며, 공개키가 그대로 노출되기 때문에 혹여나 ECDSA 암호가 깨질 경우에 비트코인을 그대로 탈취당할 위험이 있습니다.
이를 해결하기 위한 방법으로 p2pkh 스크립트를 사용할 수 있습니다. p2pkh 스크립트는 공개키 대신 공개키를 hash160 함수로 해싱한 값을 사용해 출력을 잠급니다. 이렇게 하면 잠금 스크립트의 크기가 작아질 뿐만 아니라 공개키가 그대로 노출되는 문제를 해결할 수 있습니다.

7장 트랜잭션 검증과 생성
네트워크로 전파된 트랜잭션이 블록에 추가되기 위해서는 검증이 필요합니다.
우선 입력 비트코인의 존재를 확인합니다. 이는 이미 사용한 비트코인을 이중지불하는 것을 방지하기 위함입니다. 그다음으로는 입력과 출력 비트코인 합계를 확인합니다. 만약 출력 비트코인 합이 입력 비트코인의 합보다 크다면 이는 존재하지 않는 비트코인을 네트워크 상에 생성하게 되므로 해당 트랜잭션은 거부되어야 합니다.
해제 스크립트에 들어간 서명이 유효한지도 확인해야 합니다. 서명을 구하기 위해 필요한 서명해시 z는 다음과 같은 과정을 거쳐 구할 수 있습니다.
- 모든 해제 스크립트를 비운다.
- 삭제된 해제 스크립트 자리에 사용할 UTXO의 잠금 스크립트를 넣는다.
- 해시 유형을 트랜잭션의 마지막에 덧붙인다.
트랜잭션을 검증하기 위해서는 각 입력을 검증하고 각 입력을 검증할 때마다 이에 대응하는 서명해시 z를 생성하여 검증합니다.
이렇게 검증이 가능하다면 검증 항목에 부합하는 유효한 트랜잭션을 생성할 수 있습니다. 트랜잭션을 생성하려면 최소한 하나의 UTXO를 참조해야 합니다. 또한 UTXO의 잠금 스크립트를 해제할 수 있는 서명을 생성하기 위한 비밀키도 필요합니다.
마치며
쉽게 표현해본다고 그려봤는데 전반적으로 공부를 한 저한테는 한 눈에 들어오는데 다른 분들은 어떨지 모르겠습니다. 누가봐도 한 눈에 직관적으로 이해할 수 있는 그런 표현이 가능하려면 아무래도 더 공부가 필요할 것 같습니다. 중간 정리는 여기서 마무리하겠습니다. 꾸준히 블로깅하면서 누구나 읽고 이해하기 쉬운 글을 쓸 수 있도록 정진하겠습니다. 긴 글 읽어주셔서 감사합니다!