이더리움의 트랜잭션 분석 — Part 1

Santony Choi
Santony’s Blog
Published in
5 min readMay 22, 2018

이더리움을 사용하는 대다수의 사용자는 MEW(My Ether Wallet)이나 Metamask와 같은 지갑 프로그램, 혹은 Mist와 같은 전용 브라우저를 활용해 이더리움 네트워크와 통신한다. 이보다 높은 수준의 컨트롤이 필요한 경우에는 Geth와 Parity라는 클라이언트를 활용한다. 이 클라이언트들은 위에 언급된 지갑 / 브라우저의 많은 기능을 포함하고 있으며 커맨드라인 툴, JSON RPC 인터페이스 등을 제공해 높은 수준의 컨트롤을 가능하게 한다. 이더리움의 채굴 역시 이 클라이언트들을 통해 가능하다.

하지만 위에 나열된 도구들은 사용자의 개인키를 프로그램에 불러들이는 과정이 필요하다. 나름의 방식으로 키를 보호하고 있지만, 그리고 그 방식이 충분히 납득할 만큼 안전하긴 하지만, 키가 노출될 가능성이 있다는 것은 부인할 수 없다. 따라서 보안이 극도로 강조되는 특수한 케이스에서는 이런 도구들이 제공하는 편리한 기능을 쓰는 대신, 오프라인에서 개인키를 활용해 직접 바이너리 형태의 트랜잭션을 만들어야 할 필요가 있다.

본 시리즈에서는 이처럼 특수한 케이스에서 활용할 수 있도록 이더리움 트랜잭션을 분석하고 이를 RawTransaction의 형태로 만드는 과정을 따라가보겠다.

RLP 인코딩

가장 첫 번째로 다룰 내용은 이더리움이 데이터를 serialize하는 방식인 RLP 인코딩이다. RLP는 Recursive Length Prefix의 약자로, 길이와 타입을 나타내는 prefix와 데이터를 재귀적인 형태로 나타내는 규칙을 말한다. 예시를 보며 시작하자.

0x8773616e746f6e79

santony라는 문자열을 RLP 인코딩한 결과다. 앞의 0x87을 제외하고 남은 7바이트의 16진수 값을 ascii로 변환하면 santony가 된다는 것을 쉽게 알 수 있다. 그렇다면 0x87은 무엇일까?

RLP의 문자열 인코딩 규칙

1. 값이 [0x00, 0x7f]에 속하는 1바이트 값은 그대로 적는다.

2. 문자열이 0바이트에서 55바이트 길이라면 문자열 앞에 0x80 + 문자열의 길이를 적고 문자열의 값을 뒤에 이어 적는다. 따라서 첫 바이트의 값은 [0x80, 0xb7] 에 속한다.

3. 문자열이 55바이트보다 길다면 첫 바이트는 0xb7 + 문자열의 길이를 나타낼 바이트의 자릿수를 적고 이어서 문자열의 길이를 적은 후 문자열의 값을 뒤에 이어 적는다. 예를 들어 1024바이트 길이의 문자열은, 0xb90400 + 문자열의 형태로 표현된다. 첫 바이트의 값은 [0xb8, 0xbf]에 속한다.

규칙은 생각보다 간단하다. 우리는 인코딩된 값의 첫 바이트를 통해 뒤에 붙은 값이 새로운 아이템(Item, RLP에서 항목을 구분하는 단위)의 시작인지(규칙 1), 문자열 본문 값인지(규칙2), 문자열의 길이를 나타내는 값인지(규칙3) 구분할 수 있다. 여러 문자열이 이어져있어도 한 단위의 문자열을 길이에 맞춰 디코딩한 뒤에 나오는 바이트가 새로운 아이템을 나타내는 prefix라는 것을 쉽게 알 수 있으므로 재귀적으로(Recursively) 인코딩/디코딩 할 수 있는 것이다.

새로운 예시를 보자.

0xd08773616e746f6e7982697384636f6f6c

첫 자리에 위치한 0xd0은 무엇을 나타내는 prefix일까?

RLP의 리스트 인코딩

  1. 리스트에 담긴 요소의 길이를 모두 합친 것이 0바이트에서 55바이트 길이라면 리스트의 머리에 0xc0 + 모든 요소의 길이를 더한 값을 적고 각 요소의 값을 이어서 적는다. 첫 바이트의 값은 [0xc0, 0xf7]에 속한다.

2. 요소 길이의 합이 55바이트보다 크다면 문자열에서와 유사하게 첫 바이트는 0xf7 + 요소 길이의 합을 나타낼 바이트의 자릿수를 적고 이어서 요소 길이의 합을 적은 후 요소의 값을 이어서 적는다. 첫 바이트의 값은 곧, [0xf8, 0xff]에 속하게 된다.

RLP에는 문자열과 리스트라는 두 종류의 아이템만 존재하고 규칙 역시 문자열 인코딩과 리스트 인코딩 두 가지 뿐이다. 즉, 우린 규칙을 모두 알게 된 것이다. 규칙에 맞추어 예시로 든 값을 보기 좋게 구분해서 써보겠다.

0x d0 87 73616e746f6e79 82 6973 84 636f6f6c

우리는 0xd0을 통해 이 값이 16바이트를 담고 있는 리스트라는 사실을 알 수 있다. 주의할 점은 리스트의 요소가 총 몇 개인지는 나타내지 않으며, 이를 알기 위해서는 직접 디코딩을 해보아야 한다는 사실이다. 또한 리스트의 요소 아이템들을 표현하는 prefix도 전체 길이에 포함된다는 점 역시 짚고 가자. 어찌보면 불친절하고 난해할 수 있으나 저장되는 데이터를 1바이트라도 줄여야하는 이더리움의 구조적 제약을 고려한다면 영리한 선택이라고 보인다.

다시 예시로 돌아가서, 두 번째 바이트인 0x87을 통해 이어질 7바이트가 ascii코드라는 것을 예측할 수 있다. 뒤의 구분자인 0x82, 0x84 역시 뒤에 이어질 값이 각각 2바이트, 4바이트의 문자열이라는 것을 보여준다. 결국 1바이트 리스트 prefix + 1바이트 문자열 prefix + 7바이트 ascii + 1바이트 문자열 prefix + 2바이트 ascii + 1바이트 문자열 prefix + 4바이트 ascii로 디코딩을 할 수 있고, 그 값은

[“santony”,”is”,”cool”]

이 된다.

RLP는 복잡한 과정을 통해 생성될 이더리움의 트랜잭션이 최종적으로 네트워크로 전송되는 형태이므로 이더리움을 이해하는 데에 매우 중요하다.

다음 순서에서는 RLP로 인코딩된

0xf87f81c98504a817c80083061a80941234567890123456789012345678901234567890880de0b6b3a764000091d08773616e746f6e7982697384636f6f6c29a0fd3263ef5b7550ec3b9ad789c8ae5930c78f55b2e4ac1984e22bf276cf4ba4e7a0399b9daad024031975346d195043e49e3a3a1ebbec494502978b0aa86a00bae7

값이 무엇을 뜻하는지 하나씩 분해해보겠다.

--

--