Privacy Authorization EIP-712 도메인

mechanism privacy

릴레이어가 대리 실행하는 프라이버시 요청을 서명할 때 오프체인에서 재현해야 하는 EIP-712 도메인과 struct 타입, 다이제스트 입력을 정의합니다.

IPrivacy*WithAuthorization 진입점은 특정 executor가 특정 effective sender를 대신하여 프라이버시 작업을 실행하도록 허가하는 서명을 받습니다. 이 서명은 프리컴파일이 고정한 EIP-712 다이제스트를 대상으로 하며, 도메인은 EIP712Domain(name="Maroo Privacy Precompile", version="1", chainId=<EVM chainId>, verifyingContract=<IPrivacy 주소>)이고 primary type은 PrivacyActionAuthorization입니다. EVM chain ID(uint256)와 Cosmos chain ID(해시로 struct에 포함)가 모두 다이제스트에 묶이므로, 한 네트워크에서 만든 서명을 다른 네트워크에서 재사용할 수 없습니다. 오프체인 서명자는 동일한 도메인과 struct 필드를 정확히 재구성해야 하며, 그렇지 않으면 프리컴파일이 EOA/ERC-1271/EIP-7702 signer mismatch 문자열 revert로 호출을 거절합니다.

아키텍처

flowchart LR
  Signer[Effective sender wallet]:::evm
  Relayer[Executor / relayer]:::evm
  Precompile[IPrivacy precompile\n0x...000b]:::precompile
  Signer -->|EIP-712 sign\nPrivacyActionAuthorization| Relayer
  Relayer -->|"transferWithAuthorization(request, auth)"| Precompile
  Precompile -->|recompute digest\nverify signer| Precompile
  classDef evm fill:#0096AA,stroke:#0096AA,color:#fff;
  classDef precompile fill:#FF8C50,stroke:#FF8C50,color:#fff;

Effective sender가 오프체인에서 EIP-712 PrivacyActionAuthorization에 서명하면, executor가 이를 프리컴파일에 제출하고 프리컴파일이 다이제스트를 재계산하여 복원된 서명자를 검증합니다.

EIP-712 도메인

도메인은 프리컴파일이 고정합니다. nameversion은 하드코딩된 문자열이고, chainId는 EVM chain ID(테스트넷은 450815, 메인넷은 815 — 클라이언트는 런타임에 eth_chainId로 읽어야 합니다)이며, verifyingContractIPrivacy 프리컴파일 주소 0x100000000000000000000000000000000000000b입니다. 여기에 Cosmos chain ID를 넣는 실수를 자주 하지만, 그러면 domainSeparator가 달라져 서명이 실패합니다.
// 클라이언트가 서명 대상으로 삼아야 하는 도메인입니다.
const domain = {
  name: "Maroo Privacy Precompile",
  version: "1",
  chainId: 450815n,                                        // EVM chain ID, eth_chainId로 조회
  verifyingContract: "0x100000000000000000000000000000000000000b",
} as const;

PrivacyActionAuthorization struct

Primary type는 11개 필드를 가집니다. authorizationEnvelopeSelectorauthorizationActionSelector는 각각 바깥쪽 *WithAuthorization 메서드와 안쪽 실제 액션 메서드(예: IPrivacy.transferWithAuthorization / IPrivacy.transfer)의 4바이트 selector입니다. cosmosChainIdHashkeccak256(bytes(ctx.ChainID()))입니다. Cosmos 체인ID 문자열의 해시이며, EVM 숫자 체인ID가 아니고 컴파일 타임 상수도 아닙니다. 제네시스 시점에 운영자가 정합니다(marood testnet init-files --chain-id …, 이 저장소 Makefile은 maroo-testnet로 띄웁니다). 따라서 클라이언트는 하드코딩하지 말고 런타임에 읽어야 합니다. 문자열이 틀리면 서명은 그럴듯하게 만들어지지만 서명자 복원에서 실패합니다. 어디서 읽어야 하는가: 이 값을 Solidity로 노출하는 프리컴파일은 없습니다(IPrivacy의 함수 9개는 모두 상태 변경 함수이고, 다른 프리컴파일의 getParams도 이 값을 담지 않습니다). 따라서 RPC URL과 마찬가지로 배포 설정에서 주입해야 합니다. 값이 틀렸을 때 두 경로는 다르게 실패합니다. withdrawwithdraw chain id mismatch: expected <chain>, got <yours>로 revert하며 체인이 원하는 값을 알려주지만, 인증 서명 쪽은 비교 대상 없이 서명자 복원만 실패합니다. 그래서 값 확인은 withdraw로 먼저 하는 편이 낫습니다. requestHash는 안쪽 request 튜플의 ABI 인코딩에 대한 keccak256입니다(아래 참고). batchIdbatchItemIndex는 배치가 아닌 호출에서는 0이며, batchTransferWithAuthorization의 각 item에서만 값이 채워집니다.
// 클라이언트가 viem / ethers에 등록해야 하는 EIP-712 타입 정의입니다.
const types = {
  PrivacyActionAuthorization: [
    { name: "authorizationEnvelopeSelector", type: "bytes4" },
    { name: "authorizationActionSelector",   type: "bytes4" },
    { name: "effectiveSender",               type: "address" },
    { name: "executor",                      type: "address" },
    { name: "nonce",                         type: "uint256" },
    { name: "deadline",                      type: "uint64" },
    { name: "cosmosChainIdHash",             type: "bytes32" },
    { name: "requestHash",                   type: "bytes32" },
    { name: "batchId",                       type: "bytes32" },
    { name: "batchItemIndex",                type: "uint64" },
    { name: "authorizationKind",             type: "uint8" },
  ],
} as const;

