위험 경고: '가상화폐', '블록체인'이라는 이름으로 불법 자금 모집 위험에 주의하세요. — 은행보험감독관리위원회 등 5개 부처
검색
로그인
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt
BTC
ETH
HTX
SOL
BNB
시장 동향 보기

Web3 보안 시리즈: 실수로 다른 블록체인으로 이체된 자금을 회수할 수 있을까?

ZAN Team
特邀专栏作者
2025-11-22 03:00
이 기사는 약 3399자로, 전체를 읽는 데 약 5분이 소요됩니다
잘못된 블록체인으로 암호화폐를 전송하셨나요? 걱정하지 마세요! 자산을 아직 복구할 수 있을지도 모릅니다! 이 글에서는 블록체인 간에 실수로 전송된 자금을 복구하는 방법을 알려드립니다.

암호화폐 세계에서는 단 한 번의 잘못된 클릭이 "디지털 재앙"을 초래할 수 있습니다. 가장 흔한 악몽 중 하나는 자산을 잘못된 블록체인으로 전송하는 것입니다. 예를 들어, 이더리움 세폴리아 테스트넷의 주소로 ETH를 전송하려고 했지만 실수로 이더리움 메인넷의 주소로 전송하는 경우가 있습니다. 이 경우, 이더리움 메인넷에서 실수로 전송된 자금을 복구할 수 있을까요? 자산 복구 가능 여부는 수신 주소 유형에 따라 달라집니다. 이 글에서는 다양한 시나리오를 분석합니다.

1. 시나리오 1: 수신 주소가 EOA입니다.

EOA(외부 소유 계좌)는 개인 키나 니모닉 문구로 직접 제어되는 일반 지갑 주소를 일반적으로 말합니다.

자산 회수를 위한 전제 조건:

  • 귀하는 자산을 EOA 주소로 이전했습니다.
  • 이 대상 EOA 주소에 대한 개인 키 또는 니모닉 문구를 보유하고 있습니다. (이는 일반적으로 본인의 다른 지갑 주소이거나, 협조해 줄 친구의 주소입니다.)
  • 대상 체인은 EVM 호환 체인입니다.

자산 회수 방법:

수신 EOA 주소에 대한 개인 키를 보유한 사람은 대상 블록체인에서 직접 자금을 인출할 수 있습니다.

2. 시나리오 2: 수신 주소가 계약서입니다.

이는 가장 절박한 시나리오 중 하나입니다. 스마트 계약의 주소는 개인 키로 생성되지 않기 때문에 아무도 스마트 계약의 개인 키를 소유하지 않으며, 따라서 EOA를 제어하는 것과 같은 방식으로 계약을 제어할 수 없습니다. 더 나아가, 계약에 "실수로 이체된 자산"을 처리하는 사전 작성된 복구 기능이 없다면, 실수로 이체된 자금은 계약에 영구적으로 묶여 아무도 회수할 수 없게 될 수 있습니다.

하지만 어떤 경우에는 실제로 희망의 빛이 보입니다. 다음으로, 이더리움 메인넷에 ETH가 잠겨 있는 상황을 가정하고, 자금을 어떻게 회수할 수 있는지 설명하겠습니다.

2.1. 장면 소개

요약하자면, 이 시나리오는 사용자가 토큰 채굴을 위해 Sepolia 테스트넷에서 ETH를 컨트랙트로 이체하기 위해 컨트랙트를 호출하려는 상황을 포함합니다. 그러나 트랜잭션 시작 과정에서 메인넷에 잘못된 연결이 발생하여 ETH가 메인넷 컨트랙트에 잠겼습니다. 구체적인 시나리오 구성 과정은 다음과 같습니다.

1. 이더리움 세폴리아 테스트넷에서 프로젝트팀(EOA)은 구현 계약을 배포했습니다 . 이 계약의 주요 기능은 사용자가 ETH를 입금하여 해당 A토큰을 발행하는 것이며, "mintTokens" 함수와 유사한 코드를 사용한다고 가정합니다. 배포 주소는 A라고 가정합니다 . A에는 ETH를 직접 출금할 수 있는 함수가 없습니다.

