Skip to main content
For the complete documentation index, see llms.txt

OpenZeppelin contracts for Compact

OpenZeppelin Contracts for Compact는 Midnight을 위해 Compact으로 작성한 재사용 가능한 스마트 컨트랙트 빌딩 블록 라이브러리입니다. OpenZeppelin의 Solidity 컨트랙트 라이브러리 구조를 그대로 따르며, 커스텀 컨트랙트로 조합할 수 있는 토큰 표준, 접근 제어 패턴, 보안 기본 요소를 제공합니다.

라이브러리는 모듈식입니다. 베이스 컨트랙트를 상속하는 대신, 개별 모듈을 prefix와 함께 자신의 .compact 소스로 import하고 그 circuit을 빌딩 블록으로 호출합니다.

Experimental, not for production

라이브러리는 현재 0.2.0 버전입니다. 프로젝트의 README는 이 코드를 매우 실험적이라고 설명하며, 유지보수자들은 다음과 같이 분명히 밝히고 있습니다. DO NOT USE IT IN PRODUCTION. 저장소의 audits 폴더에는 0.1.0 버전에 대한 OpenZeppelin의 감사 보고서가 있습니다. 0.2.0 버전에는 그 감사 이후에 적용된 변경 사항이 포함되어 있습니다.

이 컨트랙트들은 프로토타이핑, 학습, 그리고 Preview·Preprod 개발에만 사용하세요.

Prerequisites​

라이브러리를 사용하기 전에 다음을 갖췄는지 확인하세요.

Compatibility​

아래 표는 라이브러리가 대상으로 하는 Compact 컴파일러와 언어 버전입니다. 통합하기 전에 로컬 toolchain과 대조해 확인하세요.

ComponentVersionNotes
Library version0.2.0yarn add @openzeppelin/compact-contracts로 설치되는 릴리스입니다. 빠른 변경이 예상됩니다.
Compact compiler0.29.0라이브러리의 CI 배지로 고정.
Compact language>= 0.21.0모든 소스 파일의 pragma 기준.
Token amountsUint<128> (Uint<256> 아님)Midnight 인코딩 한계. Uint<256>은 Compact 컴파일러가 지원하지 않습니다.
Midnight NetworkPreprod, Preview 전용Mainnet 대상 아님.
Compiler version mismatch

0.2.0 버전에는 컴파일러 0.29.0 이상이 필요합니다. OpenZeppelin은 0.29.0으로 빌드하고 테스트하지만, 공식 Midnight Toolchain은 그보다 새로운 버전을 출시했습니다. pragma >= 0.21.0이 소스 수준의 호환성을 유지해 주며, 이 페이지의 예제는 컴파일러 0.31.1, 0.34.0, 0.35.0에서도 컴파일됩니다.

컴파일러는 컴파일된 컨트랙트가 어느 ledger version을 대상으로 할지도 결정합니다. 컴파일러 0.29.0은 ledger version 7을, 0.31.1은 ledger version 8을 대상으로 합니다. 컴파일러 0.34.0과 0.35.0은 ledger version 9를 대상으로 합니다. 배포하려면 compatibility matrix가 대상 네트워크에 대해 명시하는 toolchain으로 컴파일하세요. 문제를 제보하기 전에 로컬의 compact compile --version을 확인하세요.

npm beta 태그와 저장소의 main 브랜치에는 컴파일러 0.34.0과 pragma language_version >= 0.26.0을 대상으로 하는 프리릴리스가 있습니다. 이 페이지에서는 다루지 않습니다.

Modules​

라이브러리는 모듈을 네 가지 계열로 나눕니다. 대부분의 모듈은 조합을 위해 Utils(zero-address 및 Either 헬퍼)를 import합니다.

Access control · access/​

이 모듈들은 컨트랙트의 특정 circuit을 누가 호출할 수 있는지를 제어합니다. 가장 단순한 것부터 복잡한 것 순으로, 단일 소유자, 역할 기반, 그리고 그 프라이버시 보존 변형 순으로 나열되어 있습니다.

