프라이버시 프리컴파일

component privacy

0x100000000000000000000000000000000000000b의 은닉 풀 OKRW 프리컴파일입니다. Groth16 증명 아래에서 노트를 예치·전송·인출하며, 모든 실패 모드에 대해 타입 지정 revert 데이터를 반환합니다.

0x100000000000000000000000000000000000000b의 프라이버시 프리컴파일은 OKRW 은닉 풀을 구현합니다. 호출자는 투명 OKRW를 deposit하여 노트를 커밋하거나, Groth16 증명 아래에서 다른 수신자에게 노트를 transfer하거나, 노트를 투명 EOA로 withdraw할 수 있습니다. 각 메서드에는 EIP-712 기반 *WithAuthorization 릴레이 변형과 배치 변형이 함께 제공됩니다. 상태를 변경하는 모든 호출은 이를 감싼 EVM 트랜잭션이 거치는 전역 정책에 더해, 프리컴파일 경계에서 프라이버시 프리컴파일 주소에 바인딩된 컨트랙트 정책으로 호출 실행 전과 실행 후에 PCL 평가를 받습니다. 프리컴파일 경계에서 PCL이 거절하면 타입 지정 IPcl 오류로 revert됩니다. 그 밖의 실패는 이제 IPrivacy에 선언된 타입 지정 커스텀 오류로 revert됩니다. IPrecompile에서 상속되는 공통 오류, 요청 수명주기 계열(PrivacyNonPayable, PrivacyRequestExpired, PrivacyBatchSizeOutOfRange, PrivacyNativeDenomMismatch), 인증 계열(PrivacyInvalidAuthorizationField, PrivacyAuthorizationExpired, PrivacyAuthorizationNonceUsed, PrivacyAuthorizationSignerMismatch, PrivacyAuthorizationRejected, PrivacyInvalidAuthorizationMagic), 은닉 상태 계열(PrivacyDuplicateNullifier, PrivacyNullifierAlreadySpent, PrivacyDuplicateCommitment, PrivacyCommitmentAlreadyExists, PrivacyMerkleCapacityExceeded, PrivacyTxLimitExceeded, PrivacySenderTxLimitExceeded)이 포함됩니다.

아키텍처

flowchart LR
  User["Wallet / dApp"] -->|"deposit / transfer / withdraw"| Priv["IPrivacy<br/>0x…000b"]
  User -->|"*WithAuthorization<br/>(relayed)"| Relayer["Relayer"]
  Relayer -->|"executor tx"| Priv
  Priv -->|"preflight"| PCL["IPcl"]
  Priv -->|"revert with typed IPrivacy error"| User
  classDef evm fill:#0096AA,stroke:#0096AA,color:#fff;
  classDef precompile fill:#FF8C50,stroke:#FF8C50,color:#fff;
  class User,Relayer evm;
  class Priv,PCL precompile;

상태를 변경하는 모든 호출은 실행 전후에 PCL 검사를 받은 뒤 은닉 상태를 커밋하거나 revert합니다. PCL이 거절하면 타입 지정 IPcl 오류로 revert합니다.

노출되는 표면

상태를 변경하는 아홉 메서드가 세 쌍과 배치로 구성되어 있습니다.

형태직접 호출릴레이 변형
값 유입deposit (payable)—
노트 간 이동transfertransferWithAuthorization
값 유출withdrawwithdrawWithAuthorization
배치batchTransfer, singleProofBatchTransferbatchTransferWithAuthorization, singleProofBatchTransferWithAuthorization

payable은 deposit뿐입니다. 다른 메서드는 첨부된 값이 있으면 PrivacyNonPayable(methodName)으로 거절합니다. 모든 은닉 금액은 64비트이므로, 한 번의 연산으로 옮길 수 있는 최대치는 18.446744073709551615 OKRW입니다. 더 큰 잔액은 여러 노트로 나누어 보관합니다.

요청 수명주기 오류

요청 자체가 위반하는 주변 조건입니다.

  • PrivacyNonPayable(string methodName) — payable이 아닌 프라이버시 메서드에 msg.value를 첨부했습니다.
  • PrivacyRequestExpired(uint64 expiresAtUnix) — 요청의 expiresAtUnix가 이미 지났습니다. mempool 지연을 감안해 몇 블록 뒤로 설정합니다.
  • PrivacyBatchSizeOutOfRange(uint256 count, uint256 max) — batchTransfer / singleProofBatchTransfer가 허용 크기를 벗어났습니다.
  • PrivacyNativeDenomMismatch(string got, string expected) — 요청의 denom이 체인의 네이티브 denom(atokrw)이 아닙니다.


@maroo-chain/contracts의 iPrivacyAbi에는 이 네 오류와 모든 마루 프리컴파일이 상속하는 공유 IPrecompile 오류가 함께 들어 있으므로, 이 ABI로 디코드합니다. 네 오류만 직접 적은 ABI로는 InvalidAmount 같은 공유 revert를 디코드할 수 없습니다.
import { BaseError, ContractFunctionRevertedError, decodeErrorResult } from "viem";
import { iPrivacyAbi } from "@maroo-chain/contracts/abi/precompiles/privacy/IPrivacy";

try {
  await client.writeContract({ /* 프라이버시 호출 */ });
} catch (err) {
  // viem은 revert 데이터를 err가 아니라 원인 체인의 ContractFunctionRevertedError에 둡니다.
  const revert = err instanceof BaseError ? err.walk((e) => e instanceof ContractFunctionRevertedError) : null;
  if (revert instanceof ContractFunctionRevertedError && revert.raw) {
    const decoded = decodeErrorResult({ abi: iPrivacyAbi, data: revert.raw });
    console.error(decoded.errorName, decoded.args);
  } else {
    throw err; // revert가 아닌 오류(네트워크 오류, 잘못된 ABI)는 그대로 던집니다
  }
}

인증 오류 — *WithAuthorization 릴레이 경로

릴레이 변형(transferWithAuthorization, withdrawWithAuthorization, singleProofBatchTransferWithAuthorization, batchTransferWithAuthorization)은 실행자와 분리된 EIP-712 서명을 받습니다. 서명 페이로드는 privacy-authorization-eip712-domain에서 확인합니다. 와이어 struct는 6개 필드이지만 서명 대상 typeHash는 11개 필드로 다릅니다.

  • PrivacyInvalidAuthorizationField(string field) — 필수 필드가 없거나 0입니다. effectiveSender나 executor가 0 주소이거나 deadline이 0인 경우이며, field가 해당 필드 이름을 담습니다.
  • PrivacyAuthorizationExpired(uint64 deadline) — deadline이 이미 지났습니다.
  • PrivacyAuthorizationNonceUsed(address sender, uint256 nonce) — effective-sender nonce가 이미 소모되었습니다.
  • PrivacyAuthorizationSignerMismatch(address expected, address got) — 복원된 서명자가 effective sender와 일치하지 않습니다.
  • PrivacyAuthorizationRejected(uint8 authorizationKind) — 지정한 authorizationKind로 서명을 확인할 수 없습니다. kind가 1(EOA), 2(ERC-1271), 3(EIP-7702)이 아니거나, 서명에서 서명자를 복원할 수 없거나, 계정의 코드가 kind와 맞지 않거나(컨트랙트에 EOA kind, 코드 없는 계정에 ERC-1271, delegation 없는 EIP-7702), ERC-1271 호출이 실패한 경우입니다.
  • PrivacyInvalidAuthorizationMagic(bytes4 magic) — ERC-1271 검증자가 인식되지 않는 매직 값을 반환했습니다.

은닉 상태 오류

은닉 상태 기계 자체에서 발생하는 실패입니다.

  • PrivacyDuplicateNullifier() — 같은 nullifier가 한 요청에 두 번 등장합니다.
  • PrivacyNullifierAlreadySpent() — 해당 nullifier가 이미 온체인에 기록되어 있습니다(이중 지출 시도).
  • PrivacyDuplicateCommitment() — 같은 출력 커밋먼트가 한 요청에 두 번 등장합니다.
  • PrivacyCommitmentAlreadyExists() — 출력 커밋먼트가 이미 Merkle 트리에 존재합니다.
  • PrivacyMerkleCapacityExceeded(uint256 count) — 요청의 커밋먼트를 추가하면 트리 용량을 초과합니다.
  • PrivacyTxLimitExceeded(uint256 limit) — 블록당 체인 전역 프라이버시 트랜잭션 한도를 초과했습니다.
  • PrivacySenderTxLimitExceeded(address sender, uint256 limit) — 발신자별 프라이버시 트랜잭션 한도를 초과했습니다.


대부분의 경우 클라이언트가 노트 집합을 다시 스캔해 재시도해야 한다는 신호입니다. 중복 nullifier는 보통 풀에 대한 오래된 뷰를 의미합니다.

평문 문자열 실패(Error(string))

일부 검사에는 타입 지정 IPrivacy 오류가 없어서, Solidity Error(string) 형태의 평문 이유로 revert됩니다. EVM 호출자가 받을 수 있는 예로 "expiresAtUnix overflows int64"(expiresAtUnix가 2^63 이상), "privacy batchId must be non-zero"(batchId가 0인 배치 호출), "duplicate privacy batchId in EVM transaction"(같은 호출자가 한 EVM 트랜잭션 안에서 batchId를 다시 사용)가 있습니다. 이 revert도 다른 revert처럼 revert 데이터를 담고 있어서, decodeErrorResult는 이를 errorName: "Error"로 반환하고 이유는 args[0]에 담깁니다(viem은 ContractFunctionRevertedError의 reason으로도 제공합니다). 따라서 catch 분기 하나에서 두 형태를 모두 처리할 수 있으며, 데이터가 있는지가 아니라 errorName으로 분기합니다. 인증 필드의 누락이나 0 값은 여기에 해당하지 않습니다. effectiveSender나 executor가 0 주소이거나 deadline이 0이면 위의 타입 지정 오류 PrivacyInvalidAuthorizationField(field)로 revert됩니다.
ESC
검색어를 입력하세요