def generate_input_key(
        self,
        output: OutputInfo,
        private_view_key: bytes,
        private_spend_key: bytes,
    ) -> bytes:
        """Generate the one-time private key associated with an input."""

        if isinstance(output, MoneroOutputInfo):
            return ed.encodeint((ed.decodeint(output.amount_key) +
                                 ed.decodeint(private_spend_key)) % ed.l)
        else:
            raise Exception("MoneroCrypto handed a non-Monero OutputInfo.")
    def create_shared_key(self, scalar: bytes, point: bytes) -> bytes:
        """Created the shared key of which there is one per R."""

        # 8Ra.
        Ra8: Any = ed.scalarmult(ed.decodepoint(point), ed.decodeint(scalar))
        Ra8 = ed.decompress(Ra8)
        for _ in range(3):
            Ra8 = ed.add(Ra8, Ra8)
        Ra8 = ed.encodepoint(ed.compress(Ra8))
        return Ra8
Esempio n. 3
0
    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