본문 바로가기
블록체인

[이더리움] chainID, networkID

by dbadoy 2023. 4. 27.
짧게 읽고 싶다면:
chainID: 하드포크된 네트워크로부터의 재전송 공격을 막기 위한 식별자 값이며, 서명값에 포함하여 노드가 다른 네트워크로 전송된 트랜잭션임을 인지할 수 있도록 돕는 값이다.
networkID: 노드 통신 과정에서 다른 네트워크의 노드와 연결되는 것을 방지하기 위해 사용하는 유니크한 값이며 노드가 연결 직후 상태 메세지를 주고 받는 과정에서 확인하는 값이다.

 

이더리움 기반에서 작업을 해본 사람이라면 chainID를 본 적이 있을 것이다. 나의 경우 이더리움과 통신을 돕는 라이브러리(ethers.js)를 사용할 때, 노드 엔드포인트와 chainID를 입력해주는 부분에서 주로 봤다. 조금 더 들어가서, 엔드포인트만 알면 되지 왜 chainID란 것이 필요하지? 라는 궁금증이 생겨 찾아보았다면 블록체인이 하드포크되는 경우 재전송 공격을 막기 위하여 도입된 값이란 것 까지 알 수 있다.

이 글에서는 블록체인에 재전송 공격이 어떤 식으로 일어날 수 있는지, chainID가 이를 어떻게 방지할 수 있는지에 대해서 조금 더 깊이 알아본다.

먼저 재전송 공격에 대해 이야기해보자.
기존 네트워크 통신은 별 다른 암호화 과정 없이 데이터를 주고 받았다. 특정 사설 네트워크의 A에게 메세지가 요청되는 경우 라우터는 A에게 직접 메세지를 전달해주지 않는다. 라우터는 단순히 내부에 존재하는 모든 호스트에게 브로드캐스트하고, 네트워크 카드단에서 자신에게 온 메세지라면 이를 받아 처리하게 된다. 즉, 설정을 조금 바꿔주면 나에게 온 메세지뿐만 아니라 다른 사용자에게 온 메세지들도 모두 확인할 수 있다는 것이다(Promiscuous mode). 

주고 받는 데이터가 누가 봐도 상관없는 성격의 데이터일 수 있지만, 남에게 보여주기 싫은 데이터일 가능성도 분명히 존재한다. 때문에 사용자들은 수신자만 알아볼 수 있도록 종단간 암호화를 하기 시작한다. 이런 경우 다른 사용자가 메세지를 받아봤자 내용을 알아볼 수 없기 때문에 안전한 통신을 할 수 있다.
물론 암호화 알고리즘 자체에 취약점이 있거나, 키 스페이스가 너무 작아 브루트 포스 공격에 취약하다면 기밀성이 손상되지만 이는 해당 글에서 다루는 내용은 아니며 암호화 자체는 문제가 없다고 가정한다.

제 3자가 알아볼 수 없게 되었으니 이제 마음 놓고 메세지를 주고 받고 해도 될까?

공격자는 암호화를 풀기 위한 노력외로 또 다른 공격 방법을 생각해낸다. 중간에서 메세지를 받아, 무슨 내용인지는 모르지만 목적지에 이를 여러 번 보내는 것이다.

예를 들어보자.
사용자 A가 은행 B에게 'A의 계좌로부터 C에게 1,000원을 보내주세요' 라는 요청을 보냈다. C는 A가 보낸 메세지를 중간에서 탈취하여 1,000원을 1,000,000으로 바꾸고 싶다. A가 보낸 메세지를 성공적으로 탈취했으나 암호화가 되어있었고, 무슨 짓을 해도 이를 복호화할 수 없었다. C는 복호화를 포기하고, 단순히 탈취한 메세지를 은행 B에게 1,000번 보낸다. 은행 B는 암호화되어 있는 메세지를 받고, A가 보낸 메세지가 맞다고 판단하여 C에게 1,000원을 1,000번... 즉 1,000,000 원을 보내게 된다.

이렇듯 복호화 시도가 아니라 이미 송신자가 보낸 정당한 메세지를 여러 번 보내 비정상 작동을 유도하는 것이 재전송 공격이다. 해결책으로는 대표적으로 메세지에 nonce나 타임스탬프를 포함시키는 방법이 있다.

