1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
- export default () => {
- assert self.status[0] in ('setup', 'connecting')
- # fix the input selection
- self.inputs = tuple(self.coins.items())
- num_inputs = len(self.inputs)
- maxcomponents = min(self.num_components, MAX_COMPONENTS)
- max_outputs = maxcomponents - num_inputs
- if max_outputs < 1:
- raise FusionError('Too many inputs (%d >= %d)'%(num_inputs, maxcomponents))
- if self.max_outputs is not None:
- assert self.max_outputs >= 1
- max_outputs = min(self.max_outputs, max_outputs)
- # For obfuscation, when there are few distinct inputs we want to have many
- # outputs.
- # Calculate the number of distinct inputs as the number of distinct pubkeys
- # (i.e. extra inputs from same address don't count as distinct)
- num_distinct = len(set(pub for (_,_), (pub,_) in self.inputs))
- min_outputs = max(MIN_TX_COMPONENTS - num_distinct, 1)
- if max_outputs < min_outputs:
- raise FusionError('Too few distinct inputs selected (%d); cannot satisfy output count constraint (>=%d, <=%d)'%(num_distinct, min_outputs, max_outputs))
- # how much input value do we bring to the table (after input & player fees)
- sum_inputs_value = sum(v for (_,_), (p,v) in self.inputs)
- input_fees = sum(component_fee(size_of_input(p), self.component_feerate) for (_,_), (p,v) in self.inputs)
- avail_for_outputs = (sum_inputs_value
- - input_fees
- - self.min_excess_fee)
- # each P2PKH output will need at least this much allocated to it.
- fee_per_output = component_fee(34, self.component_feerate)
- offset_per_output = Protocol.MIN_OUTPUT + fee_per_output
- if avail_for_outputs < offset_per_output:
- # our input amounts are so small that we can't even manage a single output.
- raise FusionError('Selected inputs had too little value')
- rng = Random()
- rng.seed(secrets.token_bytes(32))
- tier_outputs = {}
- excess_fees = {}
- for scale in self.available_tiers:
- ### Fuzzing fee range selection ###
- # To keep privacy at higher tiers, we need to randomize our input-output
- # linkage somehow, which means throwing away some sats as extra fees beyond
- # the minimum requirement.
- # Just use (tier / 10^6) as fuzzing range. For a 10 BCH tier this means
- # randomly overpaying fees of 0 to 1000 sats.
- fuzz_fee_max = scale // 1000000
- ### End fuzzing fee range selection ###
- # Now choose a random fuzz fee. Uniform random is best for obfuscation.
- # But before we do, there is a maximum fuzzing fee that is admitted by server, and
- # a safety maximum that we have ourselves.
- fuzz_fee_max_reduced = min(fuzz_fee_max,
- MAX_EXCESS_FEE - self.min_excess_fee,
- self.max_excess_fee - self.min_excess_fee)
- assert fuzz_fee_max_reduced >= 0
- fuzz_fee = secrets.randbelow(fuzz_fee_max_reduced + 1)
- assert fuzz_fee <= fuzz_fee_max_reduced and fuzz_fee_max_reduced <= fuzz_fee_max
- reduced_avail_for_outputs = avail_for_outputs - fuzz_fee
- if reduced_avail_for_outputs < offset_per_output:
- continue
- outputs = random_outputs_for_tier(rng, reduced_avail_for_outputs, scale, offset_per_output, max_outputs)
- if not outputs or len(outputs) < min_outputs:
- # this tier is no good for us.
- continue
- # subtract off the per-output fees that we provided for, above.
- outputs = tuple(o - fee_per_output for o in outputs)
- assert len(self.inputs) + len(outputs) <= MAX_COMPONENTS
- excess_fees[scale] = sum_inputs_value - input_fees - reduced_avail_for_outputs
- tier_outputs[scale] = outputs
- self.tier_outputs = tier_outputs
- self.print_error(f"Possible tiers: {tier_outputs}")
- # remember these for safety check later on
- self.safety_sum_in = sum_inputs_value
- self.safety_excess_fees = excess_fees
- }
|