Single nominator pool
The single nominator contract is a security-focused smart contract that lets validators securely stake Toncoins without needing other participants. Designed for validators with sufficient self-stake, it keeps signing keys separate from staked funds using a cold wallet for maximum security. The contract provides an alternative simplified implementation for the nominator pool smart contract that supports a single nominator only. The benefit of this implementation is that it's more secure since the attack surface is considerably smaller. This is due to a massive reduction in the complexity of the nominator pool that has to support multiple third-party nominators.
The go-to solution for validators
This smart contract is the recommended solution for TON validators with a sufficient stake to validate independently. Other options include:
-
Hot wallet
Standard wallet implementation
Risk: Vulnerable to theft if the validator node is compromised -
Restricted wallet
Legacy implementation
Issues: Unmaintained and susceptible to gas drainage attacks -
Nominator pool
Single-nominator setup
Drawback: Unnecessary complexity for solo validators
For a complete feature comparison, see:
Comparison of existing alternatives
Official code hash
Check this in https://verifier.ton.org before sending funds to a live contract.
Single nominator v1.0
pCrmnqx2/+DkUtPU8T04ehTkbAGlqtul/B2JPmxx9bo=
Single nominator v1.1 (with withdrawals by comment)
zA05WJ6ywM/g/eKEVmV6O909lTlVrj+Y8lZkqzyQT70=
Architecture
The architecture is nearly identical to the nominator pool contract:
Separation to two roles
- Owner - cold wallet (private key that is not connected to the Internet) that owns the funds used for staking and acts as the single nominator
- Validator - the wallet whose private key is on the validator node (can sign blocks but can't steal the funds used for stake)
Workflow
- Owner holds the funds for staking ($$$) in their secure cold wallet
- Owner deposits the funds ($$$) in the SingleNominator contract (this contract)
- MyTonCtrl starts running on the validator node connected to the Internet
- MyTonCtrl uses Validator wallet to instruct SingleNominator to enter the next election cycle
- SingleNominator sends the stake ($$$) to the Elector for one cycle
- The election cycle is over, and stake can be recovered
- MyTonCtrl uses Validator wallet to instruct SingleNominator to recover the stake from the election cycle
- SingleNominator recovers the stake ($$$) of the previous cycle from the Elector
- Steps 4-8 repeat as long as Owner is happy to keep validating
- Owner withdraws the funds ($$$) from the SingleNominator contract and takes them back home
Mitigated attack vectors
-
The validator node requires a hot wallet to sign new blocks. This wallet is inherently insecure because its private key is connected to the Internet. Even if this key is compromised, the Validator cannot extract the funds used for validation. Only Owner can withdraw these funds.
-
Even if the _ Validator _ wallet is compromised, _ Owner _ can tell _ SingleNominator _ to change the validator address. This will prevent the attacker from interacting with SingleNominator further. There is no race condition here; Owner will always take precedence.
-
The SingleNominator balance holds the principal staking funds only—its balance is not used for gas fees. Gas money for entering election cycles is held in the Validator wallet. This prevents an attacker who compromised the validator from draining the principal via a gas spending attack.
-
SingleNominator verifies the format of all operations given by Validator to ensure it doesn't forward invalid messages to the Elector.
-
In an emergency, for example, if the Elector contract was upgraded and changed its interface, Owner can still send any raw message as SingleNominator to recover the stake from Elector.
-
In an extreme emergency, Owner can set the code of SingleNominator and override its current logic to address unforeseen circumstances.
The standard nominator pool can't prevent all attack scenarios - a malicious validator operator could potentially steal from nominators. This risk doesn't exist with SingleNominator since both the Owner and Validator are controlled by the same entity.
Security audits
Certik conducted a full security audit, which is available in this repo: Certik audit.
Comparison of existing alternatives
Assuming that you are a validator with enough stake to validate independently, these are the alternative setups you can use with MyTonCtrl:
1. Simple hot wallet
This basic setup connects MyTonCtrl directly to the standard wallet holding your funds. Because this wallet remains internet-connected, it operates as a hot wallet.
This is insecure because an attacker can get the private key as soon as it's connected to the Internet. With the private key, the attacker can send the staking funds to anyone.
2. Restricted wallet
This setup replaces the standard wallet with a restricted-wallet that allows outgoing transactions to be sent only to restricted destinations such as the Elector and the owner's address.
The restricted wallet is unmaintained (replaced by nominator-pool) and has unresolved attack vectors like gas drainage attacks. Since the same wallet holds gas fees and the stake principal in the same balance, an attacker who compromises the private key can generate transactions that will cause significant principal losses. In addition, there's a race condition between the attacker and the owner when trying to withdraw due to seqno collisions.
3. Nominator pool
The nominator-pool was the first to introduce a clear separation between the stake owners (nominators) and the validator connected to the Internet. This setup supports up to 40 nominators staking together on the same validator.
The nominator pool contract is overly complex due to the support of 40 concurrent nominators. In addition, the contract has to protect the nominators from the contract deployer because those are separate entities. This setup is considered ok but is very difficult to audit in full due to the size of the attack surface. The solution makes sense mostly when the validator does not have enough stake to validate alone or wants to do a rev-share with third-party stakeholders.
4. Single nominator
This is the setup implemented in this repo. It's a very simplified version of the nominator pool that supports a single nominator. There is no need to protect this nominator from the contract deployer, as they are the same entity.
If you have a single nominator who holds all stakes for validation, this is the most secure setup you can use. In addition to its simplicity, this contract provides the owner multiple emergency safeguards to recover stakes even in extreme scenarios like Elector upgrades that break the recover stake interface.
Owner only messages
The nominator owner can perform 4 operations:
1. Withdraw
Used to withdraw funds to the owner's wallet. To withdraw the funds the owner should send a message with a body that includes: opcode=0x1000 (32 bits), query_id (64 bits) and withdraw amount (stored as coin variable). The nominator contract will send the funds with BOUNCEABLE flag and mode=64.
In case the owner is using a hot wallet (not recommended), withdraw-deeplink.ts can be used to generate a deeplink to initiate a withdrawal from tonkeeper wallet.
Command line: ts-node scripts/ts/withdraw-deeplink.ts single-nominator-addr withdraw-amount
where:
- single-nominator-addr is the single nominator address the owner wishes to withdraw from.
- withdraw-amount is the amount to withdraw. The nominator contract will leave 1 TON in the contract, so the amount sent to the owner's address will be the minimum between the requested amount and the contract balance - 1.
The owner should run the deeplink from a phone with the tonkeeper wallet.
If the owner is using a cold wallet (recommended), withdraw.fif can be used to generate a boc body that includes the withdraw opcode and the amount to withdraw.
Command line: fift -s scripts/fif/withdraw.fif withdraw-amount
where withdraw-amount is the amount to withdraw from the nominator contract to the owner's wallet. As described above, the nominator contract will leave at least 1 TON in the contract.
This script will generate a boc body (named withdraw.boc) that should be signed and sent from the owner's wallet.
From the black computer, the owner should run the following:
- create and sign the tx:
fift -s wallet-v3.fif my-wallet single_nominator_address sub_wallet_id seqno amount -B withdraw.boc
where my-wallet is the owner's pk file (without extension). The amount of 1 TON should be enough to pay fees (the remaining amount will be returned to the owner). The withdraw.boc is the boc generated above. - from a computer with access to the internet, run:
lite-client -C global.config.json -c 'sendfile wallet-query.boc'
to send the boc file (wallet-query.boc) generated in the previous step.
2. Change validator
Used to change the validator address. The validator can only send NEW_STAKE and RECOVER_STAKE to the elector. If the validator's private key is compromised, the validator's address can be changed. The funds are safe in this case, as only the owner can withdraw them.
In case the owner is using a hot wallet (not recommended), change-validator-deeplink.ts can be used to generate a deeplink to change the validator address.
Command line: ts-node scripts/ts/change-validator-deeplink.ts single-nominator-addr new-validator-address
where:
- single-nominator-addr is the single nominator address.
- new-validator-address (defaults to ZERO address) is the address of the new validator. If you want to disable the validator immediately and only later set a new validator, it might be convenient to set the validator address to the ZERO address.
The owner should run the deeplink from a phone with a tonkeeper wallet.
If the owner is using a cold wallet (recommended), change-validator.fif can be used to generate a boc body that includes the change-validator opcode and the new validator address.
Command line: fift -s scripts/fif/change-validator.fif new-validator-address
.
This script will generate a boc body (change-validator.boc) that should be signed and sent from the owner's wallet.
From the black computer, the owner should run the following:
- create and sign the tx:
fift -s wallet-v3.fif my-wallet single_nominator_address sub_wallet_id seqno amount -B change-validator.boc
where my-wallet is the owner's pk file (without extension). The amount of 1 TON should be enough to pay fees (the remaining amount will be returned to the owner). The change-validator.boc is the boc generated above. - from a computer with access to the internet, run:
lite-client -C global.config.json -c 'sendfile wallet-query.boc'
to send the boc file (wallet-query.boc) generated in the previous step.
3. Send raw msg
This opcode is not expected to be used under normal conditions.
It can be used to send any message from the nominator contract (it must be signed and sent from the owner's wallet).
Use this opcode to recover funds from a changed Elector contract address where standard RECOVER_STAKE fails. The owner must construct a custom message containing the following:
opcode=0x7702
(32 bits)query_id
(64 bits)mode
(8 bits)- The raw message cell reference
4. Upgrade
This emergency opcode (0x9903) should only be used to upgrade the nominator contract in critical situations. The message must include:
opcode=0x9903
(32 bits)query_id
(64 bits)- New contract code cell reference