Last active
December 16, 2025 23:19
-
-
Save aparatext/a109b74beb33e1a9f05991bf8c23a8e6 to your computer and use it in GitHub Desktop.
ffprint, ffprint-ck - human-friendly, 128-bit secure, typo-detecting file fingerprints
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S uv run --script | |
| # /// script | |
| # requires-python = ">=3.13" | |
| # dependencies = [ "blake3"] | |
| # /// | |
| from sys import stdin | |
| from blake3 import blake3 | |
| from itertools import batched, cycle | |
| ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' | |
| COLORS = ['\033[1;36m', '\033[1;33m'] # bright cyan, bright yellow | |
| RESET = '\033[0m' | |
| def to_base58(b: bytes) -> str: | |
| n = int.from_bytes(b, 'big') | |
| chars = [] | |
| while n: | |
| n, rem = divmod(n, 58) | |
| chars.append(ALPHABET[rem]) | |
| return '1' * (len(b) - len(chars)) + ''.join(reversed(chars)) | |
| data = stdin.buffer.read() | |
| hashed = blake3(data).digest()[:16] | |
| cksum = blake3(hashed).digest()[:2] | |
| fprint = to_base58(hashed + cksum) | |
| chunks = map(''.join, batched(fprint, 5)) | |
| colored = ' '.join(f"{color}{chunk}{RESET}" for color, chunk in zip(cycle(COLORS), chunks)) | |
| print(colored) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S uv run --script | |
| # /// script | |
| # requires-python = ">=3.13" | |
| # dependencies = ["blake3"] | |
| # /// | |
| from sys import stdin, argv, exit, stderr | |
| from blake3 import blake3 | |
| ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' | |
| def from_base58(s: str) -> bytes: | |
| n = 0 | |
| for c in s: n = n*58 + ALPHABET.index(c) | |
| b = n.to_bytes((n.bit_length()+7)//8, 'big') | |
| pad = 0 | |
| for c in s: | |
| if c=='1': pad+=1 | |
| else: break | |
| return b'\0'*pad + b | |
| if len(argv)<2: | |
| print("Usage: cat file | ffprint-ck <fingerprint>", file=stderr) | |
| exit(2) | |
| fprint = argv[1].replace(' ','').strip() | |
| try: raw = from_base58(fprint) | |
| except ValueError as e: | |
| print(f"Invalid Base58 fingerprint: {e}", file=stderr) | |
| exit(2) | |
| if len(raw)<18: | |
| print("Fingerprint too short", file=stderr) | |
| exit(2) | |
| hashed, cksum = raw[:-2], raw[-2:] | |
| if blake3(hashed).digest()[:2] != cksum: | |
| print("Checksum invalid", file=stderr) | |
| exit(2) | |
| data = stdin.buffer.read() | |
| if blake3(data).digest()[:16] == hashed: | |
| print("Fingerprint match", file=stderr) | |
| exit(0) | |
| else: | |
| print("Fingerprint mismatch", file=stderr) | |
| exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment