test_base64url.sh (3792B)
1 #!/bin/sh 2 # Check that the paywall's base64url encoder agrees with the daemon's. 3 # 4 # The paivana ID is "<expiration>-<base64url(sha256(...))>". The daemon 5 # builds it with GNUNET_STRINGS_base64url_encode(); the browser rebuilds 6 # it in paywall.js to recognise the payment it just made. If the two 7 # spell base64url differently the ID never matches, the payment appears 8 # not to go through, and neither side reports anything wrong -- so this 9 # compares them directly, on vectors the daemon itself generates. 10 # 11 # It is worth having because the two have already diverged twice: once 12 # on the decode side (the daemon emits the RFC 4648 section 5 alphabet, 13 # atob() only understands section 4) and once on the encode side (the 14 # browser used Uint8Array.prototype.toBase64, which older engines do not 15 # have). Both broke the payment path. 16 # 17 # Exits 77 ("skipped") when there is no node to run the JavaScript in. 18 # 19 # Environment: 20 # SRCDIR directory holding paywall.js (default: this script's ../frontend) 21 # BUILDDIR directory holding base64url_vectors (default: $PWD) 22 23 set -eu 24 25 srcdir=${SRCDIR:-$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)} 26 builddir=${BUILDDIR:-$PWD} 27 frontend="$srcdir/../frontend" 28 29 if ! command -v node >/dev/null 2>&1; then 30 echo "no node found, skipping the base64url cross-check" >&2 31 exit 77 32 fi 33 if [ ! -x "$builddir/base64url_vectors" ]; then 34 echo "base64url_vectors not built at $builddir" >&2 35 exit 1 36 fi 37 if [ ! -f "$frontend/paywall.js" ]; then 38 echo "paywall.js not found at $frontend" >&2 39 exit 1 40 fi 41 42 "$builddir/base64url_vectors" > "$builddir/base64url_vectors.txt" 43 44 PAYWALL_JS="$frontend/paywall.js" \ 45 VECTORS="$builddir/base64url_vectors.txt" \ 46 node -e ' 47 const fs = require("fs"); 48 49 // Lift base64url() out of paywall.js rather than reimplementing it: 50 // a copy here would be a second implementation to keep in step, and 51 // this test exists precisely because keeping implementations in step 52 // by hand is what failed. The module cannot simply be imported -- 53 // it reads window.location at load -- so the one function is cut out 54 // by matching braces from its declaration. 55 const src = fs.readFileSync(process.env.PAYWALL_JS, "utf8"); 56 const start = src.indexOf("function base64url("); 57 if (start < 0) { 58 console.error("could not find base64url() in paywall.js;" + 59 " if it was renamed, update this test rather than delete it"); 60 process.exit(1); 61 } 62 let depth = 0, end = -1, seen = false; 63 for (let i = src.indexOf("{", start); i < src.length; i++) { 64 if (src[i] === "{") { depth++; seen = true; } 65 else if (src[i] === "}") { depth--; } 66 if (seen && depth === 0) { end = i + 1; break; } 67 } 68 if (end < 0) { 69 console.error("could not delimit base64url() in paywall.js"); 70 process.exit(1); 71 } 72 const base64url = new Function( 73 `${src.slice(start, end)}; return base64url;`)(); 74 75 const lines = fs.readFileSync(process.env.VECTORS, "utf8") 76 .split("\n").filter((l) => l.length > 0); 77 if (lines.length < 300) { 78 console.error(`only ${lines.length} vectors; expected the generator` + 79 " to produce hundreds -- refusing to pass vacuously"); 80 process.exit(1); 81 } 82 let bad = 0; 83 for (const line of lines) { 84 const sp = line.indexOf(" "); 85 const hex = line.slice(0, sp); 86 const want = line.slice(sp + 1); 87 const bytes = hex === "-" 88 ? new Uint8Array(0) 89 : Uint8Array.from(hex.match(/../g).map((h) => parseInt(h, 16))); 90 const got = base64url(bytes); 91 if (got !== want) { 92 if (bad < 5) { 93 console.error(`MISMATCH for ${hex}: paywall.js gave "${got}",` + 94 ` the daemon gives "${want}"`); 95 } 96 bad++; 97 } 98 } 99 if (bad > 0) { 100 console.error(`${bad} of ${lines.length} vectors disagree`); 101 process.exit(1); 102 } 103 console.log(`base64url agrees with the daemon on ${lines.length} vectors`); 104 '