requestHash 계산 방법

requestHash 필드는 서명을 특정 내부 페이로드에 고정하여, 서명된 authorization을 다른 요청에 재바인딩할 수 없게 합니다. 대부분의 메서드에서는 keccak256(abi.encode(request))이며, request는 내부 액션의 유일한 튜플 인자(예: PrivacyTransferRequest, PrivacyWithdrawRequest)입니다. 예외는 single-proof 배치 전송입니다. 이 경우 서로 다른 인코딩과 혼동되지 않도록 메서드 selector를 앞에 붙여 keccak256(methodID || abi.encode(request))로 계산합니다. methodIDsingleProofBatchTransfer의 4바이트 selector입니다.
import { encodeAbiParameters, keccak256, toFunctionSelector } from "viem";

// PrivacyTransferRequest — 일반적인 경우입니다.
const transferRequestType = { /* PrivacyTransferRequest 필드 튜플 */ } as const;
const requestHash = keccak256(encodeAbiParameters([transferRequestType], [request]));

// PrivacySingleProofBatchTransferRequest — selector 접두어가 붙습니다.
const selector = toFunctionSelector("singleProofBatchTransfer((bytes,bytes,bytes[],(bytes,bytes,bytes,uint32,uint8,bytes,bytes,bytes,bytes,bytes,bytes)[],string,uint64,bytes,uint64))");
const batchRequestHash = keccak256(
  new Uint8Array([...hexToBytes(selector), ...hexToBytes(encodeAbiParameters([batchRequestType], [batchRequest]))]),
);

프리컴파일이 수행하는 검증

서명 검증에 앞서 프리컴파일은 다이제스트 입력을 검증하며, 실패 시 문자열 revert로 거절합니다. 이 문자열들은 타입 지정 커스텀 오류가 아니므로 decodeErrorResult로 디코드되지 않습니다. 문자열은 안정적으로 유지되며 다음과 같습니다: EVM chain ID must be a uint256, verifying contract is required, authorization selectors must be 4 bytes, authorization effective sender and executor are required, authorization nonce must be a uint256, authorization deadline is required, unsupported privacy authorization kind %d. 다이제스트 자체는 정상이나 복원한 서명자가 일치하지 않을 때는 authorizationKind에 따라 privacy EOA authorization signer mismatch, privacy ERC-1271 authorization rejected: %w, privacy EIP-7702 authorization signer mismatch 중 하나로 revert됩니다.
// 프리컴파일이 인식하는 authorizationKind 값입니다.
uint8 constant AUTH_KIND_EOA         = 1; // effectiveSender에 대한 ecrecover
uint8 constant AUTH_KIND_ERC1271     = 2; // effectiveSender에 isValidSignature() 호출
uint8 constant AUTH_KIND_EIP7702_EOA = 3; // ecrecover 후 delegated code 요구

// 그 외 값은 다음 문자열로 revert됩니다:
//   "unsupported privacy authorization kind %d"

배치 필드 — batchId와 batchItemIndex

단일 요청 호출(transferWithAuthorization, withdrawWithAuthorization, singleProofBatchTransferWithAuthorization)에서는 batchId가 zero hash이고 batchItemIndex는 0입니다. batchTransferWithAuthorization에서는 배치의 각 item이 공통 batchId와 자기 자신의 batchItemIndex를 담은 다이제스트에 서명합니다. 프로토콜은 multi-proof 배치를 MaxPrivacyMultiProofBatchItems = 20 item으로 제한합니다. 프리컴파일이 어떤 증명도 실행하기 전에 그 이상을 거절하므로, 클라이언트도 미리 동일한 상한을 강제해야 합니다.
ESC
검색어를 입력하세요