onlyn00bs-badge

OnlyN00bs: a DEF CON 34 friend-finder badge. ESP32 firmware, Web Bluetooth setup app, printable case
git clone https://git.virtualshack.io/onlyn00bs-badge.git
Log | Files | Refs | README | LICENSE

index.html (21669B)


      1 <!DOCTYPE html>
      2 <html lang="en">
      3 <head>
      4 <meta charset="utf-8">
      5 <meta name="viewport" content="width=device-width, initial-scale=1">
      6 <title>onlyn00bs badge</title>
      7 <meta name="description" content="OnlyN00bs — a DEF CON friend-finder badge. Setup, screens, battery safety, and what it broadcasts.">
      8 <meta name="color-scheme" content="dark light">
      9 <meta property="og:title" content="onlyn00bs badge">
     10 <meta property="og:description" content="A DEF CON friend-finder badge. Setup, screens, battery safety, and what it broadcasts.">
     11 <meta property="og:type" content="website">
     12 <meta property="og:site_name" content="virtualshack">
     13 <meta property="og:url" content="https://badge.virtualshack.io/">
     14 <!-- og:image MUST be an absolute URL — Discord, Slack and Twitter all refuse to resolve a
     15      relative one, and the card silently renders with no image. 1200x630 (1.91:1) is the
     16      shape those unfurlers crop to; feeding them the raw 4:3 photo means each platform
     17      picks its own crop. -->
     18 <meta property="og:image" content="https://badge.virtualshack.io/assets/img/og-badge.jpg">
     19 <meta property="og:image:type" content="image/jpeg">
     20 <meta property="og:image:width" content="1200">
     21 <meta property="og:image:height" content="630">
     22 <meta property="og:image:alt" content="Seven OnlyN00bs badges in coloured 3D-printed cases, screens lit showing the finder, points, report, best-friends, home and battery views.">
     23 <!-- Without summary_large_image, Twitter/X and some clients render a small square thumbnail
     24      instead of the wide card. -->
     25 <meta name="twitter:card" content="summary_large_image">
     26 <meta name="twitter:title" content="onlyn00bs badge">
     27 <meta name="twitter:description" content="A DEF CON friend-finder badge. Setup, screens, battery safety, and what it broadcasts.">
     28 <meta name="twitter:image" content="https://badge.virtualshack.io/assets/img/og-badge.jpg">
     29 <!-- ?v= must match CACHE in sw.js — bump BOTH on any content change. index.html is served
     30      no-cache but /assets/ is not, and Cloudflare stretches it further (origin says
     31      max-age=3600, the edge served 14400), so without a version in the URL a CSS change is
     32      invisible to visitors for hours after a deploy. A new query = a new edge cache key =
     33      it lands immediately. Safe for offline: sw.js matches with ignoreSearch, so this still
     34      resolves to the precached bare URL. -->
     35 <link rel="stylesheet" href="assets/badge.css?v=39">
     36 <link rel="icon" type="image/svg+xml" href="assets/icon.svg">
     37 <link rel="manifest" href="manifest.webmanifest">
     38 <meta name="theme-color" content="#212121">
     39 </head>
     40 <body>
     41 <main class="wrapper" id="top">
     42 
     43   <h1>// onlyn00bs</h1>
     44   <p class="tagline">a friend-finder badge for def con 34 · firmware v1.10</p>
     45   <p class="small muted"><a href="https://virtualshack.io/">virtualshack.io</a> / badge</p>
     46 
     47   <!-- generated from the gallery manifest: the first row, once there are photos -->
     48   <!-- gallery-preview:begin -->
     49   <h2 id="photos">// photos</h2>
     50   <div class="gallery gallery-preview">
     51     <figure>
     52       <a href="gallery/" data-w="1600" data-h="1200"><img src="assets/gallery/thumbs/dc34-01.jpg" alt="A green OnlyN00bs badge on the finder screen beside the official DEF CON 34 badge and a challenge coin, with a purple foot charm on the case"
     53            width="720" height="540" loading="eager" decoding="async"></a>
     54     </figure>
     55     <figure>
     56       <a href="gallery/" data-w="1234" data-h="929"><img src="assets/gallery/thumbs/dc34-02.jpg" alt="A hand holding a purple OnlyN00bs badge on the finder screen, with a green badge on a lanyard behind it"
     57            width="720" height="542" loading="eager" decoding="async"></a>
     58     </figure>
     59     <figure>
     60       <a href="gallery/" data-w="929" data-h="1238"><img src="assets/gallery/thumbs/dc34-03.jpg" alt="A lavender badge with foot and gummy-bear charms, its finder screen listing five nearby badges"
     61            width="540" height="720" loading="eager" decoding="async"></a>
     62     </figure>
     63   </div>
     64   <p class="small"><a href="gallery/">all photos →</a></p>
     65   <!-- gallery-preview:end -->
     66 
     67   <p style="text-align:center"><a class="cta" href="provision/">Set up my badge →</a></p>
     68 
     69   <!-- Changelog. Newest first, CAPPED AT THE 3 MOST RECENT — add to the top and drop the
     70        bottom entry, so this stays a glance and never grows into a wall. Older entries live
     71        in git history, not here. Badge changes and page corrections both land in it, because
     72        from a reader's side "the badge does something new" and "this page was wrong" are the
     73        same question: is what I read last time still true? Keep entries to one line and
     74        user-facing (what changed for them, not which commit did it). -->
     75   <div class="note log">
     76     <p><b>What's new</b> <span class="small muted">— newest first</span></p>
     77     <ul class="small">
     78       <li><b>2026-09-26</b> · <b>The badge is open source</b> — firmware, setup app, this site,
     79         and the case models. <a href="#source">Source + license</a>.</li>
     80       <li><b>2026-08-05</b> · New <b>Deep sleep</b> feature — put the badge into low power mode without unplugging the
     81         battery. <code>Badge Settings → Power → Deep sleep</code>, then press the button to
     82         wake it. <b>Reboot has moved into the same Power menu.</b> Badges flashed before
     83         2026-08-05 need a firmware update to get it.</li>
     84       <li><b>2026-08-01</b> · <b>The setup app is live on this site</b> — hit
     85         <a href="provision/">set up my badge</a> and go. Once you've loaded the PWA, it will
     86         remain available for offline browser use.</li>
     87     </ul>
     88   </div>
     89 
     90   <nav class="toc">
     91     <ul>
     92       <li><a href="#what">// the concept</a></li>
     93       <li><a href="#hardware">// the hardware</a></li>   
     94       <li><a href="#software">// the firmware</a></li>            
     95       <li><a href="#case">// the case, lanyard, &amp; accessories</a></li>
     96       <li><a href="#source">// the source</a></li>
     97       <!-- the link appears once the gallery has photos -->
     98       <!-- gallery-link:begin -->
     99       <li><a href="gallery/">// photos</a></li>
    100       <!-- gallery-link:end -->
    101       <li><a href="#setup">// setup</a></li>
    102       <li><a href="#screens">// usage</a></li>
    103       <li><a href="#button">// the button</a></li>
    104       <li><a href="#battery">// battery &amp; safety</a></li>
    105       <li><a href="#trouble">// troubleshooting</a></li>
    106       <li><a href="#privacy">// privacy</a></li>
    107     </ul>
    108   </nav>
    109 
    110   <h2 id="what">// the concept</h2>
    111 
    112 <p>OnlyN00bs is not just an amateur badge project for DefCon 34, it's an amateur badge project for DefCon 34 brought to life through relentless tokenmaxxing. Could anything be more appropriate for 2026?</p>
    113 
    114 <p>Each badge scans for fellow n00bs and feet enthusiasts, displaying real-time proximity information and beaming your own handle - and ASCII avatar - to those in range. Avatars are user-customizable — faces, name, the lot. Earn made up points. Earn even more made up points by being in range of your friends.</p>
    115 
    116 <p>Designed to be at least mildly secure, every beacon your badge sends and receives over ESP-NOW is HMAC-signed.</p>
    117 
    118   <h2 id="hardware">// the hardware (tl;dr)</h2>
    119 <ul>
    120   <li>ESP32-E (tariffs oof)</li>
    121 <li>2.42" SSD1309 OLED (blue, yellow, and green available in limited quantities)</li>
    122 <li>2000 mAh LiPo battery (12-14 hours per charge observed in testing)</li>
    123 <li>USB-C charging (🫡 🇪🇺 )</li>
    124 <li>A single button</li>
    125 </ul>
    126 
    127   <h2 id="software">// the firmware (tl;dr)</h2>
    128   <li>"Secure" communications using "military-grade encryption" (a.k.a. HMAC signing)</li>
    129   <ul><li><p class="small">Currently clean on OpSec 👊 🇺🇸🔥</p></li></ul>
    130   <li>Beyond simple friend radar, badges track encounters over time, best friend(s), and more.</li>
    131 <ul><li>Points are awarded for time spent near fellow badge-holders, with group bonus multipliers</li></ul>
    132 <li>Customizable integrated virtual ASCII avatar that celebrates milestones, notifies you of low battery (and when to shower), and more</li>
    133 <li>Points-history graph with multiple views</li>
    134 <li>Battery gauge (a little rough) and storage remaining gauge</li>
    135 <li>Fully wireless BLE provisioning via PWA, on-screen pairing code to deter casual interlopers</li>
    136 <li>Points and encounter data export for end-of-Con visualizations (currently in development)</li>
    137   
    138   <h2 id="case">// the case</h2>
    139   <p>Each badge comes in a 3D printed glow-in-the-dark PETG plastic case. There are four primary color options: green, blue, pink (purple?), and multicolor.</p>
    140   <p class="small">Note: ignore the lanyard in this photo, it was used for testing.</p>
    141 
    142   <a class="photo" href="assets/img/all_colors.jpg" target="_blank" rel="noopener">
    143     <img src="assets/img/thumbs/all_colors.jpg" alt="the four case colours side by side: green, blue, pink and multicolour"
    144          width="900" height="677" loading="lazy" decoding="async"></a>
    145   
    146   <p>In order of glowieness ("luminescence" if you're snooty), the green is the brightest, followed by blue/multicolor, and pink:</p>
    147 
    148   <div class="shots">
    149     <a class="photo" href="assets/img/glow_1.jpg" target="_blank" rel="noopener">
    150     <img src="assets/img/thumbs/glow_1.jpg" alt="cases glowing in the dark, first comparison shot"
    151          width="677" height="900" loading="lazy" decoding="async"></a>
    152     <a class="photo" href="assets/img/glow_2.jpg" target="_blank" rel="noopener">
    153     <img src="assets/img/thumbs/glow_2.jpg" alt="cases glowing in the dark, second comparison shot"
    154          width="677" height="900" loading="lazy" decoding="async"></a>
    155   </div>
    156   <h3 id="lanyard">// the lanyard</h3>
    157   <p>Each badge will come with a lanyard:</p>
    158   <a class="photo" href="assets/img/lanyard.jpg" target="_blank" rel="noopener">
    159     <img src="assets/img/thumbs/lanyard.jpg" alt="the lanyard that ships with each badge"
    160          width="900" height="675" loading="lazy" decoding="async"></a>
    161   <h3 id="accessories">// the accessories</h3>
    162   <p>The two openings in the case were designed to be backwards compatible with DC33 OnlyFeet/N00b_Sec Croc jibbitz (and the rest of <a href="https://www.crocs.com/c/jibbitz" target="_blank" rel="noopener">Crocs Jibbitz</a> ecosystem... bring 'em if ya got 'em.). Equally, you can buy them from Crocs, 3D print them, and/or buy them from <a href="https://www.etsy.com/market/jibbitz" target="_blank" rel="noopener">Etsy</a>.</p>
    163   <p>Additionally, a variety of 3D printed accessories (e.g., as seen in the photo above) will be provided for customization. These are still in development (but if you have an idea for something really funny, let me know.):</p>
    164   <a class="photo" href="assets/img/accessories.jpg" target="_blank" rel="noopener">
    165     <img src="assets/img/thumbs/accessories.jpg" alt="3D-printed accessories that clip into the case openings"
    166          width="900" height="675" loading="lazy" decoding="async"></a>
    167 
    168   <h2 id="source">// the source</h2>
    169   <p>The badge is open source: the firmware, the setup app, this site, the parts list,
    170   and the parametric case (STLs included, so you can print one without touching CAD).</p>
    171   <p style="text-align:center"><a class="cta" href="https://git.virtualshack.io/onlyn00bs-badge/">Browse the source →</a></p>
    172   <p class="small muted" style="text-align:center"><code>git clone https://git.virtualshack.io/onlyn00bs-badge.git</code> · <a href="https://git.virtualshack.io/onlyn00bs-badge/releases/">releases</a></p>
    173   <ul>
    174     <li><b>License:</b> MIT for the code; CC BY-SA 4.0 for the case models, docs and photos.</li>
    175     <li><b>Not included: the group key.</b> Badges only see badges signed with the same key, so
    176       build your own crew with your own key and it won't talk to ours. The README says how.</li>
    177     <li><b>Also not included: anyone's data.</b> Encounter logs and the list of who got which badge
    178       stayed private. The repo is the badge, not the con.</li>
    179   </ul>
    180 
    181   <h2 id="setup">// setup</h2>
    182 
    183   <p>You need: the badge, a computer (with <b>Chrome or Edge</b>) or an Android phone, and about two
    184   minutes. <b>Not an iPhone</b>: setup uses Web Bluetooth, which doesn't exist on iOS in any
    185   browser.</p>
    186 
    187   <ol>
    188     <li><b>Put the badge in setup mode.</b> From the badge: <code>OPTIONS → Badge Settings →
    189       Developer Options → Provisioning Mode</code>. (You can also hold the microcontroller's
    190       <code>BOOT</code> button while powering it on — but that means opening the case, so the
    191       menu is the easier route.)</li>
    192     <li><b>Read the 3-character pairing code</b> off the badge screen (something like
    193       <code>K7Q</code>). This stops strangers from reconfiguring your badge.</li>
    194     <li><b>Open <a href="provision/">the setup app</a></b>, type in your handle, the pairing code, optionally configure an avatar, then hit <em>connect &amp; save</em>.</li>
    195     <li><b>Select your badge</b> from the browser's Bluetooth pop-up. It will show up as
    196       <code>badge-setup-&lt;MAC&gt;</code>. The badge then reboots into normal mode with
    197       your handle set, and its internal clock (used to timestamp your encounters — it isn't
    198       shown on screen) set from your device.</li>
    199   </ol>
    200   <div class="note stop"><p>If that internal clock drifts after a battery swap — your con REPORT
    201   times go "relative" — the same web app has a <em>sync clock only</em> button that re-syncs it
    202   without touching your handle.</p>
    203 </div>
    204 
    205   <div class="note warn">
    206     <p><b>Stuck in setup mode? Hold the badge's button for 5 seconds.</b> A badge in setup mode is
    207     not beaconing and won't see anyone, and will sit there forever. Holding the button
    208     counts down on screen and reboots straight to HOME.</p>
    209   </div>
    210     <p class="small">Note: A badge that has never been set up will name itself something like
    211     <code>n00b-1858</code> on the way out, so that it works immediately.</p>
    212   <h2 id="screens">// usage</h2>
    213 
    214   <p>Seven views, in a loop. Double-tap the button to walk forward through them:</p>
    215 
    216 <pre><code>  double-tap walks down this list          a single tap here does…
    217   ┌──────────────────────────────────────────────────────────────
    218   │ HOME      handle, avatar, points, battery
    219   │ FINDER    who's in range, closest first     page the list
    220   │ REPORT    your con so far
    221   │ POINTS    points over time, as a graph      cycle 5d / 24h / 1h
    222   │ FRIENDS   best-friends ledger
    223   │ BATTERY   charge level                      flip to storage
    224   │ OPTIONS   settings                          move the cursor
    225   └── and wraps back to HOME ────────────────────────────────────
    226 
    227   hold the button 1.5s from anywhere → jump straight to HOME
    228 </code></pre>
    229 
    230   <dl>
    231     <dt>HOME</dt>
    232     <dd>Your handle, your avatar, total points, and battery. The default view.</dd>
    233 
    234     <dt>FINDER</dt>
    235     <dd>Who's in range right now, closest first. <b>Tap</b> to page through the list if
    236       there are more badges than fit. A badge drops off ~15 seconds after its last
    237       beacon.</dd>
    238 
    239     <dt>REPORT</dt>
    240     <dd>Your con so far: an encounter summary, snapshotted when you arrive on the
    241       screen.</dd>
    242 
    243     <dt>POINTS</dt>
    244     <dd>Points over time as a graph. <b>Tap</b> to cycle the window: 5 days / 24 hours /
    245       1 hour.</dd>
    246 
    247     <dt>FRIENDS</dt>
    248     <dd>Your best-friends ledger — who you've actually spent time near, ranked by shared
    249       minutes.</dd>
    250 
    251     <dt>BATTERY</dt>
    252     <dd>Live battery gauge. <b>Tap</b> to flip to the storage readout (how full the
    253       badge's flash is).</dd>
    254 
    255     <dt>OPTIONS</dt>
    256     <dd>Note: In OPTIONS, <b>tap</b> moves the cursor down a row,
    257       <b>double-tap</b> selects it (and double-taps back out).</dd>
    258   </dl>
    259 
    260   <h2 id="button">// the button</h2>
    261 
    262   <p>One button, three gestures:</p>
    263 
    264 <pre><code>   tap          →  do this screen's thing   (page the list, cycle the graph…)
    265    tap tap      →  next screen
    266    hold 1.5s    →  jump back to HOME        (works from anywhere)
    267 
    268    in SETUP MODE only:
    269    hold 5s      →  leave setup, reboot to HOME
    270 </code></pre>
    271 
    272   <p>If you're ever lost, hold the button. You'll land on HOME.</p>
    273 
    274   <h2 id="battery">// battery &amp; safety</h2>
    275 
    276   <p>It's a lithium-polymer pouch cell and we're in the desert. Do the math.</p>
    277 
    278   <ul>
    279     <li><b>Tested runtime:</b> about <b>14 hours</b>. Bring a power bank or charge nightly.</li>
    280     <li><b>Remember</b> that LiPo batteries left empty for more than a few hours can be permanently damaged.</li>
    281     <li><b>Charging:</b> via USB-C, roughly <b>4 hours</b> from empty to full. Charge it on a
    282       hard surface — not on a hotel bed, a couch, or buried in a backpack.</li>
    283     <li><b>Never</b> charge a pack that is hot, puffy, swollen, or has been crushed or
    284       punctured. A swollen pouch is done; stop using it. <b>I'll give you a new one for free.</b></li>
    285     <li><b>Never leave it in a parked car</b>, on a dashboard, or in direct sun. A closed car
    286       in Vegas will cook a pouch cell well past anything it's rated for.</li>
    287     <li>Don't leave it charging unattended.</li>
    288     <li><b>Deep sleep is not "off":</b> a sleeping badge still draws a (very) small amount of power and will die/kill the battery eventually. For longer periods (8+ hours), charge it to roughly half and unplug the battery at the JST connector.</li>
    289     <li>Dead or damaged cells go to battery-recycling (please don't toss them in the trash).</li>
    290   </ul>
    291 
    292   <h2 id="trouble">// troubleshooting</h2>
    293 
    294   <dl>
    295     <dt>The setup page has no "connect" button, or the button does nothing</dt>
    296     <dd>You're on a browser without Web Bluetooth. Use Chrome or Edge on a laptop or
    297       Android. iOS cannot do this at all — I wasn't lying before lol.</dd>
    298 
    299     <dt>FINDER shows nobody</dt>
    300     <dd>In order of likelihood: (1) nobody is actually in range — the radio range is in the tens
    301       of meters (and bodies absorb 2.4 GHz), (2) the other badge is in setup
    302       mode, so it isn't beaconing, (3) mismatched builds — if multiple people see
    303       nothing, compare firmware versions on <code>OPTIONS → Whoami</code>.</dd>
    304 
    305     <dt>My REPORT times look wrong or say "relative"</dt>
    306     <dd>There's no clock on screen, but the badge still keeps time internally to timestamp your
    307       encounters — and it has no battery-backed clock, so it gets the time from your phone or
    308       laptop during setup. After a battery swap that can drift; the REPORT view then shows
    309       <em>relative</em> times instead of real ones. Run <em>sync clock only</em> in the setup app
    310       to fix it. Don't do this on an iPhone-only trip — you can't (sensing the theme yet?).</dd>
    311 
    312     <dt>I'm stuck on the SETUP MODE screen</dt>
    313     <dd><b>Hold the button for 5 seconds.</b> The screen counts down and the badge reboots
    314       to HOME. Works with no app and no network. One catch: it wants a <em>fresh</em> press,
    315       so if you got here by holding <code>BOOT</code> through power-on, let go first, then
    316       hold again.</dd>
    317 
    318     <dt>My badge won't connect over Bluetooth</dt>
    319     <dd>It only advertises while in setup mode. If the browser's chooser is empty, the
    320       badge isn't in setup mode — power-cycle it holding <code>BOOT</code>.</dd>
    321 
    322     <dt>It says the pairing code is wrong</dt>
    323     <dd>The code is 3 characters shown on the badge screen and it changes each time the
    324       badge enters setup mode (the charset deliberately has no
    325       <code>0/O</code> or <code>1/I/L</code>).</dd>
    326 
    327     <dt>The badge says <code>locked, power-cycle</code></dt>
    328     <dd><b>Five wrong pairing codes and the badge shuts its setup gate until it reboots</b>
    329       — that's on purpose, so nobody can sit in range guessing codes at your badge. Power-
    330       cycle it (hold <code>BOOT</code> if you want to go straight back into setup) and
    331       you'll get a fresh code.</dd>
    332   </dl>
    333 
    334   <h2 id="privacy">// privacy</h2>
    335 
    336   <p>It is a radio and DEF CON is full of weird people listening to radios.</p>
    337 
    338   <ul>
    339     <li>Your badge broadcasts <b>your handle in cleartext, once per second</b>, plus a
    340       stable hardware address that doesn't change.</li>
    341     <li>The HMAC signature proves a beacon came from someone with the group key. It
    342       <b>signs, it does not encrypt</b> — anyone sniffing 2.4 GHz nearby can read the
    343       handles and follow a badge around the venue.</li>
    344     <li>So: <b>pick a handle you're happy having in the air</b>, and assume presence is
    345       public.</li>
    346     <li>If you'd rather not be seen, <b>Badge Settings → Stealth</b> turns your broadcast
    347       off. You can still see other badges; they can't see you.</li>
    348     <li>Encounter logs, points, and your best-friends list stay on the badge. Nothing is
    349       uploaded anywhere — the setup app talks only to the badge in front of you, and the
    350       metrics export for post-con visualizations runs in your browser.</li>
    351     <li><b>Nobody can reconfigure your badge over the air.</b> Setup only listens while the
    352       badge is in setup mode.</li>
    353   </ul>
    354 
    355   <p style="text-align:center"><a class="totop" href="#top">↑ back to top</a></p>
    356 
    357   <footer style="text-align:center">
    358     <p>onlyn00bs · def con 34<br/>lab grown in <a href="https://virtualshack.io/">virtualtack's virtualshack</a> with some help from Claude</p><br/>
    359     <p class="small">If something here is wrong or
    360     missing, please let me know.</p>
    361   </footer>
    362 
    363 </main>
    364 <script>
    365   if ("serviceWorker" in navigator) {
    366     addEventListener("load", () => navigator.serviceWorker.register("sw.js").catch(() => {}));
    367   }
    368 </script>
    369 </body>
    370 </html>