2. 이더리움 세폴리아 테스트넷에서 프로젝트팀(EOA)은 팩토리 컨트랙트를 배포했습니다 . 이 컨트랙트의 기능은 제공된 구현 컨트랙트 주소와 솔트를 기반으로 최소한의 프록시 컨트랙트(클론)를 사용하여 구현 컨트랙트를 가리키는 프록시 컨트랙트를 배포하는 것입니다("deployProxyByImplementation" 함수 참조). 배포 주소가 B라고 가정합니다. 여기서는 구현 컨트랙트 A의 주소를 `_implementation`으로 전달하여 "deployProxyByImplementation" 함수를 호출하여 주소 C에 A를 가리키는 프록시 컨트랙트를 배포합니다.

3. 사용자가 Sepolia 테스트넷에서 ETH를 전송하여 A 토큰을 발행하려고 합니다. 사용자는 프록시 계약 C에 대한 호출을 시작합니다. 일반적으로 프록시 계약 C는 계약 A를 구현하는 "mintTokens" 함수를 추가로 호출하여 사용자의 작업을 완료합니다. 그러나 호출 과정에서 사용자가 이더리움 메인넷에 잘못 연결했습니다. 결과적으로 사용자는 이더리움 메인넷의 주소 C로 ETH를 직접 전송합니다. 이 시점에서 이더리움 메인넷의 주소 C에는 어떠한 계약도 배포되지 않았으며, 아무도 주소 C의 개인 키를 소유하고 있지 않습니다. 따라서 사용자의 자금은 메인넷의 주소 C에 일시적으로 고정됩니다.

2.2. 주요 지식 포인트

구체적인 구조 계획을 소개하기에 앞서, 먼저 구조에 필요한 기본적인 지식 몇 가지를 소개하겠습니다.

2.2.1. 생성 및 생성2

`create`와 `create2`는 Solidity에서 계약을 배포하는 두 가지 일반적인 방법입니다.

  • `create` 함수를 사용하여 계약을 배포할 때 계약 주소는 거래 개시자의 주소와 계정의 거래 수(nonce)에 의해 결정되며 계약의 내용과는 관련이 없습니다.
  • create2를 사용하여 계약을 배포할 때 계약 주소 계산은 더 이상 트랜잭션 개시자의 nonce에 따라 달라지지 않고 다음 네 가지 매개변수와 관련됩니다.
  • 0xff
  • 새로운 계약을 생성하기 위한 계약 주소입니다.
  • 매개변수로 사용되는 난독화 값(salt)
  • 생성할 계약의 생성 바이트코드(init_code).

2.2.2. 최소 에이전트 계약(클론)

https://docs.openzeppelin.com/contracts/4.x/api/proxy#clones

최소 프록시 계약(클론 계약이라고도 함)은 지정된 구현 계약을 가리키는 매우 낮은 비용(가스)으로 프록시 계약을 배포한다는 아이디어를 기반으로 합니다. 클론 계약에서 프록시 계약은 `create` 또는 `create2` 메서드를 사용하여 배포할 수 있습니다. 예를 들어, `cloneDeterministic` 함수를 사용하여 프록시 계약을 배포하려면 `create2` 메서드가 사용됩니다.

"cloneDeterministic" 함수에서 생성된 프록시 계약의 바이트코드는 "0x363d3d373d3d3d363d73<구현 계약 주소>5af43d82803e903d91602b57fd5bf3" 형식으로 매우 짧습니다. 구현 계약의 주소는 바이트코드에 직접 하드코딩되어 있으며, 프록시 계약에 대한 모든 호출은 구현 계약으로 위임됩니다.

"cloneDeterministic" 함수에서 볼 수 있듯이, 이 함수는 create2 메서드를 사용하여 프록시 계약을 생성합니다. 생성된 프록시 계약의 주소는 계약 생성자 주소, 솔트, 구현 계약 주소, 그리고 고정된 바이트코드 문자열과 연관되지만, 구현 계약의 바이트코드와는 연관되지 않습니다.

2.3. 구조 계획

다음으로, 메인넷 C 주소에 보관된 사용자의 ETH를 회수하는 방법을 설명하겠습니다. 핵심은 이더리움 메인넷 C 주소에 컨트랙트 코드를 배포하여 해당 주소를 확보하고 ETH를 추출하는 것입니다. 구체적인 단계는 다음과 같습니다.