재전송 공격에 대한 기본 내용을 다루었으니, 이번에는 블록체인에서 어떤식으로 재전송 공격이 발생할 수 있는지 예시를 통해 알아보자.

1. 이더리움과 이더리움 클래식으로 하드포크가 발생.
2. 이더리움에서 A가 B에게 1 ETH를 보내겠다는 트랜잭션을 생성하고, 서명하여 노드에 전송.
3. 공격자 B는 해당 트랜잭션을 이더리움 클래식에 그대로 전송.
4. 이더리움 클래식 노드 입장에서 이는 정상적인 트랜잭션이기 때문에 트랜잭션을 블록에 담음.
5. 결과적으로 A는 의도와 다르게 이더리움에서 1 ETH를, 이더리움 클래식에서도 1 ETC를 B에게 전송하게 됨.

이는 크리티컬한 문제다. 이더리움에서는 문제를 해결하기 위해 chainID라는 유니크한 값을 지정하고, 서명값에 이를 포함시킨다. 해당 내용은 EIP-155 에서 제안됐다.

EIP-155

EIP-155에서는 서명을 목적으로 트랜잭션의 해시를 계산할 때 6개의 rlp 인코딩 요소(nonce, gasprice, startgas, to, value, data)만 해싱하는 대신 9개의 rlp 인코딩 요소(nonce, gasprice, startgas, to, value, data, chainid, 0, 0)를 해싱하는 것을 제안한다. 노드는 서명값을 확인하여 chainID가 다른 것을 감지할 수 있고, 하드포크된 네트워크로부터 전송된 트랜잭션을 거를 수 있다.

이더리움 RPC 메서드 중에는 노드가 연결된 네트워크의 chainID를 가져올 수 있는 'eth_chainId' 메서드가 존재한다. 해당 메서드는 EIP-155가 제안되고 나서 약 1년이 지난 후에야 EIP-695를 통해 추가되었다.

EIP-695 내용을 살펴보면, 기존에는 'net_version' RPC 메서드를 호출하여 networkID를 가져올 수 있었지만 chainID를 가져올 수 있는 메서드는 존재하지 않기 때문에 'eth_chainId'를 제안한다고 말한다. networkID에 대해 알아보지 않을 수 없겠다.

networkID는 chainID가 도입되기 전부터 사용했던 식별자다. 노드 통신 과정에서 다른 네트워크의 노드와 연결되는 것을 방지하기 위해 사용하는 유니크한 값이며 노드 연결 직후 서로 상태 메세지를 주고 받는 과정에서 확인하는 값이다. 즉, 두 값은 완전히 다른 목적을 위한 식별자이지만, 일반적으로 networkID와 chainID가 동일한 값이었기 때문에 networkID를 리턴하는 메서드인 net_version을 통해 chainID 값을 구하고 있었던 것이고, EIP-695는 이를 분리하자고 제안한 것이다.

어떤 블로그에서는 이에 대한 기술 부채에 대해 설명하는데, networkID와 chainID가 일치하기 때문에 큰 문제가 아니라고 생각할 수 있지만, 2019년 기준 EVM 기반 블록체인 48개 중 13개의 블록체인이 networkID와 chainID가 일치하지 않았다고 한다. 

사실 이제 와서는 주요 라이브러리가 eth_chainId 메서드를 호출하여 서명 과정에서 사용하기 때문에 큰 문제가 발생할 수 있을까 생각이 든다. 코드 작성 과정에서 사용자가 직접 net_version 메서드로 networkID를 가져와 서명하는 일은 상상이 안되지만... chainID와 networkID의 차이를 알아둬서 나쁠건 없다.

+ EIP-1344: ChainID opcode
+ Besu에서는 제네시스 파일에 chainID를 설정하는데, 이 값으로 networkID를 설정한다.

참고

https://eips.ethereum.org/EIPS/eip-155

https://eips.ethereum.org/EIPS/eip-695

https://docs.goquorum.consensys.net/concepts/network-and-chain-id

https://medium.com/@pedrouid/chainid-vs-networkid-how-do-they-differ-on-ethereum-eec2ed41635b

 

'블록체인' 카테고리의 다른 글

[이더리움] Signature API 클라이언트  (0) 2023.05.16
[이더리움 코어] event.Feed  (0) 2023.05.10
[이더리움] Access list  (0) 2023.04.12
[이더리움] Multicall contract  (0) 2023.04.08
[이더리움 코어] Connection  (0) 2023.03.21