mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 06:48:16 +00:00
core/bitcoin: Implement CoinJoinApprover.
This commit is contained in:
parent
472e853f0a
commit
97fc9b74ab
@ -8,8 +8,12 @@ from trezor.messages.TxOutputType import TxOutputType
|
|||||||
from apps.common import coininfo
|
from apps.common import coininfo
|
||||||
|
|
||||||
from .. import addresses
|
from .. import addresses
|
||||||
|
from ..authorization import FEE_PER_ANONYMITY_DECIMALS
|
||||||
from . import helpers, tx_weight
|
from . import helpers, tx_weight
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from ..authorization import CoinJoinAuthorization
|
||||||
|
|
||||||
|
|
||||||
# An Approver object computes the transaction totals and either prompts the user
|
# An Approver object computes the transaction totals and either prompts the user
|
||||||
# to confirm transaction parameters (output addresses, amounts and fees) or uses
|
# to confirm transaction parameters (output addresses, amounts and fees) or uses
|
||||||
@ -96,3 +100,119 @@ class BasicApprover(Approver):
|
|||||||
await helpers.confirm_total(total, fee, self.coin)
|
await helpers.confirm_total(total, fee, self.coin)
|
||||||
else:
|
else:
|
||||||
await helpers.confirm_joint_total(spending, total, self.coin)
|
await helpers.confirm_joint_total(spending, total, self.coin)
|
||||||
|
|
||||||
|
|
||||||
|
class CoinJoinApprover(Approver):
|
||||||
|
def __init__(
|
||||||
|
self, tx: SignTx, coin: coininfo.CoinInfo, authorization: CoinJoinAuthorization
|
||||||
|
) -> None:
|
||||||
|
super().__init__(tx, coin)
|
||||||
|
self.authorization = authorization
|
||||||
|
|
||||||
|
# Upper bound on the user's contribution to the weight of the transaction.
|
||||||
|
self.our_weight = tx_weight.TxWeightCalculator(
|
||||||
|
tx.inputs_count, tx.outputs_count
|
||||||
|
)
|
||||||
|
|
||||||
|
# base for coordinator fee to be multiplied by fee_per_anonymity
|
||||||
|
self.coordinator_fee_base = 0
|
||||||
|
|
||||||
|
# size of the current group of outputs
|
||||||
|
self.group_size = 0
|
||||||
|
|
||||||
|
# number of our change outputs in the current group
|
||||||
|
self.group_our_count = 0
|
||||||
|
|
||||||
|
# amount of each output in the current group
|
||||||
|
self.group_amount = 0
|
||||||
|
|
||||||
|
# flag indicating whether our outputs are gaining any anonymity
|
||||||
|
self.anonymity = False
|
||||||
|
|
||||||
|
async def add_internal_input(self, txi: TxInputType, amount: int) -> None:
|
||||||
|
self.our_weight.add_input(txi)
|
||||||
|
if not self.authorization.check_sign_tx_input(txi, self.coin):
|
||||||
|
raise wire.ProcessError("Unauthorized path")
|
||||||
|
|
||||||
|
await super().add_internal_input(txi, amount)
|
||||||
|
|
||||||
|
def add_change_output(self, txo: TxOutputType, script_pubkey: bytes) -> None:
|
||||||
|
super().add_change_output(txo, script_pubkey)
|
||||||
|
self._add_output(txo, script_pubkey)
|
||||||
|
self.our_weight.add_output(script_pubkey)
|
||||||
|
self.group_our_count += 1
|
||||||
|
|
||||||
|
async def add_external_output(
|
||||||
|
self, txo: TxOutputType, script_pubkey: bytes
|
||||||
|
) -> None:
|
||||||
|
await super().add_external_output(txo, script_pubkey)
|
||||||
|
self._add_output(txo, script_pubkey)
|
||||||
|
|
||||||
|
async def approve_tx(self) -> None:
|
||||||
|
# Ensure that at least one of the user's outputs is in a group with an external output.
|
||||||
|
if not self.anonymity:
|
||||||
|
raise wire.ProcessError("No anonymity gain")
|
||||||
|
|
||||||
|
# The mining fee of the transaction as a whole.
|
||||||
|
mining_fee = self.total_in - self.total_out
|
||||||
|
|
||||||
|
# mining_fee > (coin.maxfee per byte * tx size)
|
||||||
|
if mining_fee > (self.coin.maxfee_kb / 1000) * (self.weight.get_total() / 4):
|
||||||
|
raise wire.ProcessError("Mining fee over threshold")
|
||||||
|
|
||||||
|
# The maximum mining fee that the user should be paying.
|
||||||
|
our_max_mining_fee = (
|
||||||
|
mining_fee * self.our_weight.get_total() / self.weight.get_total()
|
||||||
|
)
|
||||||
|
|
||||||
|
# The coordinator fee for the user's outputs.
|
||||||
|
our_coordinator_fee = self._get_coordinator_fee()
|
||||||
|
|
||||||
|
# Total fees that the user is paying.
|
||||||
|
our_fees = self.total_in - self.external_in - self.change_out
|
||||||
|
|
||||||
|
if our_fees > our_coordinator_fee + our_max_mining_fee:
|
||||||
|
raise wire.ProcessError("Total fee over threshold")
|
||||||
|
|
||||||
|
if self.tx.lock_time > 0:
|
||||||
|
raise wire.ProcessError("nLockTime not allowed in CoinJoin")
|
||||||
|
|
||||||
|
if not self.authorization.approve_sign_tx(self.tx, our_fees):
|
||||||
|
raise wire.ProcessError("Fees exceed authorized limit")
|
||||||
|
|
||||||
|
# Coordinator fee calculation.
|
||||||
|
|
||||||
|
def _get_coordinator_fee(self) -> float:
|
||||||
|
# Add the coordinator fee for the last group of outputs.
|
||||||
|
self._new_group(0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.coordinator_fee_base
|
||||||
|
* self.authorization.fee_per_anonymity
|
||||||
|
/ pow(10, FEE_PER_ANONYMITY_DECIMALS + 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _add_output(self, txo: TxOutputType, script_pubkey: bytes):
|
||||||
|
# Assumption: CoinJoin outputs are grouped by amount. (If this assumption is
|
||||||
|
# not satisfied, then we will compute a lower coordinator fee, which may lead
|
||||||
|
# us to wrongfully decline the transaction.)
|
||||||
|
if self.group_amount != txo.amount:
|
||||||
|
self._new_group(txo.amount)
|
||||||
|
|
||||||
|
self.group_size += 1
|
||||||
|
|
||||||
|
def _new_group(self, amount: int):
|
||||||
|
# Add the base coordinator fee for the previous group of outputs.
|
||||||
|
# Skip groups of size 1, because those must be change-outputs.
|
||||||
|
if self.group_size > 1:
|
||||||
|
self.coordinator_fee_base += (
|
||||||
|
self.group_our_count * self.group_size * self.group_amount
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check whether our outputs gained any anonymity.
|
||||||
|
if self.group_our_count and self.group_size > self.group_our_count:
|
||||||
|
self.anonymity = True
|
||||||
|
|
||||||
|
self.group_size = 0
|
||||||
|
self.group_our_count = 0
|
||||||
|
self.group_amount = amount
|
||||||
|
Loading…
Reference in New Issue
Block a user