ERC-6809 Key-Bound NFT standard (Non-Fungible Key Bound Token)
Final · NFTs & multi-token · created 2023-03-31
An ERC-721 extension that builds optional security features into the NFT itself. An NFT can be moved only when two separate key wallets authorize the transfer and approval, so assets stay safe even if the holding wallet is compromised.
Why it is needed
With self-custody assets, all responsibility rests with the individual. If a seed phrase or private key leaks, no one can recover the NFTs in the wallet. According to the original text, more than 46,000 crypto scam reports were filed with the US Federal Trade Commission from January 2021 to March 2022, and consumer losses exceeded 1 billion dollars. Existing security measures also have limits. Centralized services require account creation and submission of personal information and may be subject to regional restrictions, while decentralized options such as hardware wallets come with logistical problems such as purchase and shipping. This standard takes a different approach by building security into the token itself rather than the wallet. With the security feature turned on, individual assets stay protected even if the seed phrase is exposed, and with it turned off, the token behaves exactly like a regular ERC-721.
What to implement
event AccountSecured(address indexed _account, uint256 _noOfTokens);: Emitted when an account activates security with addBindings. The current held quantity is recorded along with it.event AccountResetBinding(address indexed _account);: Emitted when the holder resets the key wallet settings with resetBindings.event SafeFallbackActivated(address indexed _account);: Emitted when a key wallet calls safeFallback and moves assets to the other key wallet.event Ingress(address _account, uint256 _tokenId);: Emitted when an account newly becomes a token holder.event Egress(address _account, uint256 _tokenId);: Emitted when an account transfers all of its tokens and is no longer a holder.function getBindings(address _account) external view returns (AccountHolderBindings memory);: Returns the two key wallets bound to the account as a struct.function resetBindings() external returns (bool);: When called from a key wallet, it resets the key wallet settings of the holding account. It is used when a key wallet has been compromised.function safeFallback() external returns (bool);: When called from a key wallet, it moves all tokens of the holding account to the other key wallet. It is used when the holding wallet has been compromised.function getTransferableFunds(address _account) external view returns (TransferConditions memory);: Returns the transfer permission conditions set on the account as a struct.function isSecureWallet(address _account) external returns (bool);: Reports whether the account is one that has turned on the security feature.function isSecureToken(uint256 _tokenId) external returns (bool);: Reports whether the token belongs to an account that has turned on the security feature.
In depth
The three-wallet scheme and addBindings
addBindings(_keyWallet1, _keyWallet2) binds two key wallets to the sending account at once and must emit an AccountSecured event. It SHOULD revert if the caller is not a holder, if security is already set, if the two key wallets are the same, if a key wallet equals the caller itself, if either of them is the zero address (0x0), or if one is already a key wallet of another holding account. The original text explains that the two wallets are registered together to reduce fees, prevent mistakes, and avoid situations where safeFallback misbehaves. If addBindings is never called, the token keeps the same default behavior as a regular ERC-721.
How allowTransfer conditions are interpreted
allowTransfer(_tokenId, _time, _to, _anyToken) is called from a key wallet and runs before transferFrom or safeTransferFrom. If _tokenId is 0 there is no token restriction, if _time is 0 there is no time restriction, and if _to is the zero address there is no recipient restriction. If _anyToken is true, any token can be sent at any time to anyone, regardless of the other parameters. It must emit an AccountEnabledTransfer event, and it SHOULD revert if the caller is not a key wallet of the holder or if the owner of _tokenId differs from that holder.
allowApproval and decrementing the transfer count
allowApproval(_time, _numberOfTransfers) is called from a key wallet and runs before approve or setApprovalForAll. It lets the holder make approvals for _time and limits the number of transfers the approved party can make to _numberOfTransfers (0 means unlimited within the permitted limit). After that, each time transferFrom is called, the contract decrements the remaining count, and when it reaches 0 it sets setApprovalForAll to false to revoke the approval. approve and setApprovalForAll SHOULD revert if the secure account has not called allowApproval or if the configured _time has passed.
safeFallback: when the holding wallet is compromised
When safeFallback is called from a key wallet, all tokens in the holding account must move to the other key wallet, and a SafeFallbackActivated event must be emitted. It SHOULD revert if the caller is not a key wallet. The key point is that tokens go to the opposite key wallet rather than the caller. If they went to the calling wallet, an attacker who took over one key wallet could call safeFallback and take everything. Average gas in testing is 72,221, which varies with the number of NFTs held.
resetBindings: when a key wallet is compromised
When resetBindings is called from a key wallet, the key wallet settings of the holding account are cleared and an AccountResetBinding event must be emitted. After that, tokens behave like a regular ERC-721, and the holding wallet can register new key wallets by calling addBindings again. The reason only key wallets, not the holding wallet, can call this function is that if the holding wallet could, an attacker who took over the holding wallet could immediately disable the security and take the assets. The original text explains that if two of the three wallets are compromised at the same time there is no way to stop it, but a compromise of up to one wallet leaves a chance to recover.
ERC-721 compatibility and return value handling
This standard inherits every property of ERC-721, so it works unchanged in dApps that already use non-fungible tokens, and existing tokens and wallets are not affected. transferFrom and safeTransferFrom SHOULD revert if the caller is neither the owner nor an approved account, if _from is not the owner, or if the security account has not permitted the transfer of that _tokenId through allowTransfer. The specification states that callers must handle a false value of returns (bool success) and must not assume that false is never returned. allowTransfer and allowApproval return a bool on success and revert on failure for consistency with ERC-721.
Things to watch out for
- The two key wallets are registered together: Allowing key wallets to be added one at a time would raise fees, cause confusion, and could lead to situations where safeFallback misbehaves, so every KBT works as a three-wallet scheme in which two key wallets are registered at the same time.
- safeFallback sends assets to a key wallet other than the one that called it: If assets moved to the calling key wallet, an attacker who had taken over one key wallet could call safeFallback and take everything. Sending them to the opposite key wallet prevents a single point of failure.
- Only a key wallet can call resetBindings: If the holding wallet could perform the reset, an attacker who had taken over the holding wallet could immediately disable the security and take the assets.
- If 2 of the 3 wallets are breached, there is no defense: If two wallets are compromised at the same time by a malicious attack, the owner can do nothing. However, unlike existing standards, a chance to recover is given up to the point of one compromised wallet.
- allowApproval limits the damage from malicious approvals: To guard against common malicious attacks such as drainer dApps, it limits how long an approval is valid and how many times a third party can transfer, which reduces the scope of damage after an approval.