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

Implement signing exports

parent 92f38238
Loading
Loading
Loading
Loading
+34 −13
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
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
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.