개요
블록체인 기술에서 모든 것을 온체인(On-chain)으로 처리하는 것은 비효율적이거나 불가능한 경우가 많음 이때 오프체인 서명 검증(Off-chain Signature Verification)은 오프체인(서버)의 유연성과 온체인(컨트랙트)의 신뢰성을 결합하는 강력한 해결책이 됨
쉽게 비유하자면, 클럽 매니저(서버)가 VIP 손님(사용자)에게만 특별한 싸인이 담긴 입장권(서명)을 발급하고, 입구의 가드(스마트 컨트랙트)는 그 싸인만 확인하고 들여보내는 것과 같음 가드는 매번 매니저에게 연락할 필요 없이, 위조되지 않은 싸인인지 확인만 하면 됨
이 글에서는 오프체인 서명 검증이 무엇인지, 어떤 용어들이 사용되는지, 그리고 가장 중요하게는 어떤 원리로 동작하는지 상세히 알아봄
1. 핵심 용어 정리
오프체인 서명 (Off-chain Signature) 블록체인 외부(주로 백엔드 서버)에서 생성된 디지털 서명을 의미함 이 방식은 서명을 생성하는 과정에서 가스비가 들지 않고, 서버의 다양한 로직과 결합할 수 있는 장점이 있음
메타 트랜잭션 (Meta-transaction) 사용자가 가스비를 직접 내지 않고, 트랜잭션에 대한 의도만 ‘서명’으로 표현하면, 제3의 대리인(Relayer)이 이를 대신 실행하고 수수료를 지불하는 패턴임 오프체인 서명은 이 메타 트랜잭션을 구현하는 핵심 기술임
가스리스 트랜잭션 (Gasless Transaction) 메타 트랜잭션을 통해 사용자가 느끼기에 가스비 없이 트랜잭션을 처리하는 경험을 의미함 서비스 제공자가 페이마스터 등을 통해 가스비를 대납함으로써 구현됨
EIP-712 단순 문자열이 아닌, 구조화된 데이터에 대한 서명 표준임 이 표준을 사용하면 사용자가 자신이 무엇에 서명하는지 명확히 알 수 있으며, 다른 체인이나 다른 컨트랙트에서 서명을 재사용하는 ‘재생 공격(Replay Attack)‘을 방지할 수 있어 보안성이 매우 높음
2. 오프체인 서명 검증의 전체 흐름
서버, 사용자, 그리고 컨트랙트 간의 상호작용은 다음과 같은 단계로 이루어짐
사용자 요청 사용자의 클라이언트(웹/앱)는 트랜잭션을 바로 블록체인에 보내는 대신, 트랜잭션에 필요한 정보들을 백엔드 서버에 API 요청으로 보냄
서버 검증 및 서명 서버는 요청이 유효한지 검증함 (예: 우리 서비스의 정식 사용자인가? 요청 내용이 합당한가?) 검증이 완료되면, 서버는 EIP-712 표준에 따라 트랜잭션 파라미터가 담긴 메시지를 만들고, 서버의 개인키로 이 메시지에 서명함
사용자 트랜잭션 제출 서버는 생성된 서명(signature)과 관련 데이터들을 사용자에게 다시 응답으로 보내줌 사용자의 클라이언트는 이 서명 데이터를 포함하여 트랜잭션를 구성하고, 최종적으로 블록체인(스마트 컨트랙트)에 제출함
컨트랙트 검증 스마트 컨트랙트는 제출된 트랜잭션을 받아 서명이 유효한지 검증함 검증에 성공하면, 컨트랙트는 약속된 작업을 수행함 (예: 가스비 대납, 아이템 지급 등)
3. 핵심 원리: 컨트랙트의 서명 검증 과정
컨트랙트는 서버의 개인키를 모르는데, 어떻게 서버가 서명했다는 것을 신뢰할 수 있을까? 이 과정은 암호학적 원리를 이용해 세 단계로 진행됨
1단계: 데이터 해시(Hash) 재구성
컨트랙트는 사용자가 제출한 파라미터들(userAddress, expiryTimestamp 등)을 받아, 서버가 서명을 생성했을 때와 완벽히 동일한 구조와 순서로 이 데이터들을 조합하여 해시(hash) 값을 다시 계산함
이때 EIP-712 표준에 따라 컨트랙트의 이름, 버전, 체인 ID 등이 포함된 도메인 분리자(Domain Separator)가 해시와 함께 사용되어 다른 컨트랙트에서 서명이 재사용되는 것을 막음
// 1. 전달받은 파라미터로 해시 재계산
bytes32 hash = keccak256(abi.encode(
SIGNATURE_TYPEHASH,
userAddress,
expiryTimestamp,
recommendedGasLimit,
recommendedGasPrice
));
bytes32 digest = hashTypedDataV4(hash); // 도메인 분리자와 결합
2단계: 서명자 주소 복원
ecrecover라는 암호학적 함수는 메시지 해시(digest)와 서명값(signature), 이 두 가지를 입력받아 해당 서명을 만들 수 있는 유일한 공개키, 즉 서명자의 주소를 역으로 계산해냄 이 과정에서 서버의 개인키는 절대 노출되지 않음
// 2. 서명에서 주소 복원
address recoveredAddress = digest.recover(signature);
3단계: 서명자 주소 비교
마지막으로, 2단계에서 복원된 주소가 스마트 컨트랙트에 미리 저장된 신뢰할 수 있는 서버의 주소(signer)와 일치하는지 비교함 두 주소가 일치한다면, 이 서명은 우리가 신뢰하는 서버가 생성한 유효한 서명임이 증명된 것임
// 3. 복원된 주소가 signer와 일치하는지 확인
require(signer == recoveredAddress, "Paymaster: Invalid signer");
4. 데이터 무결성: 서명된 데이터는 어떻게 신뢰하는가?
“만약 사용자가 유효한 서명을 받은 뒤, 파라미터 값만 몰래 바꿔서 제출하면 어떡하지?” 결론부터 말하면, 불가능함
서명 검증 과정 자체가 데이터의 무결성을 보장하기 때문임 만약 사용자가 파라미터를 1비트라도 변경하면, 1단계에서 재구성되는 해시값이 원래의 해시값과 완전히 달라짐 결과적으로 2단계에서 주소 복원에 실패하거나 엉뚱한 주소를 반환하게 되어, 3단계의 주소 비교에서 반드시 실패하게 됨
이처럼, 서명은 데이터의 해시와 한 쌍으로 묶여있기 때문에 데이터가 조금이라도 변조되면 서명은 즉시 무효가 됨 이 원리 덕분에 우리는 데이터가 서버가 승인한 원본 그대로임을 신뢰할 수 있음
마무리
오프체인 서명 검증은 온체인의 신뢰성과 오프체인의 유연성을 결합한 강력한 패턴임 이를 통해 가스비 소모 없이 복잡한 비즈니스 로직을 온체인 시스템에 안전하게 적용할 수 있음 서명 검증 과정 자체가 데이터의 무결성과 서명자의 신뢰성을 동시에 보장하므로, 다양한 분야에서 보안성과 효율성을 높이는 데 활용될 수 있음