ERC-5453 모든 함수를 서명으로 승인 (Endorsement - Permit for Any Functions)

Last Call · 서명·인증 · 2022-08-12 작성

특정 동작 하나만 서명으로 허가하던 기존 방식과 달리, 어떤 함수 호출이든 한 트랜잭션 안에서 한 명 또는 여러 명의 서명(엔도스먼트, endorsement)으로 승인할 수 있게 하는 공통 규약입니다.

왜 필요한가

서명으로 동작을 허가하는 기존 표준은 쓰임이 좁습니다. ERC-20 토큰용 ERC-2612와 ERC-721 NFT용 ERC-4494는 각각 전송(transfer, safeTransferFrom)이라는 한 가지 동작만 허가할 수 있고 승인자도 한 명뿐이며 보통 허가(permit) 트랜잭션을 먼저 보내고 전송 트랜잭션을 또 보내는 두 단계가 필요합니다. 그래서 임의의 함수를 서명으로 승인하거나, 여러 사람의 승인을 모아 다중 서명이나 임계값 서명처럼 쓰거나, 다른 사람이 대신 트랜잭션을 내 주는 흐름을 만들려면 함수마다 별도 방식을 설계해야 했습니다. ERC-5453은 함수의 마지막 bytes 파라미터(ERC-5750)에 승인 데이터를 실어, 어떤 함수든 같은 트랜잭션 안에서 임의 개수의 승인자 서명으로 허가할 수 있는 일반 규약을 정합니다. 오프라인 서명, 공동 행동 승인, 누적 투표 같은 쓰임도 동기로 제시됩니다.

구현해야 하는 것

심층 설명

ERC-5750 extraData 위에 얹는 규약

이 표준을 따르는 함수는 ERC-5750에 따라 마지막 파라미터로 bytes extraData를 받아야 합니다(MUST). 호출자는 이 extraData에 GeneralExtensionDataStruct를 메모리 인코딩한 바이트를 넣습니다. 이 구조체는 erc5453MagicWord(식별용 매직 워드), erc5453Type(승인 데이터 형식), nonce(논스), validSince와 validBy(유효 기간), endorsementPayload(서명 묶음) 필드로 이루어집니다. 함수 시그니처를 새로 정의하지 않고 기존 호출의 마지막 파라미터에 승인 데이터를 끼워 넣는 방식이어서 어떤 함수든 같은 틀로 서명 승인을 받을 수 있습니다.

매직 워드와 타입 A·B

extraData를 해석할 때 erc5453MagicWord는 keccak256("ERC5453-ENDORSEMENT") 값이어야 하고, erc5453Type은 지원되는 값 중 하나여야 합니다. 원문은 ERC5453_TYPE_A = 1과 ERC5453_TYPE_B = 2를 정의합니다. 타입 A에서 endorsementPayload는 SingleEndorsementData 하나(승인자 주소 endorserAddress와 65바이트 서명 sig)를 abi 인코딩한 값이어야 하고, 타입 B에서는 SingleEndorsementData의 동적 배열을 abi 인코딩한 값이어야 합니다. Rationale에 따르면 타입 A는 ERC-2612나 ERC-4494와 같은 단일 승인 용도를, 타입 B는 임계값 승인 같은 다중 승인 용도를 맡으며 더 복잡한 승인 형식은 새 타입 값을 정의해 확장할 수 있습니다.

서명 대상은 EIP-712 ValidityBound 다이제스트

각 서명 sig는 endorserAddress에 대응하는 개인키로 validityDigest를 ECDSA(secp256k1) 서명한 값이어야 합니다. validityDigest는 ValidityBound(bytes32 functionParamStructHash, uint256 validSince, uint256 validBy, uint256 nonce) 구조체의 해시를 EIP-712 hashTypedDataV4로 감싼 값입니다. functionParamStructHash는 keccak256(abi.encodePacked(keccak256(bytes(_functionStructure)), _functionParamPacked))로 계산하며, _functionStructure는 "function methodName(type1 param1, type2 param2, ...)" 형태의 함수 구조 문자열이고 _functionParamPacked는 각 파라미터의 인코딩을 이어 붙인 값입니다. 어떤 함수를 어떤 인자로 호출하는지가 서명 안에 통째로 묶이므로, 서명 하나는 특정 호출 내용에만 유효합니다.

검증 규칙과 임계값

단일 승인은 endorserAddress == ecrecover(validityDigest, signature)가 성립하거나 EIP1271(endorserAddress).isValidSignature(validityDigest, signature)가 ERC1271.MAGICVALUE를 돌려줄 때 유효한 것으로 간주해야 합니다(MUST). 즉 외부 소유 계정의 서명과 컨트랙트 계정(ERC-1271)의 서명을 모두 받을 수 있습니다. 타입 B의 endorsementPayload에 대해서는 구현체가 유효해야 하는 승인 개수의 임계값을 둘 수 있습니다(MAY). 참조 구현은 서명 길이가 65바이트인지 확인한 뒤 OpenZeppelin의 SignatureChecker로 검증하고 onlyEndorsed 수정자로 함수 본문 실행 전에 이 검증을 수행합니다.

유효 기간 validSince와 validBy

validSince와 validBy는 둘 다 경계를 포함(inclusive)합니다. 구현자는 블록 번호와 타임스탬프 중 어느 쪽을 쓸지 선택할 수 있고(MAY), 어느 쪽인지 알 수 있는 방법을 제공해야 합니다(SHOULD). Rationale은 만료 시점뿐 아니라 시작 시점까지 두 값을 모두 둔 이유로 만료 처리의 유연성을 최대화하기 위해서라고 설명합니다. ERC-5081이 채택되면 EVM이 이를 네이티브로 지원할 수 있지만 당분간 채택되지 않을 것이므로, 스마트 컨트랙트 수준에서 지원하도록 두 값을 프로토콜에 넣었다고 밝힙니다.

참조 구현 세 가지

원문은 추상 컨트랙트 AERC5453Endorsible과 이를 활용한 두 예제를 제시합니다. EndorsableERC721은 mint(address _to, uint256 _tokenId, bytes calldata _extraData) 함수에 onlyEndorsed 수정자를 붙여 ERC-4494와 비슷한 동작을 구현합니다. ThresholdMultiSigForwarder는 forward(address _dest, uint256 _value, uint256 _gasLimit, bytes calldata _calldata, bytes calldata _extraData)로 임계값 다중 서명 승인을 거쳐 원격 컨트랙트를 호출하는, Gnosis-Safe 지갑과 비슷한 동작을 보여 줍니다. 이때 호출 데이터는 keccak256(_calldata) 해시 형태로 서명 대상에 포함됩니다.

하위 호환성과 제안 상태

이 설계는 함수가 마지막에 bytes calldata extraData를 받는다는 가정 위에 서 있으며 이 가정은 ERC-721, ERC-1155를 비롯한 여러 ERC와 호환됩니다. ERC-20처럼 호환되지 않는 표준도 래퍼 컨트랙트를 쓰거나 프록시 업그레이드를 하는 방식으로 지원하도록 고칠 수 있습니다. 이 제안의 상태는 Last Call(최종 의견 수렴, 마감 2023-09-27)로, 원문 기준으로 최종 확정 직전의 검토 단계에 있습니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs