allocateOutputs.ts 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. export default () => {
  2. assert self.status[0] in ('setup', 'connecting')
  3. # fix the input selection
  4. self.inputs = tuple(self.coins.items())
  5. num_inputs = len(self.inputs)
  6. maxcomponents = min(self.num_components, MAX_COMPONENTS)
  7. max_outputs = maxcomponents - num_inputs
  8. if max_outputs < 1:
  9. raise FusionError('Too many inputs (%d >= %d)'%(num_inputs, maxcomponents))
  10. if self.max_outputs is not None:
  11. assert self.max_outputs >= 1
  12. max_outputs = min(self.max_outputs, max_outputs)
  13. # For obfuscation, when there are few distinct inputs we want to have many
  14. # outputs.
  15. # Calculate the number of distinct inputs as the number of distinct pubkeys
  16. # (i.e. extra inputs from same address don't count as distinct)
  17. num_distinct = len(set(pub for (_,_), (pub,_) in self.inputs))
  18. min_outputs = max(MIN_TX_COMPONENTS - num_distinct, 1)
  19. if max_outputs < min_outputs:
  20. raise FusionError('Too few distinct inputs selected (%d); cannot satisfy output count constraint (>=%d, <=%d)'%(num_distinct, min_outputs, max_outputs))
  21. # how much input value do we bring to the table (after input & player fees)
  22. sum_inputs_value = sum(v for (_,_), (p,v) in self.inputs)
  23. input_fees = sum(component_fee(size_of_input(p), self.component_feerate) for (_,_), (p,v) in self.inputs)
  24. avail_for_outputs = (sum_inputs_value
  25. - input_fees
  26. - self.min_excess_fee)
  27. # each P2PKH output will need at least this much allocated to it.
  28. fee_per_output = component_fee(34, self.component_feerate)
  29. offset_per_output = Protocol.MIN_OUTPUT + fee_per_output
  30. if avail_for_outputs < offset_per_output:
  31. # our input amounts are so small that we can't even manage a single output.
  32. raise FusionError('Selected inputs had too little value')
  33. rng = Random()
  34. rng.seed(secrets.token_bytes(32))
  35. tier_outputs = {}
  36. excess_fees = {}
  37. for scale in self.available_tiers:
  38. ### Fuzzing fee range selection ###
  39. # To keep privacy at higher tiers, we need to randomize our input-output
  40. # linkage somehow, which means throwing away some sats as extra fees beyond
  41. # the minimum requirement.
  42. # Just use (tier / 10^6) as fuzzing range. For a 10 BCH tier this means
  43. # randomly overpaying fees of 0 to 1000 sats.
  44. fuzz_fee_max = scale // 1000000
  45. ### End fuzzing fee range selection ###
  46. # Now choose a random fuzz fee. Uniform random is best for obfuscation.
  47. # But before we do, there is a maximum fuzzing fee that is admitted by server, and
  48. # a safety maximum that we have ourselves.
  49. fuzz_fee_max_reduced = min(fuzz_fee_max,
  50. MAX_EXCESS_FEE - self.min_excess_fee,
  51. self.max_excess_fee - self.min_excess_fee)
  52. assert fuzz_fee_max_reduced >= 0
  53. fuzz_fee = secrets.randbelow(fuzz_fee_max_reduced + 1)
  54. assert fuzz_fee <= fuzz_fee_max_reduced and fuzz_fee_max_reduced <= fuzz_fee_max
  55. reduced_avail_for_outputs = avail_for_outputs - fuzz_fee
  56. if reduced_avail_for_outputs < offset_per_output:
  57. continue
  58. outputs = random_outputs_for_tier(rng, reduced_avail_for_outputs, scale, offset_per_output, max_outputs)
  59. if not outputs or len(outputs) < min_outputs:
  60. # this tier is no good for us.
  61. continue
  62. # subtract off the per-output fees that we provided for, above.
  63. outputs = tuple(o - fee_per_output for o in outputs)
  64. assert len(self.inputs) + len(outputs) <= MAX_COMPONENTS
  65. excess_fees[scale] = sum_inputs_value - input_fees - reduced_avail_for_outputs
  66. tier_outputs[scale] = outputs
  67. self.tier_outputs = tier_outputs
  68. self.print_error(f"Possible tiers: {tier_outputs}")
  69. # remember these for safety check later on
  70. self.safety_sum_in = sum_inputs_value
  71. self.safety_excess_fees = excess_fees
  72. }