paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

paivana_id.py (1910B)


      1 #!/usr/bin/env python3
      2 """Recompute what the paywall page computes, independently of paivana.
      3 
      4 This mirrors src/frontend/paywall.js -- makePaivanaId(),
      5 timestampRoundedToBuffer() and encodeCrock() -- because agreement between
      6 the two ends IS the protocol: neither side ever sends the paivana ID to
      7 the other, each derives it from its own copy of the inputs.
      8 
      9 Usage:  paivana_id.py <expiration-seconds> <website> [<nonce-hex>]
     10 Prints: <nonce-crockford> <paivana-id>
     11 """
     12 import base64
     13 import hashlib
     14 import os
     15 import struct
     16 import sys
     17 
     18 # GNUnet's Crockford base32 alphabet (gnunet strings.c), which is what
     19 # GNUNET_STRINGS_string_to_data() reads back on the daemon side.
     20 ENC_TABLE = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
     21 
     22 
     23 def encode_crock(data: bytes) -> str:
     24     out = []
     25     bit_buf = 0
     26     num_bits = 0
     27     pos = 0
     28     size = len(data)
     29     while pos < size or num_bits > 0:
     30         if pos < size and num_bits < 5:
     31             bit_buf = (bit_buf << 8) | data[pos]
     32             pos += 1
     33             num_bits += 8
     34         if num_bits < 5:
     35             bit_buf <<= 5 - num_bits
     36             num_bits = 5
     37         out.append(ENC_TABLE[(bit_buf >> (num_bits - 5)) & 31])
     38         num_bits -= 5
     39     return "".join(out)
     40 
     41 
     42 def make_paivana_id(cur_time: int, nonce: bytes, website: str) -> str:
     43     buf = nonce + website.encode("utf-8") + b"\0" + struct.pack(">Q", cur_time * 1000 * 1000)
     44     digest = hashlib.sha256(buf).digest()
     45     tail = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=")
     46     return f"{cur_time}-{tail}"
     47 
     48 
     49 def main() -> int:
     50     if len(sys.argv) not in (3, 4):
     51         sys.stderr.write(__doc__)
     52         return 2
     53     cur_time = int(sys.argv[1])
     54     website = sys.argv[2]
     55     nonce = bytes.fromhex(sys.argv[3]) if len(sys.argv) == 4 else os.urandom(16)
     56     print(f"{encode_crock(nonce)} {make_paivana_id(cur_time, nonce, website)}")
     57     return 0
     58 
     59 
     60 if __name__ == "__main__":
     61     sys.exit(main())