비트코인에서 스크립트(script)는 스택 기반의 아주 단순한 프로그래밍 언어이다.
( 스택은 FILO(First In Last Out) 구조이다. )
비트코인은 스크립트로 작성된 코드를 읽어 이를 실행하면서 코인 트랜잭션을 수행한다.
비트코인 스크립트 언어는 의도적으로 루프(loop)를 허용하지 않았으며,
이는 인터프리터가 스크립트를 실행할 때, 무한루프에 빠져 크래시하는 것을
원천적으로 방지할 뿐만 아니라, 트랜잭션을 불필요하게 복잡하게 구현하지
못하도록 하는 장점을 제공한다.
비트코인 블럭체인은 루프를 허용하지 않는 Turing Incomplete 스크립트를 사용하는
반면, 이더리움과 같은 몇몇 블럭체인들은 루프를 허용하면서
Turing complete 스크립트를 지원하고 있다.
비트코인의 트랜잭션 스크립트는 프로그래밍 언어이므로
트랜잭션에 다양한 스크립트를 넣을 수 있게 되어 있지만,
일반적으로 사용되는 표준 스크립트(standard script)로는
P2PKH (pay to pubkey hash)
P2SH (pay to script hash)
P2PK (pay to pubkey)
P2MS (pay to multisig) 등이 있다.
현재 가장 많이 사용되는 것은 P2PKH와 P2SH 스크립트이다.
비트코인의 트랜잭션은 "비트코인 스크립트"를 실행하여 트랜잭션이 타당한 지를 검증한다.
트랜잭션의 검증은 이전 블럭의 사용되지 않은 트랜잭션 출력(transaction output, UTXO)과 이를 사용하기 위해 작성하는 트랜잭션 입력(transaction input)을 합쳐
실행 가능한 비트코인 스크립트를 만들고
이를 실행해서 TRUE가 리턴되면 트랜잭션이 타당하다고 판단한다.
트랜잭션 검증을 위한 스크립트는 먼저 트랜잭션 입력 스크립트(ScriptSig)를
앞에 두고, 뒤에 트랜잭션 출력 스크립트(ScriptPubKey)를 합쳐
하나의 스크립트로 만든다.
아래 그림은 이러한 트랜잭션 스크립트 구성을 표현한 것이다.
스크립트는 빈 스택에서 시작하며, 이 스택에서 데이터가 들어오거나 ( Push )
나가게 된다. ( POP )
스크립트 프로그램은 두 종류의 객체를 가진다.
Opcode: 덧셈, 뺄셈, 곱셈과 같은 연산 작업을 나타냄
데이터: OP_CODE가 아닌 모든 데이터는 원시 데이터로 해석되며
스택에 들어간다
Opcode는 로우 레벨 기계 언어로, 약 140 종류의 연산이 있다.
대표적인 Opcode의 연산 종류는 다음과 같다.
-- 스택 조작 연산: POP, PUSH, DUP, SWAP
-- 산술/비교/비트연산: ADD, SUB, GT, LT, OR
-- 환경 연산: CALLER, CALLVE, NUMBER
-- 메모리 조작 연산: MLOAD, MSTORE, MSTORE8, MSIZE
-- 스토리지 조작 연산: SLOAD, SSTORE
-- 프로그램 카운터 관련 연산: JUMP, JUMPI, PC, JUMPDEST
-- 작업 중지 연산: STOP, RETURN, REVERT, INVALID, SELFDESTRUCT
스크립트는 이 Opcode와 DATA를 일렬로 늘어놓은 것이다.
여기서 포인터(Pointer)는 일렬로 늘어진 Opcode와 데이터를 순서대로 하나씩 가리킨다.
만약 포인터가 데이터를 가리키면 데이터를 스택에 넣고, Opcode를 가리키면 스택에서 데이터를 꺼내온다.
Opcode는 스택에서 데이터를 하나 이상 가져올 수 있지만, 중요한 것은 스택 구조이기 때문에 가장 나중에 들어온 데이터부터 가져온다는 것이다.
스크립트 실행이 성공적이면, 스택의 가장 상단에 있는 요소는 1이 된다.
만약 스크립트를 끝까지 실행했는데도 스택 최상단에 1이 아닌 다른 값이 들어있다면 스크립트 실행을 실패한 것으로 간주한다.
노드가 네트워크로부터 새로운 트랜잭션을 받으면,
ScriptSig와 ScriptPubkey 필드를 추출하여 연결하여 최종적으로
<ScriptSig><ScriptPubkey>
형태의 스크립트를 얻게 된다.
노드는 이 스크립트와 빈 스택 하나를 사용해 스크립트를 실행하고,
실행이 완료되면 최상위 스택 요소가 1인지 확인한다.
1이면 트랜잭션이 유효하다가 간주하고
노드는 트랜잭션을 주변 노드들에게 전파한다.
만약 트랜잭션이 유효하지 않은 경우 트랜잭션을 보내지 않고,
이런 경우 주변 노드들에게 전파되지 않으므로
네트워크에 트랜잭션이 공유되지 않게 된다.
트랜잭션은 네트워크에 있는 모든 노드들이 받을 수 있기 때문에,
네트워크의 모든 노드들은 전체 네트워크의 상태를 지키는
문지기 같은 역할을 하게 된다.