#!/usr/bin/env python3
"""Recompute what the paywall page computes, independently of paivana.

This mirrors src/frontend/paywall.js -- makePaivanaId(),
timestampRoundedToBuffer() and encodeCrock() -- because agreement between
the two ends IS the protocol: neither side ever sends the paivana ID to
the other, each derives it from its own copy of the inputs.

Usage:  paivana_id.py <expiration-seconds> <website> [<nonce-hex>]
Prints: <nonce-crockford> <paivana-id>
"""
import base64
import hashlib
import os
import struct
import sys

# GNUnet's Crockford base32 alphabet (gnunet strings.c), which is what
# GNUNET_STRINGS_string_to_data() reads back on the daemon side.
ENC_TABLE = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"


def encode_crock(data: bytes) -> str:
    out = []
    bit_buf = 0
    num_bits = 0
    pos = 0
    size = len(data)
    while pos < size or num_bits > 0:
        if pos < size and num_bits < 5:
            bit_buf = (bit_buf << 8) | data[pos]
            pos += 1
            num_bits += 8
        if num_bits < 5:
            bit_buf <<= 5 - num_bits
            num_bits = 5
        out.append(ENC_TABLE[(bit_buf >> (num_bits - 5)) & 31])
        num_bits -= 5
    return "".join(out)


def make_paivana_id(cur_time: int, nonce: bytes, website: str) -> str:
    buf = nonce + website.encode("utf-8") + b"\0" + struct.pack(">Q", cur_time * 1000 * 1000)
    digest = hashlib.sha256(buf).digest()
    tail = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")
    return f"{cur_time}-{tail}"


def main() -> int:
    if len(sys.argv) not in (3, 4):
        sys.stderr.write(__doc__)
        return 2
    cur_time = int(sys.argv[1])
    website = sys.argv[2]
    nonce = bytes.fromhex(sys.argv[3]) if len(sys.argv) == 4 else os.urandom(16)
    print(f"{encode_crock(nonce)} {make_paivana_id(cur_time, nonce, website)}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
