Verified Commit e0e5d973 authored by Jakob Moser's avatar Jakob Moser
Browse files

Implement signing exports

parent 92f38238
Loading
Loading
Loading
Loading
+34 −13
Changes for poolpay/fints/__main__.py: 34 added lines, 13 removed lines.
Original line number Diff line number Diff line
import getpass
import sys
from pathlib import Path
from typing import Annotated

import minisign
import typer
from pyrage import encrypt as age_encrypt
from pyrage import x25519
@@ -11,7 +13,7 @@ from poolpay import db, paths
from poolpay.model.BankAccount import BankAccount
from poolpay.model.messages.UpdateBalanceMessage import UpdateBalanceMessage
from poolpay.model.Transaction import Transaction
from poolpay.paths import age_public_key_file
from poolpay.paths import age_public_key_file, minisign_private_key_file

app = typer.Typer()

@@ -35,6 +37,18 @@ def sync() -> None:
        bank_account.sync(password)


def _encrypt_if_requested(content: str, encrypt: bool) -> str:
    if encrypt:
        recipient = x25519.Recipient.from_str(
            age_public_key_file.read_text(encoding="utf-8")
        )
        return age_encrypt(content.encode("utf-8"), [recipient], armored=True).decode(
            "utf-8"
        )
    else:
        return content


@app.command()
def export(
    filter_purpose: Annotated[
@@ -49,11 +63,19 @@ def export(
            help="If the export should be encrypted with the poolpaypi age key."
        ),
    ] = False,
    sign: Annotated[
        bool,
        typer.Option(
            help="If the export should be signed with the jakob minisign key."
        ),
    ] = False,
) -> None:
    """
    Export all relevant transactions as messages to be applied in a running PoolPay instance via the Wire server.

    Produces output in JSONL format to stdout.
    Produces output in JSONL format or as an ASCII-armored age blob to STDOUT.

    If signing is requested, produces detached signature to STDERR.
    """
    base_selection = select(Transaction)
    selection = (
@@ -72,18 +94,17 @@ def export(
        for transaction in transactions
    ]

    messages_string = "\n".join(str(m) for m in messages)
    payload = _encrypt_if_requested("\n".join(str(m) for m in messages), encrypt)
    print(payload)

    if encrypt:
        recipient = x25519.Recipient.from_str(
            age_public_key_file.read_text(encoding="utf-8")
        )
        encrypted = age_encrypt(
            messages_string.encode("utf-8"), [recipient], armored=True
        )
        print(encrypted.decode("utf-8"))
    else:
        print(messages_string)
    if sign:
        key = minisign.SecretKey.from_file(minisign_private_key_file)
        # Apparently, py-minisign always needs an encrypted secret key. We don't need that, but we still need to do
        # something, so we encrypt the key with an empty string as password.
        key.decrypt("")
        signature = key.sign(payload.encode("utf-8"))

        print(bytes(signature).decode("utf-8"), file=sys.stderr)


if __name__ == "__main__":
+4 −0
Changes for poolpay/paths.py: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -9,6 +9,8 @@ db_file = instance_dir / "poolpay.db"
socket_path = proj_dir / "wire.socket"
age_public_key_file = keys_dir / "poolpaypi.age-public"
age_secret_key_file = keys_dir / "private" / "poolpaypi.age-secret"
minisign_public_key_file = keys_dir / "jakob.minisign-public"
minisign_private_key_file = keys_dir / "private" / "jakob.minisign-private"

__all__ = [
    "proj_dir",
@@ -19,4 +21,6 @@ __all__ = [
    "socket_path",
    "age_public_key_file",
    "age_secret_key_file",
    "minisign_public_key_file",
    "minisign_private_key_file",
]
+1 −0
Changes for pyproject.toml: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ dependencies = [
    "fints>=5.0.0",
    "mfrc522~=0.0.7",
    "pi-rc522~=2.3.0",
    "py-minisign>=0.13.2",
    "pygame~=2.6.1",
    "pyrage>=1.3.0",
    "rpi-lgpio~=0.6",
+130 −0

File changed.

Preview size limit exceeded, changes collapsed.