ModulePurposeOpenZeppelin docs
Ownable단일 소유자 권한. 소유자를 Either<Bytes<32>, ContractAddress>로 저장합니다. 소유권 이전, 포기, 그리고 contract-address 소유자를 위한 safe/unsafe 변형을 지원합니다.Guide · API
AccessControladmin 계층을 갖춘 역할 기반 권한. 역할은 Bytes<32> 식별자입니다. 각 역할에는 부여와 회수를 제어하는 설정 가능한 admin 역할이 있습니다.Guide · API
ShieldedAccessControl프라이버시 보존형 역할 기반 접근 제어. 역할 commitment를 MerkleTree<20>에 저장하고 nullifier로 회수하므로, 역할 소속을 영지식으로 증명할 수 있습니다.Guide · API
ZOwnablePK프라이버시 보존형 단일 소유자 권한. 소유자를 public key 대신 commitment 해시로 저장하므로, 온체인 데이터가 소유자가 누구인지 드러내지 않습니다.Ownable과 함께 문서화됨

Security · security/​

이 모듈들은 컨트랙트에 라이프사이클 및 운영 가드를 적용합니다.

ModulePurposeOpenZeppelin docs
Initializable일회성 초기화 가드. 컨트랙트가 두 번 이상 초기화되는 것을 막습니다. 라이브러리의 다른 모듈은 이것을 import하지 않으며, 초기화가 필요한 모듈은 각자 자체 플래그를 추적합니다. OpenZeppelin은 최상위 컨트랙트에서만 사용할 것을 권장합니다.Guide · API
Pausable비상 정지 스위치. assertNotPaused() 뒤에서 circuit 실행을 통제합니다. _pause()와 _unpause()는 직접 만든 권한 로직으로 감싸세요.Guide · API

Tokens · token/​

이 모듈들은 Compact에 맞게 조정한 표준 토큰 인터페이스를 구현합니다.

ModulePurposeOpenZeppelin docs
FungibleTokenERC-20 방식의 대체 가능 토큰. transfer, approve, transferFrom, _mint, _burn을 지원합니다. 잔액은 Uint<128>을 사용합니다(Uint<256> 아님 - Compatibility 참고). safe·unsafe transfer 변형을 포함합니다.Guide · API
NonFungibleTokenERC-721 방식의 대체 불가능 토큰. 소유권, 승인, operator 승인, token URI를 추적합니다. 토큰 ID는 Uint<128>을 사용합니다.Guide · API
MultiTokenERC-1155 방식의 멀티 토큰. 하나의 컨트랙트에서 여러 토큰 유형을 관리합니다. 일괄 연산은 지원하지 않습니다(Compact에 동적 배열이 없습니다).Guide · API

Utilities · utils/​

타입 안전성과 입력 검증을 위한 헬퍼 함수입니다.

ModulePurposeOpenZeppelin docs
UtilsEither<ZswapCoinPublicKey, ContractAddress> 값을 다루는 순수 헬퍼. zero 검사, 동등성 검사, 그리고 조작된 입력 공격을 막는 canonicalize를 포함합니다.Guide · API

Installation​

라이브러리는 npm 패키지 @openzeppelin/compact-contracts로 제공됩니다. latest 태그는 이 페이지에서 설명하는 릴리스인 0.2.0 버전을 설치합니다.

새 프로젝트에 라이브러리를 추가하려면 프로젝트를 만들고 패키지를 설치하세요.

mkdir my-project && cd my-project
yarn init -y
yarn add @openzeppelin/compact-contracts

패키지에는 각 모듈의 Compact 소스가 access/, security/, token/, utils/ 폴더에 들어 있습니다.

Yarn Plug'n'Play

컨트랙트의 import는 node_modules 안의 파일을 가리킵니다. Yarn 2 이상은 기본적으로 Plug'n'Play를 사용하며, 이 폴더를 만들지 않습니다. 설치 후 프로젝트에 node_modules 폴더가 없으면 yarn config set nodeLinker node-modules를 실행한 뒤 yarn install을 실행하세요. 또는 npm install @openzeppelin/compact-contracts로 패키지를 설치하세요.

Compose a contract​

개별 모듈을 prefix와 함께 .compact 소스로 import한 뒤, 그 circuit을 빌딩 블록으로 호출하세요. prefix Pausable_; 관례에 유의하세요. prefix 자체가 _로 끝납니다. 내부적으로 underscore가 붙은 _pause circuit을 호출하면 Pausable__pause()가 되는데, 이중 underscore는 의도된 것입니다.

pragma language_version >= 0.21.0;

import CompactStandardLibrary;
import "./node_modules/@openzeppelin/compact-contracts/access/Ownable"
prefix Ownable_;
import "./node_modules/@openzeppelin/compact-contracts/security/Pausable"
prefix Pausable_;
import "./node_modules/@openzeppelin/compact-contracts/token/FungibleToken"
prefix FungibleToken_;

constructor(
_name: Opaque<"string">,
_symbol: Opaque<"string">,
_decimals: Uint<8>,
_recipient: Either<Bytes<32>, ContractAddress>,
_amount: Uint<128>,
_initOwner: Either<Bytes<32>, ContractAddress>,
) {
Ownable_initialize(_initOwner);
FungibleToken_initialize(_name, _symbol, _decimals);
FungibleToken__mint(_recipient, _amount);
}

export circuit transfer(
to: Either<Bytes<32>, ContractAddress>,
value: Uint<128>,
): Boolean {
Pausable_assertNotPaused();
return FungibleToken_transfer(to, value);
}

export circuit pause(): [] {
Ownable_assertOnlyOwner();
Pausable__pause();
}

export circuit unpause(): [] {
Ownable_assertOnlyOwner();
Pausable__unpause();
}

export pure circuit computeAccountId(secretKey: Bytes<32>): Bytes<32> {
return Ownable_computeAccountId(secretKey);
}
Account identifiers and witnesses

각 주소의 Bytes<32> 쪽에는 지갑 public key가 아니라 계정 식별자가 담깁니다. Ownable과 FungibleToken은 이를 호출자 비밀 키의 persistentHash로 유도하며, 각각 이를 계산하는 pure computeAccountId circuit을 export합니다. 이 예제는 Ownable의 circuit을 자체 computeAccountId circuit으로 감쌉니다. DApp은 컴파일된 출력에서 pureCircuits.computeAccountId(secretKey)를 호출해, 생성자와 transfer가 받는 식별자를 얻을 수 있습니다. 컴파일된 컨트랙트는 witness wit_OwnableSK와 wit_FungibleTokenSK를 선언하며, DApp이 이를 구현해 해당 32바이트 비밀 키를 반환합니다. 패키지에는 witness 구현이 포함되어 있지 않습니다. 자세한 내용은 OpenZeppelin의 Ownable guide를 참고하세요.

Compile​

Compact 컴파일러를 실행해 컨트랙트로부터 TypeScript 바인딩과 ZK circuit 아티팩트를 생성합니다.

compact compile MyContract.compact artifacts/MyContract

컴파일에 성공하면 각 circuit과 그 proving-system 크기가 함께 표시됩니다. 이 출력은 컴파일러 0.31.1에서 나온 것이며, row 수는 컴파일러 버전마다 다릅니다.

Compiling 3 circuits:
circuit "pause" (k=13, rows=2362)
circuit "transfer" (k=13, rows=3855)
circuit "unpause" (k=13, rows=2359)
Overall progress [====================] 3/3

pure computeAccountId circuit은 증명이 필요 없으므로 출력에 나열되지 않습니다.

k는 domain 크기(circuit이 2^k개의 row에 배치됩니다)이고, rows는 그중 circuit이 실제로 사용하는 row 수입니다. circuit이 작을수록 증명과 검증이 빠릅니다. 더 자세한 설명은 OpenZeppelin의 ZK Circuits 101을 참고하세요.

Patterns to know​

다음 패턴들은 라이브러리 전반에 적용되며, 컨트랙트를 어떻게 구성할지에 영향을 줍니다.

  • 위임을 통한 모듈식 조합: OpenZeppelin의 Module/컨트랙트 패턴은 이 라이브러리가 따르기를 기대하는 공식 구조입니다. 각 모듈은 두 종류의 circuit을 export합니다. 하나는 leading underscore가 없는 external circuit(transfer, approve 등)으로, 그대로 노출해도 안전합니다. 다른 하나는 leading underscore가 붙은 public circuit(_mint, _burn 등)으로, 직접 만든 컨트랙트 로직으로 감싸는 조합용 빌딩 블록입니다. internal 헬퍼는 export되지 않은 채로 남습니다. 컨트랙트는 모듈을 prefix와 함께 import해 그 circuit들을 연결합니다. 생성자 밖에서 initialize()를 호출하거나 circuit을 그대로 재export해서는 안 됩니다.

  • prefix를 통한 조합: import ... prefix X_; 문법은 각 모듈에 네임스페이스를 부여해, 식별자 충돌 없이 여러 모듈이 한 컨트랙트에 공존할 수 있게 합니다. 이름이 _로 시작하는 circuit은 호출 지점에서 X__name 형태로 나타납니다.

  • 초기화는 명시적이다: Ownable, ZOwnablePK, ShieldedAccessControl, 그리고 세 가지 토큰 모듈은 각각 자체 초기화 플래그를 추적합니다. 각 모듈은 생성자에서 명시적인 initialize 호출을 요구합니다. 그 호출이 실행되기 전까지, 가드가 걸린 circuit은 런타임에 contract not initialized 오류로 실패합니다.

  • safe circuit은 contract-address 수신자를 거부한다: 0.2.0 버전은 컴파일러 0.29.0을 대상으로 하는데, 이 컴파일러는 cross-contract calls(ledger version 9와 함께 도입됨)보다 앞섭니다. 따라서 모든 모듈의 safe 경로(예: Ownable.transferOwnership, AccessControl.grantRole, FungibleToken.transfer)는 ContractAddress 수신자를 거부합니다. 각 모듈은 safe 변형과 함께, 위험이 드러나도록 명시적으로 이름 붙인 _unsafe* circuit을 짝지어 제공합니다. 유지보수자들은 라이브러리가 cross-contract call을 지원하게 되면 unsafe 변형을 deprecate할 계획입니다.

  • 호출자 권한 확인에는 두 가지 방식이 있다: Ownable, AccessControl, 그리고 토큰 모듈들은 계정 식별자로 호출자를 식별합니다. 이 식별자는 각 모듈이 wit_OwnableSK 같은 자체 witness에서 읽어 오는 비밀 키의 persistentHash입니다. 이들은 derived-identity 패턴에 맞춰 ownPublicKey()에 의존하지 않습니다. 다만 그 가이드의 예제와 달리, 이 해시에는 도메인 구분자가 없습니다. 어느 쪽도 컨트랙트별 salt를 쓰지 않으므로, 같은 키를 사용하는 모든 컨트랙트에서 식별자가 동일합니다. 식별자가 서로 연결되지 않아야 한다면 컨트랙트마다 다른 키를 사용하세요. ZOwnablePK와 ShieldedAccessControl은 witness에서 유도한 비밀을 해싱·instance salt와 결합해 commitment로 호출자를 검증하며, public key를 온체인에 절대 노출하지 않습니다. ZOwnablePK는 여전히 ownPublicKey()를 소유자 식별자로 해싱하므로, 호출자를 인증하는 것은 그 wit_secretNonce witness에서 오는 비밀 nonce입니다. 컨트랙트가 소유자나 역할 보유자가 누구인지 드러내지 않아야 한다면 프라이버시 보존형 변형을 권장합니다.

  • Either<Bytes<32>, ContractAddress>는 표준 주소 타입이다: Utils.canonicalize 헬퍼는 Either에서 사용하지 않는 쪽을 0으로 만들어, 양쪽 모두에 데이터가 담기는 조작된 입력 공격을 막습니다.

Resources​

자세한 내용은 다음 링크를 참고하세요.

Reporting issues​

라이브러리 코드 관련 문제는 OpenZeppelin 트래커에 제보하세요. 보안 사항 제보는 security@openzeppelin.com으로 이메일을 보내세요.

이 문서 페이지 관련 문제는 Midnight docs 저장소에 제보하세요.