For the complete documentation index, see llms.txt
컨트랙트 업데이트 가능성 결정하기
Midnight 네트워크에서 컨트랙트 업데이트 가능성이 어떻게 구현되는지, 그리고 DApp 개발자가 중단 없는 장기 컨트랙트 운영을 위해 이 기능을 이해해야 하는 이유를 설명합니다.
업데이트 가능성의 중요성
모든 블록체인에는 스마트 컨트랙트의 하위 호환성과 유지보수 없이 실행 가능한 기간에 영향을 미치는 고유한 요소가 있습니다. 실제로 업데이트는 프로토콜 요구사항보다 개발자 자신의 필요에 의해 이루어지는 경우가 더 많습니다 -- 보안 취약점 패치, 로직 버그 수정, 기능 확장 등입니다. Ethereum 같은 플랫폼에서는 많은 컨트랙트가 무기한 실행될 수 있지만, 상호작용하는 컨트랙트가 deprecated되면서 비실용적이 될 수 있고, 드물게 EVM 실행 프리미티 브의 런타임 시맨틱이 변경되어 영향을 받을 수도 있습니다. Solana에서는 프로그램이 기본적으로 변경 가능하며 생태계 차원에서 활발한 유지보수를 권장합니다. 온체인 런타임은 마이너 및 패치 릴리스 간 하위 호환성을 유지하지만, 주기적인 메이저 버전 업그레이드에서 프로그램 업데이트가 필요한 breaking change가 도입될 수 있습니다.
Midnight는 완전히 다른 패러다임을 도입합니다.
Midnight는 프라이버시 중심의 영지식 증명 기반 블록체인으로, 스마트 컨트랙트가 본질적으로 기반 암호 인프라 -- 증명 시스템과 회로 컴파일 파이프라인 -- 에 연결되어 있습니다. 프로토콜이 발전하면서 이 스택의 요소(proving scheme, circuit intermediate representation(ZKIR), verifier key 형식 등)가 업데이트될 수 있습니다. 이때 기존에 배포된 회로가 새로운 증명 생성 및 검증 규칙과 호환되지 않을 수 있으며, 증명을 더 이상 생성하거나 검증할 수 없어 컨트랙트가 사용 불가능해질 수 있습니다.
이를 해결하기 위해 Midnight는 Contract Maintenance Authority(CMA)를 통한 컨트랙트 업데이트 가능성을 제공합니다. CMA는 컨트랙트 회로의 verifier key를 추가하거나 제거할 수 있습니다. Midnight 컨트랙트의 각 회로는 온체인에서 암호화 verifier key로 표현되며, 이 키를 삽입하거나 제거하여 배포 후 회로 동작을 업데이트합니다.
업데이트 가능성 메커니즘의 주요 목적은 증명 시스템이 업데이트되더라도 컨트랙트가 계속 작동하도록 하는 것이지만, 비즈니스 로직 변경이나 버그 및 보안 문제 수정에도 활용할 수 있습니다.
Midnight에서 컨트랙트 업데이트 지원 여부와 방식을 결정하는 것은 DApp 아키텍처 설계의 핵심 단계이며, 나중에 고려할 문제가 아닙니다. 개발자는 증명 인프라의 일부 요소가 언제든 변경될 수 있다고 가정해야 합니다. 특히 중요한 장기 상태를 보유하면서 안전성이나 탈중앙성을 희생하지 않고는 새 컨트랙트로 마이그레이션하기 어려운 컨트랙트에서 중요합니다 -- 토큰 베스팅, 온체인 파생상품 거래 오더북, ID 시스템 등이 해당됩니다.
개발자는 증명 시스템 변경을 모니터링하고, 사용자 경험이 중단되지 않도록 적시에 컨트랙트를 업데이트해야 합니다. 업데이트 가능성을 활성화하지 않았다면, 비호환성이 발생하기 전에 새 컨트랙트로의 안전한 마이그레이션 전략을 설계하고 실행해야 합니다. 릴리스 호환성 매트릭스를 통해 증명 시스템 변경을 추적하고, 다가오는 릴리스가 컨트랙트에 영향을 미치는 경우 사전에 회로 업데이트를 계획하세요.
업데이트 가능성의 작동 방식
컨트랙트 업데이트 가능성은 verifier key의 삽입과 제거로 구현됩니다. Verifier key는 컴파일된 회로에서 생성되는 암호화 키로, 원장이 영지식 증명을 검증하는 데 사용합니다. 각 컨트랙트 진입점은 영지식 회로로 컴파일되며, 각 회로는 증명 시스템 버전별로 verifier key에 매핑됩니다. Contract Maintenance Authority(CMA)만이 이 키를 삽입하거나 제거할 수 있습니다.
업데이트 가능성은 기본적으로 비활성화되어 있으며, 컨트랙트 배포 시 명시적으로 활성화해야 합니다. 새로 배포된 컨트랙트는 기본적으로 threshold가 1인 빈 유지보수 위원회를 가지며, 이는 절대 충족될 수 없는 조건입니다. 따라서 배포자가 Contract Maintenance Authority(CMA) -- 공개 키의 멀티시그 위원회와 유지보수 업데이트에 공동 서명해야 하는 키 수의 threshold -- 를 명시적으로 설정하지 않는 한 컨트랙트는 영구적으로 업그레이드할 수 없습니다.
CMA는 다음 작업을 수행할 수 있습니다:
- CMA 교체: 컨트랙트에 연결된 CMA를 교체합니다. 새 authority가 현재 authority를 승계하며, 유지보수 제어를 이전하거나 빈 위원회를 설정하여 완전히 포기할 수 있습니다.
- Verifier key 제거: 컨트랙트 operation에서 특정 버전의 verifier key를 제거합니다. 제거된 버전을 사용하려는 이후 트랜잭션은 거부됩니다.
- 새 verifier key 삽입: 컨트랙트 operation에 특정 버전의 새 verifier key를 추가합니다. 새 기능을 추가하거나 새 증명 시스템 버전으로 기존 기능을 다시 활성화합니다. 해당 버전의 키가 이미 존재하면 안 되며, 존재하는 경우 먼저 제거해야 합니다.
이 메커니즘으로 회로 구현을 교체하여 컨트랙트의 주소, 상태, 잔액을 유지하면서 proving 동작을 변경할 수 있습니다.
유지보수 authority 운영
유지보수 authority를 운영하려면 다음이 필요합니다:
- 컨트랙트 배포 시 CMA 설정(위원회 공개 키 세트와 서명 threshold)
- 위원회 멤버의 개인 키 안전한 보관
- 유지보수 트랜잭션을 구성, 서명, 제출하기 위한 도구 사용
DApp 업그레이드를 제어하는 개인 키의 침해는 블록체인 프로토콜에서 가장 흔한 공격 벡터 중 하나입니다. 독립적인 주체 간에 제어를 분산하고 단일 주체가 통제하지 못하도록 하는 등 탈중앙화와 안전을 위한 모범 사례를 따르세요.
Midnight SDK(@midnight-ntwrk/midnight-js-contracts)는 컨트랙트 유지보수 authority 관리와 컨트랙트 업데이트를 지원합니다. Verifier key 삽입 및 제거, 유지보수 authority 교체를 위한 인터페이스를 제공하며, 모두 TypeScript API로 트랜잭션을 구성하고 제출합니다.
DeployContractOptions에서
signingKey 값을 제공하여 초기 컨트랙트 authority를 지정할 수 있습니다.
초기 서명 키는
sampleSigningKey로 생성합니다.
서로 다른 배포에 동일한 서명 키 세트를 지정하면 여러 컨트랙트에서 동일한 CMA를 재사용할 수 있습니다.
DeployedContract(deployContract가 반환)와
FoundContract(findDeployedContract가 반환) 모두 유지보수 인터페이스를 노출합니다.
circuitMaintenanceTx 속성을 사용하여 배포된 컨트랙트의 회로를 업데이트할 수 있습니다. 이 속성은 컨트랙트에 정의된 각 회로에 대해 하나의
CircuitMaintenanceTxInterface를 포함합니다.
insertVerifierKey로 새 verifier key를 추가하고, removeVerifierKey로 기존 키를 제거합니다.
마찬가지로,
contractMaintenanceTx 속성으로 배포된 컨트랙트의 유지보수 authority를 업데이트할 수 있습니다.
예제
다음 예제는 배포된 컨트랙트에 유지보수 작업을 수행하는 방법입니다.
providers와 compiledContract 설정 방법은 배포 가이드를 참고하세요.
import { findDeployedContract } from "@midnight-ntwrk/midnight-js-contracts";
// providers = { publicDataProvider, proofProvider, zkConfigProvider,
// privateStateProvider, walletProvider, midnightProvider }
const foundContract = await findDeployedContract(providers, {
contractAddress,
compiledContract
});
// Insert a new verifier key for the 'foo' circuit
await foundContract.circuitMaintenanceTx.foo.insertVerifierKey(newVerifierKey);
// Remove a verifier key for the 'foo' circuit
await foundContract.circuitMaintenanceTx.foo.removeVerifierKey();
// Replace the contract maintenance authority
await foundContract.contractMaintenanceTx.replaceAuthority(newSigningKey);