def __init__(self, crypto: Crypto, key: bytes) -> None: """Constructor.""" # Set the Crypto class. self.crypto: Crypto = crypto # Set the keys. self.private_spend_key: bytes = ed.encodeint(ed.decodeint(key) % ed.l) self.public_spend_key: bytes = ed.public_from_secret( self.private_spend_key) self.private_view_key: bytes = ed.Hs(self.private_spend_key) self.public_view_key: bytes = ed.public_from_secret( self.private_view_key)
def spendable_transaction( self, inputs: List[OutputInfo], mixins: List[List[int]], outputs: List[SpendableOutput], ring: List[List[List[bytes]]], change: SpendableOutput, fee: int, ) -> MoneroSpendableTransaction: """Create a MoneroSpendableTransaction.""" # Clone the arguments. inputs = list(inputs) outputs = list(outputs) # Calculate the Transaction's amount. amount: int = 0 for input_i in inputs: amount += input_i.amount for output in outputs: amount -= output.amount amount -= fee # Verify the outputs, change output (if needed), and fee are payable. if amount < 0: raise Exception( "Transaction doesn't have enough of an amount to pay " + "all the outputs, a change output (if needed), and the fee.") elif amount == 0: if len(outputs) < 2: raise Exception( "Transaction doesn't have enough to create a second output." ) else: # Add the change output. change.amount = amount outputs.append(change) # Shuffle the outputs. shuffle(outputs) # Embed the mixins and ring into the inputs. for i in range(len(inputs)): inputs[i].mixins = [] index_sum: int = 0 for index in mixins[i]: inputs[i].mixins.append(index - index_sum) index_sum += inputs[i].mixins[-1] inputs[i].ring = ring[i] # Create an r. r: bytes = ed.Hs(urandom(32)) # Create the actual output key and the output amounts. Rs: List[bytes] = [] rA8s: List[bytes] = [] amount_keys: List[bytes] = [] output_keys: List[bytes] = [] output_amounts: List[int] = [] for o in range(len(outputs)): rA8s.append(self.create_shared_key(r, outputs[o].view_key)) amount_keys.append(ed.Hs(rA8s[-1] + to_var_int(o))) output_keys.append( ed.encodepoint( ed.add_compressed( ed.scalarmult(ed.B, ed.decodeint(amount_keys[-1])), ed.decodepoint(outputs[o].spend_key), ))) rG: bytes if outputs[o].network == self.network_bytes_property[2]: rG = ed.encodepoint( ed.scalarmult(ed.decodepoint(outputs[o].spend_key), ed.decodeint(r))) else: rG = ed.encodepoint(ed.scalarmult(ed.B, ed.decodeint(r))) Rs.append(rG) output_amounts.append(outputs[o].amount) # Deduplicate the Rs. Rs = list(set(Rs)) # Create an extra. extra: bytes = bytes([0x01]) + Rs[0] # Add the other Rs. if len(Rs) > 1: extra += bytes([0x04]) + to_var_int(len(Rs) - 1) for R in Rs[1:]: extra += R # Add the payment IDs. extra_payment_IDs: bytes = bytes() for o in range(len(outputs)): potential_payment_id: Optional[bytes] = outputs[o].payment_id if potential_payment_id: extra_payment_IDs += bytes([0x01]) + (int.from_bytes( potential_payment_id, byteorder="little") ^ int.from_bytes( ed.H(rA8s[o] + bytes([0x8D]))[0:8], byteorder="little")).to_bytes(8, byteorder="little") if extra_payment_IDs: extra += (bytes([0x02]) + to_var_int(len(extra_payment_IDs)) + extra_payment_IDs) return MoneroSpendableTransaction(inputs, amount_keys, output_keys, output_amounts, extra, fee)
def can_spend_output( self, unique_factors: Dict[bytes, Tuple[int, int]], shared_key: bytes, tx: Transaction, o: int, ) -> Optional[MoneroOutputInfo]: """Checks if an output is spendable and returns the relevant info.""" # Grab the output. output = tx.outputs[o] # Transaction one time keys are defined as P = Hs(H8Ra || i)G + B. # This is rewrittable as B = P - Hs(8Ra || i) G. # Hs(8Ra || i) amount_key: bytes = ed.Hs(shared_key + to_var_int(o)) # P - Hs(8Ra || i)G amount_key_G: ed.CompressedPoint = ed.scalarmult( ed.B, ed.decodeint(amount_key)) # Make it negative so it can be subtracted by adding it. amount_key_G = (-amount_key_G[0], amount_key_G[1]) spend_key: bytes = ed.encodepoint( ed.add_compressed(ed.decodepoint(output.key), amount_key_G)) # We now have the spend key of the Transaction. if spend_key in unique_factors: # Get the amount. amount: int = 0 if isinstance(output, MinerOutput): amount = output.amount else: # Decrypt the amount. amount = int.from_bytes( output.amount, byteorder="little") ^ int.from_bytes( ed.H(b"amount" + amount_key)[0:8], byteorder="little") commitment: bytes = ed.COMMITMENT_MASK if isinstance(output, Output): # The encrypted amount is malleable. # We need to rebuild the commitment to verify it's accurate. commitment = ed.Hs(b"commitment_mask" + amount_key) if (ed.encodepoint( ed.add_compressed( ed.scalarmult( ed.B, ed.decodeint(commitment), ), ed.scalarmult(ed.C, amount), )) != output.commitment): return None return MoneroOutputInfo( OutputIndex(tx.tx_hash, o), tx.unlock_time, amount, spend_key, (unique_factors[spend_key][0], unique_factors[spend_key][1]), amount_key, commitment, ) return None