ERC-6809 키 바운드 NFT 표준 (Non-Fungible Key Bound Token)
Final · NFT·멀티토큰 · 2023-03-31 작성
NFT 자체에 선택형 보안 기능을 내장한 ERC-721 확장입니다. 별도의 키 지갑 2개가 전송과 승인을 허가해야만 NFT를 옮길 수 있어 보유 지갑이 털려도 자산을 지킬 수 있습니다.
왜 필요한가
셀프커스터디(자기 보관) 자산은 모든 책임이 개인에게 있습니다. 시드 문구나 개인키가 유출되면 지갑 안의 NFT를 누구도 되찾아 줄 수 없습니다. 원문에 따르면 2021년 1월부터 2022년 3월까지 미국 연방거래위원회에 접수된 암호화폐 사기 신고는 46,000건이 넘고 소비자 피해액은 10억 달러를 초과했습니다. 기존 보안 수단에도 한계가 있습니다. 중앙화 서비스는 계정 생성과 개인정보 제출이 필요하고 지역 제한을 받을 수 있으며 하드웨어 지갑 같은 분산형 수단은 구매, 배송 같은 물류 문제가 따릅니다. 이 표준은 지갑이 아니라 토큰 자체에 보안을 넣는 식으로 접근합니다. 보안 기능을 켜 두면 시드 문구가 공개되더라도 개별 자산이 보호되고 기능을 켜지 않으면 일반 ERC-721과 똑같이 동작합니다.
구현해야 하는 것
event AccountSecured(address indexed _account, uint256 _noOfTokens);: 계정이 addBindings로 보안을 활성화했을 때 발생합니다. 현재 보유 수량이 함께 기록됩니다.event AccountResetBinding(address indexed _account);: 보유자가 resetBindings로 키 지갑 설정을 초기화했을 때 발생합니다.event SafeFallbackActivated(address indexed _account);: 키 지갑이 safeFallback을 호출해 자산을 다른 키 지갑으로 옮겼을 때 발생합니다.event Ingress(address _account, uint256 _tokenId);: 계정이 새로 토큰 보유자가 됐을 때 발생합니다.event Egress(address _account, uint256 _tokenId);: 계정이 토큰을 모두 전송해 더 이상 보유자가 아니게 됐을 때 발생합니다.function getBindings(address _account) external view returns (AccountHolderBindings memory);: 해당 계정에 묶인 키 지갑 2개를 구조체로 돌려줍니다.function resetBindings() external returns (bool);: 키 지갑에서 호출하면 보유 계정의 키 지갑 설정을 초기화합니다. 키 지갑이 뚫렸을 때 씁니다.function safeFallback() external returns (bool);: 키 지갑에서 호출하면 보유 계정의 모든 토큰을 다른 키 지갑으로 옮깁니다. 보유 지갑이 뚫렸을 때 씁니다.function getTransferableFunds(address _account) external view returns (TransferConditions memory);: 해당 계정에 설정된 전송 허가 조건을 구조체로 돌려줍니다.function isSecureWallet(address _account) external returns (bool);: 해당 계정이 보안 기능을 켠 지갑인지 알려줍니다.function isSecureToken(uint256 _tokenId) external returns (bool);: 해당 토큰이 보안 기능이 켜진 계정에 속해 있는지 알려줍니다.
심층 설명
3지갑 체계와 addBindings
addBindings(_keyWallet1, _keyWallet2)는 보내는 계정에 키 지갑 2개를 동시에 묶고 AccountSecured 이벤트를 반드시 발생시킵니다. 호출자가 보유자가 아니거나, 이미 보안이 설정돼 있거나, 두 키 지갑이 서로 같거나, 키 지갑이 호출자 자신과 같거나, 둘 중 하나라도 제로 주소(0x0)이거나, 이미 다른 보유 계정의 키 지갑인 경우 revert해야 합니다(SHOULD). 두 지갑을 동시에 등록하는 이유는 수수료 절감과 실수 방지, 그리고 safeFallback이 잘못 작동하는 상황을 막기 위해서라고 원문은 설명합니다. addBindings를 아예 호출하지 않으면 기존 ERC-721과 동일한 기본 동작을 유지합니다.
allowTransfer의 조건 해석
allowTransfer(_tokenId, _time, _to, _anyToken)는 키 지갑에서 호출하며 transferFrom이나 safeTransferFrom 전에 먼저 실행합니다. _tokenId가 0이면 토큰 제한이 없고, _time이 0이면 시간 제한이 없으며 _to가 제로 주소면 수신 주소 제한이 없습니다. _anyToken이 true면 나머지 파라미터와 무관하게 어떤 토큰이든 언제든 누구에게나 전송할 수 있게 됩니다. AccountEnabledTransfer 이벤트를 반드시 발생시켜야 하고 호출자가 보유자의 키 지갑이 아니거나 _tokenId의 소유자가 그 보유자와 다르면 revert해야 합니다(SHOULD).
allowApproval과 전송 횟수 차감
allowApproval(_time, _numberOfTransfers)는 키 지갑에서 호출하며 approve나 setApprovalForAll 전에 먼저 실행합니다. 보유자가 _time 동안 승인을 할 수 있게 하고 승인받은 쪽이 할 수 있는 전송 횟수를 _numberOfTransfers로 제한합니다(0이면 허용 한도 안에서 무제한). 이후 transferFrom이 호출될 때마다 컨트랙트가 남은 횟수를 차감하고 0에 도달하면 setApprovalForAll을 false로 바꿔 승인을 회수합니다. approve와 setApprovalForAll은 보안 계정이 allowApproval을 호출하지 않았거나 설정된 _time이 지난 경우 revert해야 합니다(SHOULD).
safeFallback: 보유 지갑이 뚫렸을 때
키 지갑에서 safeFallback을 호출하면 보유 계정의 모든 토큰이 다른 쪽 키 지갑으로 이동하고 SafeFallbackActivated 이벤트가 발생해야 합니다. 호출자가 키 지갑이 아니면 revert해야 합니다(SHOULD). 토큰이 호출한 지갑이 아니라 반대쪽 키 지갑으로 가도록 한 것이 핵심인데, 만약 호출한 지갑으로 간다면 키 지갑 하나를 탈취한 공격자가 safeFallback만 불러서 전부 가져갈 수 있기 때문입니다. 테스트 기준 평균 가스는 72,221로, 보유한 NFT 수에 따라 달라집니다.
resetBindings: 키 지갑이 뚫렸을 때
키 지갑에서 resetBindings를 호출하면 보유 계정의 키 지갑 설정이 해제되고 AccountResetBinding 이벤트가 발생해야 합니다. 이후 토큰은 일반 ERC-721처럼 동작하며 보유 지갑이 addBindings를 다시 호출해 새 키 지갑을 등록할 수 있습니다. 보유 지갑이 아니라 키 지갑만 이 함수를 부를 수 있게 한 이유는, 보유 지갑이 가능하다면 보유 지갑을 탈취한 공격자가 바로 보안을 풀고 자산을 가져갈 수 있기 때문입니다. 3개 지갑 중 2개가 동시에 뚫리면 막을 방법이 없지만 1개 침해까지는 복구 기회를 준다고 원문은 설명합니다.
ERC-721 호환성과 반환값 처리
이 표준은 ERC-721의 모든 특성을 상속해 기존 비대체 토큰을 쓰는 dApp에서 그대로 작동하며 기존 토큰과 지갑은 영향을 받지 않습니다. transferFrom과 safeTransferFrom은 호출자가 소유자도 승인받은 자도 아니거나, _from이 소유자가 아니거나, 보안 계정이 해당 _tokenId의 전송을 allowTransfer로 허가하지 않은 경우 revert해야 합니다(SHOULD). 명세는 호출자가 returns (bool success)의 false를 반드시 처리해야 하며 false가 절대 반환되지 않는다고 가정해서는 안 된다고 명시합니다. allowTransfer와 allowApproval이 성공 시 bool을 반환하고 실패 시 revert하는 것은 ERC-721과의 일관성을 위한 선택입니다.
쓸 때 주의할 점
- 키 지갑 2개는 동시에 등록합니다: 키 지갑을 하나씩 추가할 수 있게 하면 수수료가 늘고 혼란이 생기며 safeFallback이 잘못 작동하는 상황이 생길 수 있어 모든 KBT는 2개를 동시에 등록하는 3지갑 체계로 작동합니다.
- safeFallback은 호출한 지갑이 아닌 다른 키 지갑으로 보냅니다: 자산이 호출한 키 지갑으로 이동한다면 키 지갑 하나를 탈취한 공격자가 safeFallback을 호출해 전부 가져갈 수 있으므로, 반대쪽 키 지갑으로 보내 단일 실패 지점을 막습니다.
- resetBindings는 키 지갑만 호출할 수 있습니다: 보유 지갑이 초기화를 할 수 있다면 보유 지갑을 탈취한 공격자가 즉시 보안을 해제하고 자산을 가져갈 수 있기 때문입니다.
- 지갑 3개 중 2개가 뚫리면 방어할 수 없습니다: 악의적 공격으로 2개 지갑이 동시에 침해되면 소유자가 할 수 있는 일이 없습니다. 다만 1개 침해까지는 기존 표준과 달리 복구 기회가 주어집니다.
- allowApproval은 악성 승인 피해를 제한합니다: 드레이너 dApp 같은 흔한 악성 공격에 대비해 승인 가능 시간과 제3자의 전송 횟수를 제한함으로써 승인 이후의 피해 범위를 줄입니다.