1. 테스트넷과 동일한 주소 B를 사용하여 메인넷에 팩토리 컨트랙트를 배포합니다. 동일한 팩토리 컨트랙트 주소가 필요한 이유는 이후 "cloneDeterministic"을 호출하여 프록시 컨트랙트를 배포할 때 프록시 컨트랙트의 주소 계산이 팩토리 컨트랙트 주소와 관련되기 때문입니다. Sepolia 테스트넷에서 팩토리 컨트랙트를 배포하는 트랜잭션을 검토하여 이 트랜잭션에서 배포자(프로젝트 주소)의 논스를 얻습니다. 메인넷에서 팩토리 컨트랙트를 배포하기 전에 프로젝트 소유자(EOA) 주소의 논스를 논스로 변경합니다. 그런 다음 메인넷에 팩토리 컨트랙트를 배포합니다. 배포자의 주소와 논스가 테스트넷의 배포 트랜잭션과 동일하므로 메인넷에 배포되는 팩토리 컨트랙트 주소도 B가 됩니다.

2. 테스트넷과 동일한 주소 A에 구현 계약을 메인넷에 배포합니다. #최소 프록시 계약(클론)# 섹션에서 언급했듯이, 클론 계약의 "cloneDeterministic" 함수를 사용하여 프록시 계약을 배포하면 프록시 계약 주소가 계산됩니다. 계산된 프록시 계약 주소는 입력 매개변수 `salt`와 구현 계약 주소에 따라 달라지지만, 구현 계약의 바이트코드와는 독립적입니다. 따라서 주소 A에는 계약 하나만 배포하면 됩니다. 계약의 구체적인 내용은 프록시 계약 주소 계산에 영향을 미치지 않습니다. 그런 다음 아래 코드와 같이 ETH 추출 기능이 있는 계약을 주소 A에 직접 배포할 수 있습니다.

테스트넷에서 구현 계약 A는 프로젝트 소유자 주소(EOA)를 통해 배포됩니다. 따라서 구현 계약 A의 주소는 트랜잭션 개시자와 해당 nonce에만 연관됩니다. 따라서 테스트넷에서 구현 계약 A를 배포하는 트랜잭션을 관찰하고, 관련 nonce를 찾은 후, 메인넷의 프로젝트 소유자 주소(EOA)를 지정된 nonce로 푸시한 후 구현 계약 A를 배포하면 됩니다.

3. 테스트넷과 동일한 주소 C에 메인넷에 프록시 계약을 배포합니다. 테스트넷에 배포된 프록시 계약 C의 트랜잭션을 관찰하고 솔트 정보를 얻은 후, 구현 계약 A의 주소와 솔트를 매개변수로 전달하여 팩토리 계약 B의 "deployProxyByImplementation" 함수를 호출합니다. 이렇게 하면 메인넷의 주소 C에 프록시 계약이 배포됩니다.

4. 메인넷 프록시 계약 C가 자금을 인출하기 위해 호출됩니다. 프로젝트 주소(EOA)는 프록시 계약 C의 인출 함수를 호출하고 자금 수신자를 지정하며, 프록시 계약 C에서 동결된 ETH를 성공적으로 인출한 후 해당 사용자에게 반환합니다.

2.4. 요약

위의 구제 계획에서 볼 수 있듯이, 계약 배포자의 대상 체인에 있는 관련 nonce가 사용되지 않는 경우, 자금을 가두는 계약에 인출 기능이 있는 경우 또는 다양한 방법으로 인출 기능을 배포할 수 있는 경우(계약을 업그레이드하거나 클론과 같은 프록시를 사용할 수 있는 경우 등)와 같이 여러 조건이 동시에 충족되는 경우에만 자금을 회수할 수 있습니다.

따라서 모든 거래자는 계약 체결 전에 각 거래를 꼼꼼하게 검증하는 등 매우 신중해야 합니다. 계약 체결 전에 ZAN의 AI SCAN 취약점 검사 도구를 사용하여 보안을 점검할 수 있습니다. 자금이 실수로 잠긴 경우 당황하지 마십시오. ZAN의 계약 보안 감사팀에 연락하여 자금 회수를 도와드릴 수 있습니다.

이 글은 ZAN Team(X 계정 @zan_team ) 및 AntChain OpenLabs(X 계정 @AntChainOpenLab )의 Cara( @Cara6289 )가 작성했습니다.

안전
스마트 계약
Odaily 공식 커뮤니티에 가입하세요