def get_minimum_fee(
        self,
        minimum_fee: Tuple[int, int],
        inputs: int,
        outputs: int,
        mixins: List[List[int]],
        extra: int,
        fee: int,
    ) -> int:
        """
        Calculates the minimum fee via the passed in:
        - Minimum fee (fee per byte, quantization mask)
        - Number of inputs
        - Number of outputs
        - Mixins
        - Extra length
        - Fee (a bit ironic, yet the fee changes the serialization length)
        """

        # Calculate the Transaction length.
        length: int = len(to_var_int(inputs)) + (803 * inputs)
        for i in mixins:
            for v in i:
                length += len(to_var_int(v))
        length += len(to_var_int(outputs)) + (74 * outputs)
        length += len(to_var_int(extra)) + extra
        length += len(to_var_int(fee))

        length += 64 * min(outputs, 4)
        length += 614

        # Calculate and return the minimum fee.
        return (((length * minimum_fee[0]) + minimum_fee[1] - 1) //
                minimum_fee[1] * minimum_fee[1])
Ejemplo n.º 2
0
def padded_varint_serializations_test() -> None:
    for _ in range(500):
        num: int = randint(0, 2**16)

        var_int: bytes = to_var_int(num)
        length: int = len(var_int)

        rpc_var_int: bytes = to_rpc_var_int(num)
        rpc_length: int = len(rpc_var_int)

        before: int = randint(0, 32)
        for i in range(before):
            var_int = bytes([randint(0, 255)]) + var_int
            rpc_var_int = bytes([randint(0, 255)]) + rpc_var_int
        for i in range(randint(0, 32)):
            var_int += bytes([randint(0, 255)])
            rpc_var_int += bytes([randint(0, 255)])

        res: Tuple[int, int] = from_var_int(var_int, before)
        assert res[0] == num
        assert res[1] == before + length

        rpc_res: Tuple[int, int] = from_rpc_var_int(rpc_var_int, before)
        assert res[0] == num
        assert res[1] == before + length
def multiple_Rs_test(harness: Harness) -> None:
    # Wallet.
    wallet: Wallet = Wallet(harness.crypto, urandom(32))

    # WatchWallet.
    watch: WatchWallet = WatchWallet(
        harness.crypto,
        harness.rpc,
        wallet.private_view_key,
        wallet.public_spend_key,
        harness.rpc.get_block_count() - 1,
    )

    # Test multiple Rs.
    indexes: List[Tuple[int, int]] = [(0, 0)]
    amounts: List[int] = []
    txs: List[bytes] = []
    for _ in range(5):
        indexes.append((randint(0, 300), randint(0, 300)))
        amounts.append(randint(ATOMIC_XMR, 40 * ATOMIC_XMR))
        txs.append(harness.send(watch.new_address(indexes[-1]), amounts[-1]))

        # Get the Transaction.
        tx: Transaction = watch.rpc.get_transaction(txs[-1])

        # Add multiple other Rs to the Transaction.
        for _ in range(3):
            tx.Rs.append(public_from_secret(Hs(urandom(32))))

        # Check the other Rs had no affect.
        spendable: Tuple[List[bytes], Dict[OutputIndex,
                                           OutputInfo]] = watch.can_spend(tx)
        assert not spendable[0]
        assert len(spendable[1]) == 1
        assert list(spendable[1].keys())[0].tx_hash == txs[-1]
        assert spendable[1][list(spendable[1].keys())[0]].amount == amounts[-1]

    # Test multiple identical Rs.
    for _ in range(5):
        # Send to a random index.
        indexes.append((randint(0, 300), randint(0, 300)))
        amounts.append(randint(ATOMIC_XMR, 5 * ATOMIC_XMR))
        txs.append(harness.send(watch.new_address(indexes[-1]), amounts[-1]))

        # Manually get the Transaction's JSON.
        tx_json: Dict[str, Any] = json.loads(
            watch.rpc.rpc_request(
                "get_transactions",
                {
                    "txs_hashes": [txs[-1].hex()],
                    "decode_as_json": True
                },
            )["txs"][0]["as_json"])

        # Create a Transaction from it.
        tx: Transaction = Transaction(txs[-1], tx_json)

        # Get a duplicate list of Rs.
        Rs: List[bytes] = tx.Rs * 2
        # Use the Rs and tx to craft a new extra in tx_json.
        extra: bytes = bytes([0x01]) + Rs[0]
        # Add the other Rs.
        extra += bytes([0x04]) + to_var_int(len(Rs) - 1)
        for R in Rs[1:]:
            extra += R
        # Store it in tx_json.
        tx_json["extra"] = []
        for b in range(len(extra)):
            tx_json["extra"].append(extra[b])

        # Parse the modified JSON.
        modified_tx: Transaction = Transaction(txs[-1], tx_json)

        # Check the duplicate Rs were stripped.
        assert tx.Rs == modified_tx.Rs
        spendable: Tuple[List[bytes], Dict[OutputIndex,
                                           OutputInfo]] = watch.can_spend(tx)
        assert not spendable[0]
        assert len(spendable[1]) == 1
        assert list(spendable[1].keys())[0].tx_hash == txs[-1]
        assert spendable[1][list(spendable[1].keys())[0]].amount == amounts[-1]

    # Send back to the master wallet.
    harness.return_funds(wallet, watch, sum(amounts))
    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
    def serialize(self) -> Tuple[bytes, bytes]:
        """Serialize a MoneroSpendableTransaction."""

        # Serialize the version and lock time.
        prefix: bytes = bytes([2, 0])

        prefix += to_var_int(len(self.inputs))
        for input_i in self.inputs:
            prefix += bytes([2, 0])
            prefix += to_var_int(len(input_i.mixins))
            for mixin in input_i.mixins:
                prefix += to_var_int(mixin)
            prefix += input_i.image

        prefix += to_var_int(len(self.output_keys))
        for o in range(len(self.output_keys)):
            prefix += bytes([0, 2])
            prefix += self.output_keys[o]

        prefix += to_var_int(len(self.extra))
        prefix += self.extra

        if self.signatures is None:
            return (ed.H(prefix), bytes())

        # RangeCT below. All of our Transactions are Simple Padded Bulletproofs (type 4).
        base: bytes = bytes([5])
        base += to_var_int(self.fee)

        for o in range(len(self.output_keys)):
            for i in range(8):
                base += bytes([self.signatures.ecdh_info[o].amount[i]])

        for out_public_key in self.signatures.out_public_keys:
            for i in range(32):
                base += bytes([out_public_key.mask[i]])

        # Prunable info.
        prunable: bytes = to_var_int(len(
            self.signatures.prunable.bulletproofs))
        for bulletproof in self.signatures.prunable.bulletproofs:
            for i in range(32):
                prunable += bytes([bulletproof.capital_a[i]])
            for i in range(32):
                prunable += bytes([bulletproof.s[i]])
            for i in range(32):
                prunable += bytes([bulletproof.t1[i]])
            for i in range(32):
                prunable += bytes([bulletproof.t2[i]])
            for i in range(32):
                prunable += bytes([bulletproof.taux[i]])
            for i in range(32):
                prunable += bytes([bulletproof.mu[i]])

            prunable += to_var_int(len(bulletproof.l))
            for l in bulletproof.l:
                for i in range(32):
                    prunable += bytes([l[i]])

            prunable += to_var_int(len(bulletproof.r))
            for r in bulletproof.r:
                for i in range(32):
                    prunable += bytes([r[i]])

            for i in range(32):
                prunable += bytes([bulletproof.a[i]])
            for i in range(32):
                prunable += bytes([bulletproof.b[i]])
            for i in range(32):
                prunable += bytes([bulletproof.t[i]])

        for cl in self.signatures.prunable.CLSAGs:
            for s in cl.s:
                for i in range(32):
                    prunable += bytes([s[i]])
            for i in range(32):
                prunable += bytes([cl.c1[i]])
            for i in range(32):
                prunable += bytes([cl.D[i]])

        for pseudo_out in self.signatures.prunable.pseudo_outs:
            for i in range(32):
                prunable += bytes([pseudo_out[i]])

        return (
            ed.H(ed.H(prefix) + ed.H(base) + ed.H(prunable)),
            prefix + base + prunable,
        )
Ejemplo n.º 7
0
def varint_serializations_test() -> None:
    for _ in range(500):
        num: int = randint(0, 2**16)
        assert from_var_int(to_var_int(num), 0)[0] == num
        assert from_rpc_var_int(to_rpc_var_int(num), 0)[0] == num