Privacy Authorization EIP-712 도메인
릴레이어가 대리 실행하는 프라이버시 요청을 서명할 때 오프체인에서 재현해야 하는 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 도메인
name과 version은 하드코딩된 문자열이고, chainId는 EVM chain ID(테스트넷은 450815, 메인넷은 815 — 클라이언트는 런타임에 eth_chainId로 읽어야 합니다)이며, verifyingContract는 IPrivacy 프리컴파일 주소 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
authorizationEnvelopeSelector와 authorizationActionSelector는 각각 바깥쪽 *WithAuthorization 메서드와 안쪽 실제 액션 메서드(예: IPrivacy.transferWithAuthorization / IPrivacy.transfer)의 4바이트 selector입니다. cosmosChainIdHash는 keccak256(bytes(ctx.ChainID()))이며, 테스트넷에서는 keccak256("maroo-testnet")입니다. requestHash는 안쪽 request 튜플의 ABI 인코딩에 대한 keccak256입니다(아래 참고). batchId와 batchItemIndex는 배치가 아닌 호출에서는 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))로 계산합니다. methodID는 singleProofBatchTransfer의 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]))]),
); 프리컴파일이 수행하는 검증
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으로 제한합니다. 프리컴파일이 어떤 증명도 실행하기 전에 그 이상을 거절하므로, 클라이언트도 미리 동일한 상한을 강제해야 합니다.